Skip to content

Commit 8f16252

Browse files
committed
Merge branch 'worktree-workspaces'
2 parents 23fc684 + cfbc147 commit 8f16252

6 files changed

Lines changed: 235 additions & 24 deletions

File tree

app-workspace/src/main/java/eu/darken/butler/workspace/ui/actions/WorkspaceActionBar.kt

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.compose.foundation.border
55
import androidx.compose.foundation.layout.Arrangement
66
import androidx.compose.foundation.layout.Box
77
import androidx.compose.foundation.layout.BoxWithConstraints
8+
import androidx.compose.foundation.layout.Column
89
import androidx.compose.foundation.layout.Row
910
import androidx.compose.foundation.layout.fillMaxWidth
1011
import androidx.compose.foundation.layout.height
@@ -14,7 +15,13 @@ import androidx.compose.foundation.layout.size
1415
import androidx.compose.foundation.shape.CircleShape
1516
import androidx.compose.foundation.shape.RoundedCornerShape
1617
import androidx.compose.material.icons.Icons
18+
import androidx.compose.material.icons.twotone.ContentCopy
19+
import androidx.compose.material.icons.twotone.Delete
20+
import androidx.compose.material.icons.twotone.Edit
1721
import androidx.compose.material.icons.twotone.MoreVert
22+
import androidx.compose.material.icons.twotone.Refresh
23+
import androidx.compose.material.icons.twotone.Share
24+
import androidx.compose.material.icons.twotone.Star
1825
import androidx.compose.material3.Card
1926
import androidx.compose.material3.CardDefaults
2027
import androidx.compose.material3.DropdownMenu
@@ -31,12 +38,18 @@ import androidx.compose.runtime.remember
3138
import androidx.compose.runtime.setValue
3239
import androidx.compose.ui.Alignment
3340
import androidx.compose.ui.Modifier
41+
import androidx.compose.ui.graphics.vector.ImageVector
3442
import androidx.compose.ui.platform.LocalContext
3543
import androidx.compose.ui.res.pluralStringResource
3644
import androidx.compose.ui.res.stringResource
3745
import androidx.compose.ui.unit.Dp
3846
import androidx.compose.ui.unit.dp
47+
import eu.darken.butler.common.ca.CaString
48+
import eu.darken.butler.common.ca.toCaString
49+
import eu.darken.butler.common.compose.Preview2
50+
import eu.darken.butler.common.compose.PreviewWrapper
3951
import eu.darken.butler.workspace.R
52+
import eu.darken.butler.common.R as CommonR
4053

4154
/**
4255
* Shared action bar for workspace modules
@@ -242,3 +255,100 @@ fun WorkspaceActionBar(
242255
}
243256
}
244257
}
258+
259+
@Preview2
260+
@Composable
261+
private fun WorkspaceActionBarPreview() {
262+
PreviewWrapper {
263+
Column(
264+
verticalArrangement = Arrangement.spacedBy(16.dp),
265+
modifier = Modifier.padding(16.dp)
266+
) {
267+
// Sample action implementation for preview
268+
data class SampleAction(
269+
override val icon: ImageVector,
270+
override val label: CaString,
271+
override val isVisible: Boolean = true,
272+
override val isEnabled: Boolean = true,
273+
override val isDestructive: Boolean = false,
274+
override val group: WorkspaceAction.Group = WorkspaceAction.Group.PRIMARY,
275+
override val badge: Boolean = false,
276+
) : WorkspaceAction
277+
278+
// Basic action bar with a few actions
279+
WorkspaceActionBar(
280+
actions = listOf(
281+
SampleAction(Icons.TwoTone.Share, CommonR.string.general_share_action.toCaString()),
282+
SampleAction(Icons.TwoTone.Edit, CommonR.string.general_edit_action.toCaString()),
283+
SampleAction(
284+
Icons.TwoTone.Delete,
285+
CommonR.string.general_delete_action.toCaString(),
286+
isDestructive = true
287+
),
288+
),
289+
onActionClick = {},
290+
)
291+
292+
// With selection count (selection mode)
293+
WorkspaceActionBar(
294+
actions = listOf(
295+
SampleAction(Icons.TwoTone.ContentCopy, CommonR.string.general_copy_action.toCaString()),
296+
SampleAction(
297+
Icons.TwoTone.Delete,
298+
CommonR.string.general_delete_action.toCaString(),
299+
isDestructive = true
300+
),
301+
),
302+
onActionClick = {},
303+
selectionCount = 5,
304+
)
305+
306+
// With overflow menu (more actions than fit)
307+
WorkspaceActionBar(
308+
actions = listOf(
309+
SampleAction(Icons.TwoTone.Share, CommonR.string.general_share_action.toCaString()),
310+
SampleAction(Icons.TwoTone.Edit, CommonR.string.general_edit_action.toCaString()),
311+
SampleAction(Icons.TwoTone.ContentCopy, CommonR.string.general_copy_action.toCaString()),
312+
SampleAction(
313+
Icons.TwoTone.Star,
314+
CommonR.string.general_view_action.toCaString(),
315+
group = WorkspaceAction.Group.SECONDARY
316+
),
317+
SampleAction(
318+
Icons.TwoTone.Refresh,
319+
CommonR.string.general_refresh_action.toCaString(),
320+
group = WorkspaceAction.Group.SECONDARY
321+
),
322+
SampleAction(
323+
Icons.TwoTone.Delete,
324+
CommonR.string.general_delete_action.toCaString(),
325+
isDestructive = true
326+
),
327+
),
328+
onActionClick = {},
329+
)
330+
331+
// With badges and disabled action
332+
WorkspaceActionBar(
333+
actions = listOf(
334+
SampleAction(
335+
Icons.TwoTone.Refresh,
336+
CommonR.string.general_refresh_action.toCaString(),
337+
badge = true
338+
),
339+
SampleAction(
340+
Icons.TwoTone.Edit,
341+
CommonR.string.general_edit_action.toCaString(),
342+
isEnabled = false
343+
),
344+
SampleAction(
345+
Icons.TwoTone.Delete,
346+
CommonR.string.general_delete_action.toCaString(),
347+
isDestructive = true
348+
),
349+
),
350+
onActionClick = {},
351+
)
352+
}
353+
}
354+
}

app/src/main/java/eu/darken/butler/workspace/ui/manager/WorkspaceManagerScreen.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package eu.darken.butler.workspace.ui.manager
22

3-
import android.os.Parcelable
43
import androidx.compose.foundation.layout.Arrangement
54
import androidx.compose.foundation.layout.Box
65
import androidx.compose.foundation.layout.Row
@@ -22,6 +21,7 @@ import androidx.compose.material3.TopAppBar
2221
import androidx.compose.material3.TopAppBarDefaults
2322
import androidx.compose.material3.rememberTopAppBarState
2423
import androidx.compose.runtime.Composable
24+
import androidx.compose.runtime.LaunchedEffect
2525
import androidx.compose.runtime.getValue
2626
import androidx.compose.runtime.mutableStateOf
2727
import androidx.compose.runtime.remember
@@ -49,6 +49,11 @@ import eu.darken.butler.workspace.core.Workspace
4949
fun WorkspaceManagerScreenHost(
5050
vm: WorkspaceManagerViewModel = hiltViewModel()
5151
) {
52+
// Invalidate preview cache every time the screen appears
53+
LaunchedEffect(Unit) {
54+
vm.onScreenAppeared()
55+
}
56+
5257
ErrorEventHandler(vm)
5358

5459
val state by waitForState(vm.state)

app/src/main/java/eu/darken/butler/workspace/ui/manager/WorkspaceManagerViewModel.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import eu.darken.butler.workspace.core.WorkspaceAction
1212
import eu.darken.butler.workspace.core.WorkspaceRepo
1313
import eu.darken.butler.workspace.core.WorkspaceSettings
1414
import eu.darken.butler.workspace.ui.WorkspacePageManager
15+
import eu.darken.butler.workspace.ui.manager.preview.WorkspacePreviewManager
1516
import kotlinx.coroutines.flow.combine
1617
import javax.inject.Inject
1718

@@ -22,6 +23,7 @@ class WorkspaceManagerViewModel @Inject constructor(
2223
private val workspaceRepo: WorkspaceRepo,
2324
private val workspaceSettings: WorkspaceSettings,
2425
private val workspacePageManager: WorkspacePageManager,
26+
private val workspacePreviewManager: WorkspacePreviewManager,
2527
) : ViewModel4(dispatchers, logTag("Workspace", "Manager", "VM"), navCtrl) {
2628

2729
val state = combine(
@@ -79,6 +81,11 @@ class WorkspaceManagerViewModel @Inject constructor(
7981
workspaceRepo.execute(WorkspaceAction.CloseAll)
8082
}
8183

84+
fun onScreenAppeared() = launch {
85+
log(tag) { "onScreenAppeared() - invalidating focused workspace preview" }
86+
workspacePreviewManager.invalidateFocusedWorkspacePreview()
87+
}
88+
8289
data class State(
8390
val workspaces: List<WorkspaceItem> = emptyList(),
8491
val showBadgeExplanation: Boolean = true,

app/src/main/java/eu/darken/butler/workspace/ui/manager/preview/WorkspacePreviewCaptureService.kt

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@ package eu.darken.butler.workspace.ui.manager.preview
22

33
import android.content.Context
44
import android.graphics.Bitmap
5-
import androidx.compose.foundation.layout.Box
6-
import androidx.compose.foundation.layout.fillMaxSize
7-
import androidx.compose.ui.Modifier
85
import androidx.compose.ui.unit.DpSize
96
import androidx.lifecycle.ViewModelStoreOwner
107
import eu.darken.butler.common.coroutine.DispatcherProvider
118
import eu.darken.butler.common.debug.logging.Logging.Priority.*
129
import eu.darken.butler.common.debug.logging.asLog
1310
import eu.darken.butler.common.debug.logging.log
1411
import eu.darken.butler.common.debug.logging.logTag
12+
import eu.darken.butler.common.theming.MyAppTheme
13+
import eu.darken.butler.common.theming.themeState
14+
import eu.darken.butler.main.core.GeneralSettings
1515
import eu.darken.butler.workspace.core.Workspace
1616
import eu.darken.butler.workspace.ui.manager.WorkspaceDesign
1717
import eu.darken.butler.workspace.ui.workspaces.WorkspaceMapper
1818
import eu.darken.butler.workspace.ui.workspaces.WorkspacePaneInfo
19+
import kotlinx.coroutines.flow.first
1920
import kotlinx.coroutines.withContext
2021
import javax.inject.Inject
2122
import javax.inject.Singleton
@@ -29,6 +30,7 @@ import javax.inject.Singleton
2930
class WorkspacePreviewCaptureService @Inject constructor(
3031
private val composableBitmapRenderer: ComposableBitmapRenderer,
3132
private val dispatcherProvider: DispatcherProvider,
33+
private val generalSettings: GeneralSettings,
3234
) {
3335

3436
suspend fun captureWorkspace(
@@ -40,22 +42,24 @@ class WorkspacePreviewCaptureService @Inject constructor(
4042
): Bitmap? = try {
4143
log(TAG, INFO) { "Capturing preview for workspace ${workspaceId.shortTag} (${workspaceType})" }
4244

45+
val themeState = generalSettings.themeState.first()
46+
4347
withContext(dispatcherProvider.Main) {
4448
composableBitmapRenderer.renderToBitmap(
4549
canvasSize = size,
4650
captureContext = captureContext,
4751
viewModelStoreOwner = viewmodelStoreOwner,
4852
) {
49-
Box(modifier = Modifier.fillMaxSize()) {
50-
WorkspaceMapper(
51-
info = WorkspacePaneInfo(
52-
id = workspaceId,
53-
type = workspaceType,
54-
),
55-
design = WorkspaceDesign(
56-
layout = WorkspaceDesign.Layout.SINGLE
57-
),
58-
)
53+
MyAppTheme(state = themeState) {
54+
WorkspaceMapper(
55+
info = WorkspacePaneInfo(
56+
id = workspaceId,
57+
type = workspaceType,
58+
),
59+
design = WorkspaceDesign(
60+
layout = WorkspaceDesign.Layout.SINGLE
61+
),
62+
)
5963
}
6064
}
6165
}

app/src/main/java/eu/darken/butler/workspace/ui/manager/preview/WorkspacePreviewFetcher.kt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import eu.darken.butler.common.coil.use
1818
import eu.darken.butler.common.debug.logging.Logging.Priority.*
1919
import eu.darken.butler.common.debug.logging.log
2020
import eu.darken.butler.common.debug.logging.logTag
21+
import eu.darken.butler.common.hasApiLevel
2122
import eu.darken.butler.main.ui.MainActivity
2223
import eu.darken.butler.workspace.core.WorkspaceRepo
2324
import eu.darken.butler.workspace.core.preview.WorkspacePreviewModel
@@ -65,7 +66,7 @@ class WorkspacePreviewFetcher @Inject constructor(
6566
source = bufferedSource,
6667
fileSystem = FileSystem.SYSTEM,
6768
),
68-
mimeType = "image/png",
69+
mimeType = "image/webp",
6970
dataSource = DataSource.DISK
7071
)
7172
}
@@ -102,16 +103,23 @@ class WorkspacePreviewFetcher @Inject constructor(
102103
log(TAG) { "Successfully captured preview for ${data.workspaceId.shortTag}" }
103104

104105
if (options.diskCachePolicy.writeEnabled) {
105-
val pngBytes = ByteArrayOutputStream().use { stream ->
106-
bitmap.compress(Bitmap.CompressFormat.PNG, 85, stream)
106+
val webpBytes = ByteArrayOutputStream().use { stream ->
107+
bitmap.compress(
108+
@Suppress("NewApi")
109+
if (hasApiLevel(30)) Bitmap.CompressFormat.WEBP_LOSSY else Bitmap.CompressFormat.JPEG,
110+
25,
111+
stream
112+
)
107113
stream.toByteArray()
108114
}
109115
diskCache.value?.openEditor(cacheKey)?.use { editor ->
110116
diskCache.value!!.fileSystem.write(editor.data) {
111-
write(pngBytes)
117+
write(webpBytes)
112118
}
113119
log(TAG) { "Wrote preview to disk cache for ${data.workspaceId.shortTag}" }
114120
}
121+
} else {
122+
log(TAG, WARN) { "Disk cache write is disabled for ${data.workspaceId.shortTag}" }
115123
}
116124

117125
return ImageFetchResult(
@@ -144,6 +152,6 @@ class WorkspacePreviewFetcher @Inject constructor(
144152
}
145153

146154
companion object {
147-
private val TAG = logTag("Workspace", "PreviewFetcher")
155+
private val TAG = logTag("Workspace", "Preview", "Fetcher")
148156
}
149157
}

0 commit comments

Comments
 (0)