Skip to content

Commit 35cebb6

Browse files
committed
Merge branch 'worktree-workspaces'
2 parents e18e81a + 8c59308 commit 35cebb6

8 files changed

Lines changed: 810 additions & 320 deletions
Lines changed: 2 additions & 273 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,29 @@
11
package eu.darken.butler.workspace.ui.manager
22

3-
import androidx.compose.foundation.ExperimentalFoundationApi
4-
import androidx.compose.foundation.layout.Arrangement
53
import androidx.compose.foundation.layout.Box
64
import androidx.compose.foundation.layout.BoxWithConstraints
7-
import androidx.compose.foundation.layout.Column
85
import androidx.compose.foundation.layout.PaddingValues
96
import androidx.compose.foundation.layout.fillMaxSize
107
import androidx.compose.foundation.layout.fillMaxWidth
11-
import androidx.compose.foundation.layout.padding
128
import androidx.compose.foundation.layout.size
139
import 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
2610
import 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
3111
import androidx.compose.ui.Alignment
3212
import androidx.compose.ui.Modifier
3313
import androidx.compose.ui.platform.LocalDensity
34-
import androidx.compose.ui.res.stringResource
35-
import androidx.compose.ui.unit.Dp
3614
import androidx.compose.ui.unit.dp
3715
import eu.darken.butler.R
3816
import eu.darken.butler.common.ca.toCaString
3917
import eu.darken.butler.common.compose.Preview2
4018
import eu.darken.butler.common.compose.PreviewWrapper
4119
import eu.darken.butler.common.debug.logging.log
4220
import 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

5122
enum class LayoutMode {
5223
LIST,
5324
GRID
5425
}
5526

56-
@OptIn(ExperimentalFoundationApi::class)
5727
@Composable
5828
fun 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-
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package eu.darken.butler.workspace.ui.manager
2+
3+
import android.os.Parcelable
4+
import kotlinx.parcelize.Parcelize
5+
6+
sealed class WorkspaceManagerColumnItemKey : Parcelable {
7+
@Parcelize
8+
data object StatusCard : WorkspaceManagerColumnItemKey()
9+
10+
sealed class Workspace(open val id: eu.darken.butler.workspace.core.Workspace.Id) : WorkspaceManagerColumnItemKey() {
11+
@Parcelize
12+
data class Standard(override val id: eu.darken.butler.workspace.core.Workspace.Id) : Workspace(id)
13+
14+
@Parcelize
15+
data class Compact(override val id: eu.darken.butler.workspace.core.Workspace.Id) : Workspace(id)
16+
17+
@Parcelize
18+
data class Detailed(override val id: eu.darken.butler.workspace.core.Workspace.Id) : Workspace(id)
19+
}
20+
21+
sealed class Explanation : WorkspaceManagerColumnItemKey() {
22+
@Parcelize
23+
data object BadgeExplanation : Explanation()
24+
25+
@Parcelize
26+
data object ButtonBehaviorExplanation : Explanation()
27+
28+
@Parcelize
29+
data object TutorialCard : Explanation()
30+
31+
@Parcelize
32+
data object TipsCard : Explanation()
33+
}
34+
35+
@Parcelize
36+
data class Custom(val type: String, val id: String) : WorkspaceManagerColumnItemKey()
37+
}

0 commit comments

Comments
 (0)