Skip to content

Commit 87a92f9

Browse files
authored
Apply architecture iteration - Part 3 (#663)
Apply arch iteration on `EditTaskListScreen`. Also apply fix on `BaseUI` where `remember` prevented update new `eventHandler` even if `contentState` was re-created.
1 parent 951bf88 commit 87a92f9

File tree

10 files changed

+281
-81
lines changed

10 files changed

+281
-81
lines changed

app-feature/edittasklist/src/commonMain/kotlin/dev/sergiobelda/todometer/app/feature/edittasklist/di/EditTaskListViewModelModule.kt

+5-2
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
package dev.sergiobelda.todometer.app.feature.edittasklist.di
1818

1919
import dev.sergiobelda.todometer.app.feature.edittasklist.ui.EditTaskListViewModel
20-
import org.koin.core.module.dsl.viewModelOf
20+
import dev.sergiobelda.todometer.common.ui.base.di.baseViewModelOf
21+
import org.koin.core.module.dsl.named
2122
import org.koin.dsl.module
2223

2324
val editTaskListViewModelModule = module {
24-
viewModelOf(::EditTaskListViewModel)
25+
baseViewModelOf(::EditTaskListViewModel) {
26+
named<EditTaskListViewModel>()
27+
}
2528
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2025 Sergio Belda
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dev.sergiobelda.todometer.app.feature.edittasklist.navigation
18+
19+
import dev.sergiobelda.todometer.common.ui.base.navigation.BaseNavigationEvent
20+
21+
sealed class EditTaskListNavigationEvent : BaseNavigationEvent {
22+
data object NavigateBack : EditTaskListNavigationEvent()
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2025 Sergio Belda
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dev.sergiobelda.todometer.app.feature.edittasklist.navigation
18+
19+
import dev.sergiobelda.todometer.common.ui.base.navigation.BaseNavigationEventHandler
20+
21+
fun editTaskListNavigationEventHandler(
22+
navigateBack: () -> Unit,
23+
): BaseNavigationEventHandler<EditTaskListNavigationEvent> = BaseNavigationEventHandler {
24+
when (it) {
25+
EditTaskListNavigationEvent.NavigateBack -> navigateBack()
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2025 Sergio Belda
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dev.sergiobelda.todometer.app.feature.edittasklist.ui
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.getValue
21+
import androidx.compose.runtime.mutableStateOf
22+
import androidx.compose.runtime.saveable.Saver
23+
import androidx.compose.runtime.saveable.rememberSaveable
24+
import androidx.compose.runtime.setValue
25+
import dev.sergiobelda.todometer.common.ui.base.BaseContentState
26+
import dev.sergiobelda.todometer.common.ui.base.BaseEvent
27+
28+
data class EditTaskListContentState internal constructor(
29+
private val taskListName: String,
30+
) : BaseContentState {
31+
32+
var nameTextFieldValue: String by mutableStateOf(taskListName)
33+
private set
34+
35+
override fun handleEvent(event: BaseEvent) {
36+
when (event) {
37+
is EditTaskListEvent.TaskListNameValueChange -> {
38+
nameTextFieldValue = event.value
39+
}
40+
}
41+
}
42+
43+
companion object {
44+
fun saver(): Saver<EditTaskListContentState, String> = Saver(
45+
save = {
46+
it.nameTextFieldValue
47+
},
48+
restore = {
49+
EditTaskListContentState(
50+
taskListName = it,
51+
)
52+
},
53+
)
54+
}
55+
}
56+
57+
@Composable
58+
fun rememberEditTaskListContentState(
59+
taskListNameInputValue: String,
60+
): EditTaskListContentState = rememberSaveable(
61+
inputs = arrayOf(taskListNameInputValue),
62+
saver = EditTaskListContentState.saver(),
63+
) {
64+
EditTaskListContentState(
65+
taskListName = taskListNameInputValue,
66+
)
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2025 Sergio Belda
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dev.sergiobelda.todometer.app.feature.edittasklist.ui
18+
19+
import dev.sergiobelda.todometer.common.ui.base.BaseEvent
20+
21+
sealed class EditTaskListEvent : BaseEvent {
22+
data class UpdateTaskList(val name: String) : EditTaskListEvent()
23+
data class TaskListNameValueChange(val value: String) : EditTaskListEvent()
24+
}

app-feature/edittasklist/src/commonMain/kotlin/dev/sergiobelda/todometer/app/feature/edittasklist/ui/EditTaskListScreen.kt

+93-51
Original file line numberDiff line numberDiff line change
@@ -19,82 +19,124 @@ package dev.sergiobelda.todometer.app.feature.edittasklist.ui
1919
import androidx.compose.foundation.layout.Column
2020
import androidx.compose.foundation.layout.padding
2121
import androidx.compose.foundation.text.KeyboardOptions
22+
import androidx.compose.material3.MaterialTheme
2223
import androidx.compose.material3.Scaffold
2324
import androidx.compose.material3.Text
2425
import androidx.compose.runtime.Composable
25-
import androidx.compose.runtime.getValue
26-
import androidx.compose.runtime.mutableStateOf
27-
import androidx.compose.runtime.remember
28-
import androidx.compose.runtime.saveable.rememberSaveable
29-
import androidx.compose.runtime.setValue
3026
import androidx.compose.ui.Modifier
3127
import androidx.compose.ui.text.input.ImeAction
3228
import androidx.compose.ui.text.input.KeyboardCapitalization
3329
import dev.sergiobelda.navigation.compose.extended.annotation.NavDestination
3430
import dev.sergiobelda.todometer.app.common.designsystem.components.TodometerTitledTextField
31+
import dev.sergiobelda.todometer.app.common.designsystem.theme.Alpha.applyMediumEmphasisAlpha
3532
import dev.sergiobelda.todometer.app.common.ui.components.SaveActionTopAppBar
3633
import dev.sergiobelda.todometer.app.common.ui.loading.LoadingScreenDialog
3734
import dev.sergiobelda.todometer.app.common.ui.values.TextFieldPadding
35+
import dev.sergiobelda.todometer.app.feature.edittasklist.navigation.EditTaskListNavigationEvent
3836
import dev.sergiobelda.todometer.common.resources.TodometerResources
37+
import dev.sergiobelda.todometer.common.ui.base.BaseUI
3938

40-
@NavDestination(
41-
destinationId = "edittasklist",
42-
name = "EditTaskList",
43-
)
44-
@Composable
45-
fun EditTaskListScreen(
46-
navigateBack: () -> Unit,
47-
viewModel: EditTaskListViewModel,
48-
) {
49-
when {
50-
viewModel.state.isLoading -> {
51-
LoadingScreenDialog(navigateBack)
52-
}
39+
data object EditTaskListScreen : BaseUI<EditTaskListUIState, EditTaskListContentState>() {
5340

54-
!viewModel.state.isLoading -> {
55-
viewModel.state.taskList?.let { taskList ->
56-
var taskListName by rememberSaveable { mutableStateOf(taskList.name) }
57-
var taskListNameInputError by remember { mutableStateOf(false) }
41+
@Composable
42+
override fun rememberContentState(
43+
uiState: EditTaskListUIState,
44+
): EditTaskListContentState = rememberEditTaskListContentState(
45+
taskListNameInputValue = uiState.taskList?.name ?: "",
46+
)
5847

48+
@NavDestination(
49+
destinationId = "edittasklist",
50+
name = "EditTaskList",
51+
)
52+
@Composable
53+
override fun Content(
54+
uiState: EditTaskListUIState,
55+
contentState: EditTaskListContentState,
56+
) {
57+
when {
58+
uiState.isLoading -> {
59+
LoadingScreenDialog(
60+
navigateBack = {
61+
onEvent(
62+
EditTaskListNavigationEvent.NavigateBack,
63+
)
64+
},
65+
)
66+
}
67+
68+
!uiState.isLoading -> {
5969
Scaffold(
6070
topBar = {
61-
SaveActionTopAppBar(
62-
navigateBack = navigateBack,
63-
title = TodometerResources.strings.editTaskList,
64-
isSaveButtonEnabled = !viewModel.state.isLoading,
71+
EditTaskListTopBar(
72+
isSaveButtonEnabled = !uiState.isLoading,
6573
onSaveButtonClick = {
66-
if (taskListName.isBlank()) {
67-
taskListNameInputError = true
68-
} else {
69-
viewModel.updateTaskList(taskListName)
70-
navigateBack()
71-
}
74+
onEvent(
75+
EditTaskListEvent.UpdateTaskList(
76+
contentState.nameTextFieldValue,
77+
),
78+
)
79+
onEvent(EditTaskListNavigationEvent.NavigateBack)
7280
},
7381
)
7482
},
7583
content = { paddingValues ->
76-
Column(modifier = Modifier.padding(paddingValues)) {
77-
TodometerTitledTextField(
78-
title = TodometerResources.strings.name,
79-
value = taskListName,
80-
onValueChange = {
81-
taskListName = it
82-
taskListNameInputError = false
83-
},
84-
placeholder = { Text(TodometerResources.strings.enterTaskListName) },
85-
singleLine = true,
86-
isError = taskListNameInputError,
87-
errorMessage = TodometerResources.strings.fieldNotEmpty,
88-
keyboardOptions = KeyboardOptions(
89-
capitalization = KeyboardCapitalization.Sentences,
90-
imeAction = ImeAction.Done,
91-
),
92-
modifier = Modifier.padding(TextFieldPadding),
93-
)
94-
}
84+
EditTaskListContent(
85+
taskListNameValue = contentState.nameTextFieldValue,
86+
modifier = Modifier
87+
.padding(paddingValues),
88+
)
9589
},
9690
)
9791
}
9892
}
9993
}
94+
95+
@Composable
96+
private fun EditTaskListTopBar(
97+
isSaveButtonEnabled: Boolean,
98+
onSaveButtonClick: () -> Unit,
99+
) {
100+
SaveActionTopAppBar(
101+
navigateBack = {
102+
onEvent(
103+
EditTaskListNavigationEvent.NavigateBack,
104+
)
105+
},
106+
title = TodometerResources.strings.editTaskList,
107+
isSaveButtonEnabled = isSaveButtonEnabled,
108+
onSaveButtonClick = onSaveButtonClick,
109+
saveButtonTintColor = if (isSaveButtonEnabled) {
110+
MaterialTheme.colorScheme.primary
111+
} else {
112+
MaterialTheme.colorScheme.onSurface.applyMediumEmphasisAlpha()
113+
},
114+
)
115+
}
116+
117+
@Composable
118+
private fun EditTaskListContent(
119+
taskListNameValue: String,
120+
modifier: Modifier,
121+
) {
122+
Column(
123+
modifier = modifier,
124+
) {
125+
TodometerTitledTextField(
126+
title = TodometerResources.strings.name,
127+
value = taskListNameValue,
128+
onValueChange = {
129+
onEvent(EditTaskListEvent.TaskListNameValueChange(it))
130+
},
131+
placeholder = { Text(TodometerResources.strings.enterTaskListName) },
132+
singleLine = true,
133+
errorMessage = TodometerResources.strings.fieldNotEmpty,
134+
keyboardOptions = KeyboardOptions(
135+
capitalization = KeyboardCapitalization.Sentences,
136+
imeAction = ImeAction.Done,
137+
),
138+
modifier = Modifier.padding(TextFieldPadding),
139+
)
140+
}
141+
}
100142
}

app-feature/edittasklist/src/commonMain/kotlin/dev/sergiobelda/todometer/app/feature/edittasklist/ui/EditTaskListState.kt app-feature/edittasklist/src/commonMain/kotlin/dev/sergiobelda/todometer/app/feature/edittasklist/ui/EditTaskListUIState.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 Sergio Belda
2+
* Copyright 2025 Sergio Belda
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,11 +18,12 @@ package dev.sergiobelda.todometer.app.feature.edittasklist.ui
1818

1919
import androidx.compose.runtime.Immutable
2020
import dev.sergiobelda.todometer.common.domain.model.TaskList
21+
import dev.sergiobelda.todometer.common.ui.base.BaseUIState
2122
import dev.sergiobelda.todometer.common.ui.error.ErrorUi
2223

2324
@Immutable
24-
data class EditTaskListState(
25+
data class EditTaskListUIState(
2526
val isLoading: Boolean = false,
2627
val taskList: TaskList? = null,
2728
val errorUi: ErrorUi? = null,
28-
)
29+
) : BaseUIState

0 commit comments

Comments
 (0)