11package eu.darken.butler.workspace.ui.manager
22
3- import androidx.compose.foundation.ExperimentalFoundationApi
4- import androidx.compose.foundation.layout.Arrangement
53import androidx.compose.foundation.layout.Box
64import androidx.compose.foundation.layout.BoxWithConstraints
7- import androidx.compose.foundation.layout.Column
85import androidx.compose.foundation.layout.PaddingValues
96import androidx.compose.foundation.layout.fillMaxSize
107import androidx.compose.foundation.layout.fillMaxWidth
11- import androidx.compose.foundation.layout.padding
128import androidx.compose.foundation.layout.size
139import androidx.compose.foundation.layout.widthIn
14- import androidx.compose.foundation.lazy.LazyColumn
15- import androidx.compose.foundation.lazy.grid.GridCells
16- import androidx.compose.foundation.lazy.grid.GridItemSpan
17- import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
18- import androidx.compose.foundation.lazy.grid.items
19- import androidx.compose.foundation.lazy.items
20- import androidx.compose.foundation.lazy.rememberLazyListState
21- import androidx.compose.material.icons.Icons
22- import androidx.compose.material.icons.twotone.Workspaces
23- import androidx.compose.material3.Icon
24- import androidx.compose.material3.MaterialTheme
25- import androidx.compose.material3.Text
2610import androidx.compose.runtime.Composable
27- import androidx.compose.runtime.getValue
28- import androidx.compose.runtime.mutableStateOf
29- import androidx.compose.runtime.remember
30- import androidx.compose.runtime.setValue
3111import androidx.compose.ui.Alignment
3212import androidx.compose.ui.Modifier
3313import androidx.compose.ui.platform.LocalDensity
34- import androidx.compose.ui.res.stringResource
35- import androidx.compose.ui.unit.Dp
3614import androidx.compose.ui.unit.dp
3715import eu.darken.butler.R
3816import eu.darken.butler.common.ca.toCaString
3917import eu.darken.butler.common.compose.Preview2
4018import eu.darken.butler.common.compose.PreviewWrapper
4119import eu.darken.butler.common.debug.logging.log
4220import eu.darken.butler.workspace.core.Workspace
43- import eu.darken.butler.workspace.ui.manager.rows.WorkspaceBadgeExplanationCard
44- import eu.darken.butler.workspace.ui.manager.rows.WorkspaceButtonBehaviorCard
45- import eu.darken.butler.workspace.ui.manager.rows.WorkspaceGridItem
46- import eu.darken.butler.workspace.ui.manager.rows.WorkspaceListItem
47- import eu.darken.butler.workspace.ui.manager.rows.WorkspaceStatusCard
48- import sh.calvin.reorderable.ReorderableItem
49- import sh.calvin.reorderable.rememberReorderableLazyListState
5021
5122enum class LayoutMode {
5223 LIST ,
5324 GRID
5425}
5526
56- @OptIn(ExperimentalFoundationApi ::class )
5727@Composable
5828fun AdaptiveWorkspaceManagerContent (
5929 state : WorkspaceManagerViewModel .State ,
@@ -89,7 +59,7 @@ fun AdaptiveWorkspaceManagerContent(
8959
9060 when (layoutMode) {
9161 LayoutMode .LIST -> {
92- ListLayout (
62+ WorkspaceManagerListLayout (
9363 modifier = contentModifier,
9464 state = state,
9565 paddingValues = paddingValues,
@@ -102,7 +72,7 @@ fun AdaptiveWorkspaceManagerContent(
10272 )
10373 }
10474 LayoutMode .GRID -> {
105- GridLayout (
75+ WorkspaceManagerGridLayout (
10676 modifier = contentModifier,
10777 state = state,
10878 paddingValues = paddingValues,
@@ -209,246 +179,5 @@ private fun AdaptiveWorkspaceManagerContentGridPreview() {
209179 }
210180}
211181
212- @OptIn(ExperimentalFoundationApi ::class )
213- @Composable
214- private fun ListLayout (
215- modifier : Modifier = Modifier ,
216- state : WorkspaceManagerViewModel .State ,
217- paddingValues : PaddingValues ,
218- onCloseWorkspace : (Workspace .Id ) -> Unit ,
219- onReorderWorkspaces : (List <Workspace .Id >) -> Unit ,
220- onSelectWorkspace : (Workspace .Id ) -> Unit ,
221- onDismissBadgeExplanation : () -> Unit ,
222- onDismissButtonBehaviorExplanation : () -> Unit ,
223- onToggleButtonActions : () -> Unit ,
224- ) {
225- var localWorkspaceItems by remember { mutableStateOf(state.workspaces) }
226- var isDragging by remember { mutableStateOf(false ) }
227-
228- if (! isDragging) {
229- log(" WorkspaceManager" ) { " Updating local workspace items: ${state.workspaces} " }
230- localWorkspaceItems = state.workspaces
231- }
232-
233- val lazyListState = rememberLazyListState()
234- val reorderableLazyListState = rememberReorderableLazyListState(
235- lazyListState = lazyListState
236- ) { from, to ->
237- log(" WorkspaceManager" ) { " Reorder from ${from.index} to ${to.index} " }
238-
239- val fromKey = from.key as ? LazyColumnItemKey
240- val toKey = to.key as ? LazyColumnItemKey
241-
242- when {
243- fromKey is LazyColumnItemKey .Workspace && toKey is LazyColumnItemKey .Workspace -> {
244- val fromWorkspaceIndex = localWorkspaceItems.indexOfFirst { it.id == fromKey.id }
245- val toWorkspaceIndex = localWorkspaceItems.indexOfFirst { it.id == toKey.id }
246-
247- if (fromWorkspaceIndex != - 1 && toWorkspaceIndex != - 1 ) {
248- val newList = localWorkspaceItems.toMutableList()
249- val movedItem = newList.removeAt(fromWorkspaceIndex)
250- newList.add(toWorkspaceIndex, movedItem)
251- localWorkspaceItems = newList
252- }
253- }
254- }
255- }
256-
257- LazyColumn (
258- modifier = modifier,
259- state = lazyListState,
260- contentPadding = paddingValues,
261- verticalArrangement = Arrangement .spacedBy(8 .dp)
262- ) {
263- // Status card
264- if (state.workspaceCount > 0 ) {
265- item(key = LazyColumnItemKey .StatusCard ) {
266- WorkspaceStatusCard (
267- workspaceCount = state.workspaceCount,
268- operationsCount = state.operationsCount,
269- attentionCount = state.attentionCount
270- )
271- }
272- }
273-
274- if (state.workspaces.isEmpty()) {
275- item(key = LazyColumnItemKey .Custom (" empty_state" , " " )) {
276- EmptyStateContent ()
277- }
278- } else {
279- items(
280- items = localWorkspaceItems,
281- key = { workspace -> LazyColumnItemKey .Workspace .Standard (workspace.id) }
282- ) { workspace ->
283- ReorderableItem (
284- reorderableLazyListState,
285- key = LazyColumnItemKey .Workspace .Standard (workspace.id)
286- ) { itemIsDragging ->
287- WorkspaceListItem (
288- modifier = Modifier
289- .padding(horizontal = 16 .dp)
290- .animateItem(),
291- reorderableScope = this @ReorderableItem,
292- workspace = workspace,
293- onClose = { onCloseWorkspace(workspace.id) },
294- onSelect = { onSelectWorkspace(workspace.id) },
295- isDragging = itemIsDragging,
296- onDragStarted = { isDragging = true },
297- onDragStopped = {
298- isDragging = false
299- onReorderWorkspaces(localWorkspaceItems.map { it.id })
300- }
301- )
302- }
303- }
304- }
305-
306- // Explanation cards
307- if (state.showButtonBehaviorExplanation) {
308- item(key = LazyColumnItemKey .Explanation .ButtonBehaviorExplanation ) {
309- Box (modifier = Modifier .padding(horizontal = 16 .dp)) {
310- WorkspaceButtonBehaviorCard (
311- isButtonFlipped = state.isButtonActionsFlipped,
312- onToggleFlipped = { onToggleButtonActions() },
313- onDismiss = onDismissButtonBehaviorExplanation
314- )
315- }
316- }
317- }
318-
319- if (state.showBadgeExplanation) {
320- item(key = LazyColumnItemKey .Explanation .BadgeExplanation ) {
321- Box (modifier = Modifier .padding(horizontal = 16 .dp)) {
322- WorkspaceBadgeExplanationCard (
323- onDismiss = onDismissBadgeExplanation
324- )
325- }
326- }
327- }
328- }
329- }
330182
331- @Composable
332- private fun GridLayout (
333- modifier : Modifier = Modifier ,
334- state : WorkspaceManagerViewModel .State ,
335- paddingValues : PaddingValues ,
336- screenWidth : Dp ,
337- onCloseWorkspace : (Workspace .Id ) -> Unit ,
338- onReorderWorkspaces : (List <Workspace .Id >) -> Unit ,
339- onSelectWorkspace : (Workspace .Id ) -> Unit ,
340- onDismissBadgeExplanation : () -> Unit ,
341- onDismissButtonBehaviorExplanation : () -> Unit ,
342- onToggleButtonActions : () -> Unit ,
343- ) {
344- // Calculate number of columns based on screen width
345- val columns = when {
346- screenWidth < 840 .dp -> 2
347- else -> 3
348- }
349-
350- LazyVerticalGrid (
351- modifier = modifier,
352- columns = GridCells .Fixed (columns),
353- contentPadding = PaddingValues (
354- top = paddingValues.calculateTopPadding(),
355- bottom = paddingValues.calculateBottomPadding(),
356- start = 24 .dp,
357- end = 24 .dp
358- ),
359- horizontalArrangement = Arrangement .spacedBy(8 .dp),
360- verticalArrangement = Arrangement .spacedBy(8 .dp)
361- ) {
362- // Status card spanning full width
363- if (state.workspaceCount > 0 ) {
364- item(
365- key = LazyColumnItemKey .StatusCard ,
366- span = { GridItemSpan (maxLineSpan) }
367- ) {
368- WorkspaceStatusCard (
369- workspaceCount = state.workspaceCount,
370- operationsCount = state.operationsCount,
371- attentionCount = state.attentionCount
372- )
373- }
374- }
375-
376- if (state.workspaces.isEmpty()) {
377- item(
378- key = LazyColumnItemKey .Custom (" empty_state" , " " ),
379- span = { GridItemSpan (maxLineSpan) }
380- ) {
381- EmptyStateContent ()
382- }
383- } else {
384- items(
385- items = state.workspaces,
386- key = { workspace -> LazyColumnItemKey .Workspace .Standard (workspace.id) },
387- span = { GridItemSpan (1 ) }
388- ) { workspace ->
389- WorkspaceGridItem (
390- modifier = Modifier ,
391- workspace = workspace,
392- onClose = { onCloseWorkspace(workspace.id) },
393- onSelect = { onSelectWorkspace(workspace.id) }
394- )
395- }
396- }
397-
398- // Explanation cards spanning full width
399- if (state.showButtonBehaviorExplanation) {
400- item(
401- key = LazyColumnItemKey .Explanation .ButtonBehaviorExplanation ,
402- span = { GridItemSpan (maxLineSpan) }
403- ) {
404- WorkspaceButtonBehaviorCard (
405- isButtonFlipped = state.isButtonActionsFlipped,
406- onToggleFlipped = { onToggleButtonActions() },
407- onDismiss = onDismissButtonBehaviorExplanation
408- )
409- }
410- }
411-
412- if (state.showBadgeExplanation) {
413- item(
414- key = LazyColumnItemKey .Explanation .BadgeExplanation ,
415- span = { GridItemSpan (maxLineSpan) }
416- ) {
417- WorkspaceBadgeExplanationCard (
418- onDismiss = onDismissBadgeExplanation
419- )
420- }
421- }
422- }
423- }
424183
425- @Composable
426- private fun EmptyStateContent () {
427- Column (
428- modifier = Modifier
429- .fillMaxWidth()
430- .padding(32 .dp)
431- .padding(vertical = 48 .dp),
432- horizontalAlignment = Alignment .CenterHorizontally ,
433- verticalArrangement = Arrangement .Center
434- ) {
435- Icon (
436- imageVector = Icons .TwoTone .Workspaces ,
437- contentDescription = null ,
438- tint = MaterialTheme .colorScheme.outline,
439- modifier = Modifier .size(64 .dp)
440- )
441- Text (
442- text = stringResource(R .string.workspace_manager_empty_title),
443- style = MaterialTheme .typography.headlineSmall,
444- color = MaterialTheme .colorScheme.onSurface,
445- modifier = Modifier .padding(top = 16 .dp)
446- )
447- Text (
448- text = stringResource(R .string.workspace_manager_empty_subtitle),
449- style = MaterialTheme .typography.bodyMedium,
450- color = MaterialTheme .colorScheme.onSurfaceVariant,
451- modifier = Modifier .padding(top = 8 .dp)
452- )
453- }
454- }
0 commit comments