Skip to content

Commit d33e2ad

Browse files
authored
Merge pull request #94 from seve-andre/improve-stability
2 parents 7f65230 + b42742d commit d33e2ad

4 files changed

Lines changed: 149 additions & 86 deletions

File tree

app/src/main/kotlin/com/mitch/template/ui/screens/home/HomeScreen.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import androidx.compose.material3.Text
88
import androidx.compose.runtime.Composable
99
import androidx.compose.runtime.getValue
1010
import androidx.compose.runtime.mutableStateOf
11-
import androidx.compose.runtime.remember
11+
import androidx.compose.runtime.saveable.rememberSaveable
1212
import androidx.compose.runtime.setValue
1313
import androidx.compose.ui.Alignment
1414
import androidx.compose.ui.Modifier
@@ -23,6 +23,7 @@ import com.mitch.template.ui.designsystem.TemplateTheme
2323
import com.mitch.template.ui.designsystem.components.loading.LoadingScreen
2424
import com.mitch.template.ui.screens.home.components.LanguagePickerDialog
2525
import com.mitch.template.ui.screens.home.components.ThemePickerDialog
26+
import kotlinx.collections.immutable.toPersistentSet
2627

2728
@Composable
2829
fun HomeRoute(viewModel: HomeViewModel) {
@@ -46,20 +47,25 @@ fun HomeScreen(
4647
HomeUiState.Loading -> LoadingScreen()
4748

4849
is HomeUiState.Success -> {
49-
var activeDialog by remember { mutableStateOf(ActiveDialog.None) }
50+
var activeDialog by rememberSaveable {
51+
mutableStateOf(ActiveDialog.None)
52+
}
53+
5054
when (activeDialog) {
5155
ActiveDialog.None -> Unit
5256

5357
ActiveDialog.Language -> LanguagePickerDialog(
54-
selectedLanguage = uiState.language,
5558
onDismiss = { activeDialog = ActiveDialog.None },
56-
onConfirm = onChangeLanguage
59+
onConfirm = onChangeLanguage,
60+
selectedLanguage = uiState.language,
61+
languageOptions = TemplateLanguagePreference.entries.toPersistentSet()
5762
)
5863

5964
ActiveDialog.Theme -> ThemePickerDialog(
60-
selectedTheme = uiState.theme,
6165
onDismiss = { activeDialog = ActiveDialog.None },
62-
onConfirm = onChangeTheme
66+
onConfirm = onChangeTheme,
67+
selectedTheme = uiState.theme,
68+
themeOptions = TemplateThemePreference.entries.toPersistentSet()
6369
)
6470
}
6571

app/src/main/kotlin/com/mitch/template/ui/screens/home/HomeUiState.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.mitch.template.ui.screens.home
22

3+
import androidx.compose.runtime.Stable
34
import com.mitch.template.domain.models.TemplateLanguagePreference
45
import com.mitch.template.domain.models.TemplateThemePreference
56

7+
@Stable
68
sealed interface HomeUiState {
79
data object Loading : HomeUiState
810

app/src/main/kotlin/com/mitch/template/ui/screens/home/components/LanguagePickerDialog.kt

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ import androidx.compose.material3.Text
1818
import androidx.compose.material3.TextButton
1919
import androidx.compose.runtime.Composable
2020
import androidx.compose.runtime.getValue
21+
import androidx.compose.runtime.key
2122
import androidx.compose.runtime.mutableStateOf
22-
import androidx.compose.runtime.remember
23+
import androidx.compose.runtime.saveable.rememberSaveable
2324
import androidx.compose.runtime.setValue
2425
import androidx.compose.ui.Alignment
2526
import androidx.compose.ui.Modifier
@@ -36,14 +37,17 @@ import com.mitch.template.ui.designsystem.TemplateDesignSystem
3637
import com.mitch.template.ui.designsystem.TemplateIcons
3738
import com.mitch.template.ui.designsystem.TemplateTheme
3839
import com.mitch.template.ui.designsystem.theme.custom.padding
40+
import kotlinx.collections.immutable.PersistentSet
41+
import kotlinx.collections.immutable.toPersistentSet
3942

4043
@Composable
4144
fun LanguagePickerDialog(
42-
selectedLanguage: TemplateLanguagePreference,
4345
onDismiss: () -> Unit,
44-
onConfirm: (TemplateLanguagePreference) -> Unit
46+
onConfirm: (TemplateLanguagePreference) -> Unit,
47+
selectedLanguage: TemplateLanguagePreference,
48+
languageOptions: PersistentSet<TemplateLanguagePreference>
4549
) {
46-
var tempLanguage by remember { mutableStateOf(selectedLanguage) }
50+
var tempLanguage by rememberSaveable { mutableStateOf(selectedLanguage) }
4751

4852
AlertDialog(
4953
onDismissRequest = onDismiss,
@@ -58,44 +62,19 @@ fun LanguagePickerDialog(
5862
},
5963
text = {
6064
Column(modifier = Modifier.selectableGroup()) {
61-
for (languagePreference in TemplateLanguagePreference.entries) {
65+
for (languagePreference in languageOptions) {
6266
val isSelected = languagePreference == tempLanguage
63-
Row(
64-
modifier = Modifier
65-
.fillMaxWidth()
66-
.height(56.dp)
67-
.clip(RoundedCornerShape(16.dp))
68-
.selectable(
69-
selected = isSelected,
70-
onClick = { tempLanguage = languagePreference },
71-
role = Role.RadioButton
72-
)
73-
.padding(horizontal = TemplateDesignSystem.padding.medium),
74-
horizontalArrangement = Arrangement.spacedBy(TemplateDesignSystem.padding.medium),
75-
verticalAlignment = Alignment.CenterVertically
76-
) {
77-
RadioButton(
78-
selected = isSelected,
79-
onClick = null
67+
68+
key(languagePreference.ordinal) {
69+
LanguageOptionRadioButton(
70+
isSelected = isSelected,
71+
onClick = {
72+
if (!isSelected) {
73+
tempLanguage = languagePreference
74+
}
75+
},
76+
languagePreference = languagePreference
8077
)
81-
Row(
82-
horizontalArrangement = Arrangement.spacedBy(TemplateDesignSystem.padding.small),
83-
verticalAlignment = Alignment.CenterVertically
84-
) {
85-
Image(
86-
painter = languagePreference.flag(),
87-
contentDescription = null,
88-
modifier = Modifier.size(20.dp)
89-
)
90-
Text(
91-
text = if (languagePreference.locale == null) {
92-
stringResource(id = R.string.system_default)
93-
} else {
94-
languagePreference.locale.getDisplayLanguage(languagePreference.locale)
95-
},
96-
style = TemplateDesignSystem.typography.bodyLarge
97-
)
98-
}
9978
}
10079
}
10180
}
@@ -119,6 +98,47 @@ fun LanguagePickerDialog(
11998
)
12099
}
121100

101+
@Composable
102+
private fun LanguageOptionRadioButton(
103+
isSelected: Boolean,
104+
onClick: () -> Unit,
105+
languagePreference: TemplateLanguagePreference
106+
) {
107+
Row(
108+
modifier = Modifier
109+
.fillMaxWidth()
110+
.height(56.dp)
111+
.clip(RoundedCornerShape(16.dp))
112+
.selectable(
113+
selected = isSelected,
114+
onClick = onClick,
115+
role = Role.RadioButton
116+
)
117+
.padding(horizontal = TemplateDesignSystem.padding.medium),
118+
horizontalArrangement = Arrangement.spacedBy(TemplateDesignSystem.padding.medium),
119+
verticalAlignment = Alignment.CenterVertically
120+
) {
121+
RadioButton(
122+
selected = isSelected,
123+
onClick = null
124+
)
125+
Row(
126+
horizontalArrangement = Arrangement.spacedBy(TemplateDesignSystem.padding.small),
127+
verticalAlignment = Alignment.CenterVertically
128+
) {
129+
Image(
130+
painter = languagePreference.flag(),
131+
contentDescription = null,
132+
modifier = Modifier.size(20.dp)
133+
)
134+
Text(
135+
text = languagePreference.displayLanguage(),
136+
style = TemplateDesignSystem.typography.bodyLarge
137+
)
138+
}
139+
}
140+
}
141+
122142
@Composable
123143
private fun TemplateLanguagePreference.flag(): Painter {
124144
val flagId = when (this) {
@@ -129,14 +149,24 @@ private fun TemplateLanguagePreference.flag(): Painter {
129149
return painterResource(id = flagId)
130150
}
131151

152+
@Composable
153+
private fun TemplateLanguagePreference.displayLanguage(): String {
154+
return if (this.locale == null) {
155+
stringResource(id = R.string.system_default)
156+
} else {
157+
this.locale.getDisplayLanguage(this.locale)
158+
}
159+
}
160+
132161
@PreviewLightDark
133162
@Composable
134163
private fun LanguagePickerDialogPreview() {
135164
TemplateTheme {
136165
LanguagePickerDialog(
137-
selectedLanguage = TemplateLanguagePreference.English,
138166
onDismiss = { },
139-
onConfirm = { }
167+
onConfirm = { },
168+
selectedLanguage = TemplateLanguagePreference.English,
169+
languageOptions = TemplateLanguagePreference.entries.toPersistentSet()
140170
)
141171
}
142172
}

app/src/main/kotlin/com/mitch/template/ui/screens/home/components/ThemePickerDialog.kt

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import androidx.compose.material3.Text
1717
import androidx.compose.material3.TextButton
1818
import androidx.compose.runtime.Composable
1919
import androidx.compose.runtime.getValue
20+
import androidx.compose.runtime.key
2021
import androidx.compose.runtime.mutableStateOf
21-
import androidx.compose.runtime.remember
22+
import androidx.compose.runtime.saveable.rememberSaveable
2223
import androidx.compose.runtime.setValue
2324
import androidx.compose.ui.Alignment
2425
import androidx.compose.ui.Modifier
@@ -34,14 +35,17 @@ import com.mitch.template.ui.designsystem.TemplateDesignSystem
3435
import com.mitch.template.ui.designsystem.TemplateIcons
3536
import com.mitch.template.ui.designsystem.TemplateTheme
3637
import com.mitch.template.ui.designsystem.theme.custom.padding
38+
import kotlinx.collections.immutable.PersistentSet
39+
import kotlinx.collections.immutable.toPersistentSet
3740

3841
@Composable
3942
fun ThemePickerDialog(
40-
selectedTheme: TemplateThemePreference,
4143
onDismiss: () -> Unit,
42-
onConfirm: (TemplateThemePreference) -> Unit
44+
onConfirm: (TemplateThemePreference) -> Unit,
45+
selectedTheme: TemplateThemePreference,
46+
themeOptions: PersistentSet<TemplateThemePreference>
4347
) {
44-
var tempTheme by remember { mutableStateOf(selectedTheme) }
48+
var tempTheme by rememberSaveable { mutableStateOf(selectedTheme) }
4549

4650
AlertDialog(
4751
onDismissRequest = onDismiss,
@@ -56,40 +60,19 @@ fun ThemePickerDialog(
5660
},
5761
text = {
5862
Column(modifier = Modifier.selectableGroup()) {
59-
for (themePreference in TemplateThemePreference.entries) {
63+
for (themePreference in themeOptions) {
6064
val isSelected = themePreference == tempTheme
61-
Row(
62-
modifier = Modifier
63-
.fillMaxWidth()
64-
.height(56.dp)
65-
.clip(RoundedCornerShape(16.dp))
66-
.selectable(
67-
selected = isSelected,
68-
onClick = { tempTheme = themePreference },
69-
role = Role.RadioButton
70-
)
71-
.padding(horizontal = TemplateDesignSystem.padding.medium),
72-
horizontalArrangement = Arrangement.spacedBy(TemplateDesignSystem.padding.medium),
73-
verticalAlignment = Alignment.CenterVertically
74-
) {
75-
RadioButton(
76-
selected = isSelected,
77-
onClick = null // null recommended for accessibility with screen readers
65+
66+
key(themePreference.ordinal) {
67+
ThemeOptionRadioButton(
68+
isSelected = isSelected,
69+
onClick = {
70+
if (!isSelected) {
71+
tempTheme = themePreference
72+
}
73+
},
74+
themePreference = themePreference
7875
)
79-
Row(
80-
horizontalArrangement = Arrangement.spacedBy(TemplateDesignSystem.padding.small),
81-
verticalAlignment = Alignment.CenterVertically
82-
) {
83-
Icon(
84-
imageVector = themePreference.icon(),
85-
contentDescription = null,
86-
modifier = Modifier.size(20.dp)
87-
)
88-
Text(
89-
text = themePreference.title(),
90-
style = TemplateDesignSystem.typography.bodyLarge
91-
)
92-
}
9376
}
9477
}
9578
}
@@ -113,6 +96,47 @@ fun ThemePickerDialog(
11396
)
11497
}
11598

99+
@Composable
100+
private fun ThemeOptionRadioButton(
101+
isSelected: Boolean,
102+
onClick: () -> Unit,
103+
themePreference: TemplateThemePreference
104+
) {
105+
Row(
106+
modifier = Modifier
107+
.fillMaxWidth()
108+
.height(56.dp)
109+
.clip(RoundedCornerShape(16.dp))
110+
.selectable(
111+
selected = isSelected,
112+
onClick = onClick,
113+
role = Role.RadioButton
114+
)
115+
.padding(horizontal = TemplateDesignSystem.padding.medium),
116+
horizontalArrangement = Arrangement.spacedBy(TemplateDesignSystem.padding.medium),
117+
verticalAlignment = Alignment.CenterVertically
118+
) {
119+
RadioButton(
120+
selected = isSelected,
121+
onClick = null // null recommended for accessibility with screen readers
122+
)
123+
Row(
124+
horizontalArrangement = Arrangement.spacedBy(TemplateDesignSystem.padding.small),
125+
verticalAlignment = Alignment.CenterVertically
126+
) {
127+
Icon(
128+
imageVector = themePreference.icon(),
129+
contentDescription = null,
130+
modifier = Modifier.size(20.dp)
131+
)
132+
Text(
133+
text = themePreference.title(),
134+
style = TemplateDesignSystem.typography.bodyLarge
135+
)
136+
}
137+
}
138+
}
139+
116140
@Composable
117141
private fun TemplateThemePreference.icon(): ImageVector {
118142
return when (this) {
@@ -137,9 +161,10 @@ private fun TemplateThemePreference.title(): String {
137161
private fun ThemePickerDialogPreview() {
138162
TemplateTheme {
139163
ThemePickerDialog(
140-
selectedTheme = TemplateThemePreference.Dark,
141164
onDismiss = { },
142-
onConfirm = { }
165+
onConfirm = { },
166+
selectedTheme = TemplateThemePreference.Dark,
167+
themeOptions = TemplateThemePreference.entries.toPersistentSet()
143168
)
144169
}
145170
}

0 commit comments

Comments
 (0)