Skip to content

Commit 844d298

Browse files
committed
Refactor WizardScreen to use ViewModel
Introduces `WizardViewModel` to manage the state and logic of the `WizardScreen`. This change centralizes state management and improves the testability of the screen.
1 parent cec7393 commit 844d298

4 files changed

Lines changed: 124 additions & 62 deletions

File tree

app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ dependencies {
5757
implementation(libs.androidx.adaptive)
5858
implementation(libs.androidx.adaptive.layout)
5959
implementation(libs.androidx.adaptive.navigation)
60+
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.4")
61+
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4")
6062

6163
// Patch annotations + processor
6264
implementation(project(":patch-annotations"))

app/src/main/java/de/berlindroid/zepatch/MainActivity.kt

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,13 @@ import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaf
5252
import androidx.compose.material3.rememberTopAppBarState
5353
import androidx.compose.runtime.Composable
5454
import androidx.compose.runtime.getValue
55-
import androidx.compose.runtime.mutableIntStateOf
56-
import androidx.compose.runtime.mutableStateOf
57-
import androidx.compose.runtime.remember
5855
import androidx.compose.runtime.rememberCoroutineScope
59-
import androidx.compose.runtime.setValue
6056
import androidx.compose.ui.Modifier
6157
import androidx.compose.ui.graphics.ImageBitmap
6258
import androidx.compose.ui.input.nestedscroll.nestedScroll
6359
import androidx.compose.ui.platform.LocalContext
6460
import androidx.compose.ui.unit.dp
61+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
6562
import com.embroidermodder.punching.Histogram
6663
import de.berlindroid.zepatch.PatchablePreviewMode.BITMAP
6764
import de.berlindroid.zepatch.PatchablePreviewMode.COMPOSABLE
@@ -75,9 +72,10 @@ import de.berlindroid.zepatch.ui.theme.ZePatchTheme
7572
import de.berlindroid.zepatch.utils.multiLet
7673
import de.berlindroid.zepatch.utils.uppercaseWords
7774
import kotlinx.coroutines.launch
75+
import androidx.lifecycle.viewmodel.compose.viewModel as lifecycleViewModel
7876

7977

80-
private enum class PatchablePreviewMode {
78+
enum class PatchablePreviewMode {
8179
COMPOSABLE, BITMAP, REDUCED_BITMAP, STITCHES
8280
}
8381

@@ -197,18 +195,15 @@ private fun PatchableDetail(
197195
onBackClick: () -> Unit,
198196
patchable: @Composable (Boolean, (ImageBitmap) -> Unit) -> Unit,
199197
) {
200-
var imageBitmap by remember { mutableStateOf<ImageBitmap?>(null) }
201-
var reducedImageBitmap by remember { mutableStateOf<ImageBitmap?>(null) }
202-
var reducedHistogram by remember { mutableStateOf<Histogram?>(null) }
203-
var colorCount by remember { mutableIntStateOf(3) }
204-
var embroideryData by remember { mutableStateOf<ByteArray?>(null) }
205-
var embroideryPreviewImage by remember { mutableStateOf<ImageBitmap?>(null) }
206-
207198
val context = LocalContext.current
199+
200+
val viewModel: WizardViewModel = lifecycleViewModel()
201+
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
202+
208203
val launcher = rememberLauncherForActivityResult(
209204
contract = ActivityResultContracts.StartActivityForResult()
210205
) { result ->
211-
savePesAfterSelection(context, result, embroideryData)
206+
savePesAfterSelection(context, result, uiState.embroideryData)
212207
}
213208

214209
Scaffold(
@@ -232,38 +227,35 @@ private fun PatchableDetail(
232227
Column(
233228
modifier = Modifier.padding(innerPadding),
234229
) {
235-
var currentMode by remember { mutableStateOf(COMPOSABLE) }
236-
237-
ProgressPills(imageBitmap, reducedImageBitmap, currentMode)
230+
ProgressPills(uiState.imageBitmap, uiState.reducedImageBitmap, uiState.previewMode)
238231

239232
WizardContent(
240-
currentMode,
241-
imageBitmap,
242-
colorCount,
243-
reducedImageBitmap,
244-
reducedHistogram,
233+
uiState.previewMode,
234+
uiState.imageBitmap,
235+
uiState.colorCount,
236+
uiState.reducedImageBitmap,
237+
uiState.reducedHistogram,
245238
name,
246-
onBitmapUpdated = { imageBitmap = it },
247-
onColorCountUpdated = { colorCount = it },
248-
onReducedUpdated = { img, histo ->
249-
reducedImageBitmap = img;reducedHistogram = histo
250-
},
251-
onEmbroideryUpdated = { data, preview ->
252-
embroideryData = data
253-
embroideryPreviewImage = preview
254-
},
239+
onBitmapUpdated = viewModel::updateBitmap,
240+
onColorCountUpdated = viewModel::updateColorCount,
241+
computeReducedBitmap = viewModel::computeReducedBitmap,
242+
onReducedUpdated = viewModel::setReducedResult,
243+
onEmbroideryUpdated = viewModel::updateEmbroidery,
255244
patchable
256245
)
257246

258247
WizardButtons(
259-
currentMode,
260-
imageBitmap,
261-
reducedImageBitmap,
262-
embroideryData,
248+
uiState.previewMode,
249+
uiState.imageBitmap,
250+
uiState.reducedImageBitmap,
251+
uiState.embroideryData,
263252
name,
264253
launcher
265254
) {
266-
currentMode = it
255+
viewModel.setPreviewMode(it)
256+
if (it == PatchablePreviewMode.REDUCED_BITMAP) {
257+
viewModel.computeReducedBitmap()
258+
}
267259
}
268260
}
269261
}
@@ -279,6 +271,7 @@ private fun WizardContent(
279271
name: String,
280272
onBitmapUpdated: (ImageBitmap) -> Unit,
281273
onColorCountUpdated: (Int) -> Unit,
274+
computeReducedBitmap: () -> Unit,
282275
onReducedUpdated: (ImageBitmap, Histogram) -> Unit,
283276
onEmbroideryUpdated: (ByteArray, ImageBitmap) -> Unit,
284277
patchable: @Composable (Boolean, (ImageBitmap) -> Unit) -> Unit,
@@ -301,7 +294,8 @@ private fun WizardContent(
301294
image = imageBitmap,
302295
colorCount = colorCount,
303296
onColorCountChanged = onColorCountUpdated,
304-
onReduced = onReducedUpdated,
297+
computeReducedBitmap = computeReducedBitmap,
298+
reducedImage = reducedImageBitmap,
305299
)
306300

307301
STITCHES -> reducedImageBitmap?.multiLet(reducedHistogram) { img, histo ->
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package de.berlindroid.zepatch
2+
3+
import android.graphics.Bitmap
4+
import androidx.lifecycle.ViewModel
5+
import androidx.lifecycle.viewModelScope
6+
import androidx.compose.ui.graphics.ImageBitmap
7+
import androidx.compose.ui.graphics.asAndroidBitmap
8+
import androidx.compose.ui.graphics.asImageBitmap
9+
import androidx.core.graphics.scale
10+
import com.embroidermodder.punching.Histogram
11+
import com.embroidermodder.punching.reduceColors
12+
import de.berlindroid.zepatch.PatchablePreviewMode.COMPOSABLE
13+
import kotlinx.coroutines.Dispatchers
14+
import kotlinx.coroutines.flow.MutableStateFlow
15+
import kotlinx.coroutines.flow.StateFlow
16+
import kotlinx.coroutines.flow.asStateFlow
17+
import kotlinx.coroutines.launch
18+
19+
/**
20+
* ViewModel to manage the Wizard screen state.
21+
*/
22+
class WizardViewModel : ViewModel() {
23+
24+
data class UIState(
25+
val imageBitmap: ImageBitmap? = null,
26+
val reducedImageBitmap: ImageBitmap? = null,
27+
val reducedHistogram: Histogram? = null,
28+
val colorCount: Int = 3,
29+
val embroideryData: ByteArray? = null,
30+
val embroideryPreviewImage: ImageBitmap? = null,
31+
val previewMode: PatchablePreviewMode = COMPOSABLE,
32+
)
33+
34+
private val _uiState = MutableStateFlow(UIState())
35+
val uiState: StateFlow<UIState> = _uiState.asStateFlow()
36+
37+
fun setPreviewMode(mode: PatchablePreviewMode) {
38+
_uiState.value = _uiState.value.copy(previewMode = mode)
39+
}
40+
41+
fun updateColorCount(count: Int) {
42+
_uiState.value = _uiState.value.copy(colorCount = count)
43+
}
44+
45+
fun updateBitmap(bitmap: ImageBitmap) {
46+
_uiState.value = _uiState.value.copy(
47+
imageBitmap = bitmap,
48+
// Reset downstream results when source bitmap changes
49+
reducedImageBitmap = null,
50+
reducedHistogram = null,
51+
embroideryData = null,
52+
embroideryPreviewImage = null,
53+
)
54+
}
55+
56+
fun computeReducedBitmap() {
57+
val state = _uiState.value
58+
val image = state.imageBitmap ?: return
59+
val colorCount = state.colorCount
60+
viewModelScope.launch(Dispatchers.IO) {
61+
val aspect = image.width / image.height.toFloat()
62+
val (reducedBmp, histogram) = image.asAndroidBitmap()
63+
.copy(Bitmap.Config.ARGB_8888, false)
64+
.scale((512 * aspect).toInt(), 512, false)
65+
.reduceColors(colorCount)
66+
67+
_uiState.value = _uiState.value.copy(
68+
reducedImageBitmap = reducedBmp.asImageBitmap(),
69+
reducedHistogram = histogram,
70+
)
71+
}
72+
}
73+
74+
fun updateEmbroidery(data: ByteArray, preview: ImageBitmap) {
75+
_uiState.value = _uiState.value.copy(
76+
embroideryData = data,
77+
embroideryPreviewImage = preview,
78+
)
79+
}
80+
81+
fun setReducedResult(image: ImageBitmap, histogram: Histogram) {
82+
_uiState.value = _uiState.value.copy(
83+
reducedImageBitmap = image,
84+
reducedHistogram = histogram,
85+
)
86+
}
87+
}

app/src/main/java/de/berlindroid/zepatch/ui/PatchableToReducedBitmap.kt

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,10 @@ fun PatchableToReducedBitmap(
3535
modifier: Modifier = Modifier,
3636
image: ImageBitmap? = null,
3737
colorCount: Int = 3,
38-
onReduced: (ImageBitmap, Histogram) -> Unit = { _, _ -> },
38+
reducedImage: ImageBitmap? = null,
39+
computeReducedBitmap: () -> Unit = {},
3940
onColorCountChanged: (Int) -> Unit = {}
4041
) {
41-
42-
var reducedImage by remember { mutableStateOf<ImageBitmap?>(null) }
43-
var reducedHistogram by remember { mutableStateOf<Histogram?>(null) }
44-
val coroutineScope = rememberCoroutineScope()
45-
4642
Column(modifier = modifier.fillMaxWidth()) {
4743
image?.let {
4844
Image(
@@ -51,22 +47,7 @@ fun PatchableToReducedBitmap(
5147
modifier = Modifier.fillMaxWidth()
5248
)
5349
}
54-
Button(onClick = {
55-
// TODO: VMIZE
56-
coroutineScope.launch(Dispatchers.IO) {
57-
reducedImage = null
58-
image?.let {
59-
val aspect = it.width / it.height.toFloat()
60-
val (bitmap, histogram) = it.asAndroidBitmap()
61-
.copy(Bitmap.Config.ARGB_8888, false)
62-
.scale((512 * aspect).toInt(), 512, false)
63-
.reduceColors(colorCount)
64-
65-
reducedImage = bitmap.asImageBitmap()
66-
reducedHistogram = histogram
67-
}
68-
}
69-
}) { Text("Do it") }
50+
Button(onClick = computeReducedBitmap) { Text("Do it") }
7051

7152
TextField(
7253
value = "$colorCount",
@@ -84,13 +65,12 @@ fun PatchableToReducedBitmap(
8465
)
8566
)
8667

87-
reducedImage?.multiLet(reducedHistogram) { image, histogram ->
68+
reducedImage?.let {
8869
Image(
89-
bitmap = image,
70+
bitmap = it,
9071
contentDescription = "patch bitmap",
9172
modifier = Modifier.fillMaxWidth()
9273
)
93-
onReduced(image, histogram)
9474
} ?: CircularProgressIndicator()
9575
}
9676
}
@@ -101,7 +81,6 @@ fun PatchableToReducedBitmapPreview() {
10181
PatchableToReducedBitmap(
10282
image = ImageBitmap(width = 100, height = 100), // Example ImageBitmap
10383
colorCount = 5,
104-
onReduced = { _, _ -> },
10584
onColorCountChanged = {}
10685
)
10786
}

0 commit comments

Comments
 (0)