Skip to content

Commit f915370

Browse files
committed
GeneralSettingsFragment: Rewrite to Jetpack Compose (#368, #581)
* Add GeneralSettingsViewModel * Remove existing preference layout * Remove languages preference
1 parent 8f390f1 commit f915370

File tree

4 files changed

+275
-65
lines changed

4 files changed

+275
-65
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package com.edricchan.studybuddy.features.settings.general.ui
2+
3+
import androidx.annotation.StringRes
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.rememberScrollState
6+
import androidx.compose.foundation.verticalScroll
7+
import androidx.compose.material3.Icon
8+
import androidx.compose.material3.Text
9+
import androidx.compose.runtime.Composable
10+
import androidx.compose.runtime.getValue
11+
import androidx.compose.runtime.mutableStateOf
12+
import androidx.compose.runtime.remember
13+
import androidx.compose.runtime.setValue
14+
import androidx.compose.ui.Modifier
15+
import androidx.compose.ui.res.painterResource
16+
import androidx.compose.ui.res.stringResource
17+
import androidx.compose.ui.tooling.preview.Preview
18+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
19+
import com.edricchan.studybuddy.core.settings.appearance.DarkThemeValue
20+
import com.edricchan.studybuddy.features.settings.R
21+
import com.edricchan.studybuddy.features.settings.general.vm.GeneralSettingsViewModel
22+
import com.edricchan.studybuddy.ui.preference.compose.ListDialogPreference
23+
import com.edricchan.studybuddy.ui.preference.compose.PreferenceCategory
24+
import com.edricchan.studybuddy.ui.preference.compose.twostate.SwitchPreference
25+
import com.edricchan.studybuddy.ui.theming.compose.StudyBuddyTheme
26+
import com.edricchan.studybuddy.ui.theming.isDynamicColorAvailable
27+
28+
29+
@get:StringRes
30+
val DarkThemeValue.Version2.labelResource
31+
get() = when (this) {
32+
DarkThemeValue.V2Always -> R.string.pref_dark_theme_entry_always
33+
DarkThemeValue.V2Never -> R.string.pref_dark_theme_entry_never
34+
DarkThemeValue.V2FollowSystem -> R.string.pref_dark_theme_entry_system
35+
}
36+
37+
@Composable
38+
fun GeneralSettingsScreen(
39+
modifier: Modifier = Modifier,
40+
enableUserTracking: Boolean,
41+
onEnableUserTrackingChange: (Boolean) -> Unit,
42+
useCustomTabs: Boolean,
43+
onUseCustomTabsChange: (Boolean) -> Unit,
44+
useDarkTheme: DarkThemeValue.Version2,
45+
onDarkThemeChange: (DarkThemeValue.Version2) -> Unit,
46+
enableDynamicTheme: Boolean,
47+
onDynamicThemeChange: (Boolean) -> Unit,
48+
isDynamicThemeAvailable: Boolean = isDynamicColorAvailable
49+
) = Column(modifier = modifier.verticalScroll(rememberScrollState())) {
50+
SwitchPreference(
51+
icon = {
52+
Icon(
53+
painterResource(R.drawable.ic_bug_report_outline_24dp),
54+
contentDescription = null
55+
)
56+
},
57+
title = {
58+
Text(text = stringResource(R.string.pref_enable_crashlytics_user_tracking_title))
59+
},
60+
subtitle = { Text(text = stringResource(R.string.pref_enable_crashlytics_user_tracking_summary)) },
61+
checked = enableUserTracking,
62+
onCheckedChange = onEnableUserTrackingChange
63+
)
64+
65+
SwitchPreference(
66+
icon = {
67+
Icon(
68+
painterResource(R.drawable.ic_open_in_browser_24dp),
69+
contentDescription = null
70+
)
71+
},
72+
title = {
73+
Text(text = stringResource(R.string.pref_use_custom_tabs_title))
74+
},
75+
checked = useCustomTabs,
76+
onCheckedChange = onUseCustomTabsChange
77+
)
78+
79+
PreferenceCategory(
80+
title = { Text(text = stringResource(R.string.pref_category_theme)) }
81+
) {
82+
ListDialogPreference(
83+
icon = {
84+
Icon(
85+
painterResource(R.drawable.ic_dark_mode_outline_24dp),
86+
contentDescription = null
87+
)
88+
},
89+
title = { Text(text = stringResource(R.string.pref_dark_theme_title)) },
90+
subtitle = {
91+
// FIXME: Remove !! operator
92+
Text(text = stringResource(useDarkTheme.labelResource))
93+
},
94+
values = DarkThemeValue.Version2.entries,
95+
value = useDarkTheme,
96+
onValueChanged = onDarkThemeChange,
97+
valueLabel = { value ->
98+
Text(text = stringResource(value.labelResource))
99+
}
100+
)
101+
102+
if (isDynamicThemeAvailable) {
103+
SwitchPreference(
104+
icon = {
105+
Icon(
106+
painterResource(R.drawable.ic_auto_awesome_outline_24dp),
107+
contentDescription = null
108+
)
109+
},
110+
title = { Text(text = stringResource(R.string.pref_dynamic_theme_title)) },
111+
subtitle = { Text(text = stringResource(R.string.pref_dynamic_theme_summary)) },
112+
checked = enableDynamicTheme,
113+
onCheckedChange = onDynamicThemeChange
114+
)
115+
}
116+
}
117+
}
118+
119+
@Composable
120+
fun GeneralSettingsScreen(
121+
modifier: Modifier = Modifier,
122+
viewModel: GeneralSettingsViewModel,
123+
onDynamicThemeChange: (Boolean) -> Unit = {},
124+
onDarkThemeChange: (DarkThemeValue) -> Unit = {}
125+
) {
126+
val enableUserTracking by viewModel.prefEnableUserTracking.asFlow().collectAsStateWithLifecycle(
127+
initialValue = false
128+
)
129+
val useCustomTabs by viewModel.prefUseCustomTabs.asFlow().collectAsStateWithLifecycle(
130+
initialValue = true
131+
)
132+
val useDarkTheme by viewModel.prefDarkTheme.asFlow().collectAsStateWithLifecycle(
133+
initialValue = DarkThemeValue.V2FollowSystem
134+
)
135+
val enableDynamicTheme by viewModel.prefEnableDynamicTheme.asFlow().collectAsStateWithLifecycle(
136+
initialValue = isDynamicColorAvailable
137+
)
138+
139+
GeneralSettingsScreen(
140+
modifier = modifier,
141+
enableUserTracking = enableUserTracking,
142+
onEnableUserTrackingChange = viewModel.prefEnableUserTracking::set,
143+
useCustomTabs = useCustomTabs,
144+
onUseCustomTabsChange = viewModel.prefUseCustomTabs::set,
145+
useDarkTheme = useDarkTheme,
146+
onDarkThemeChange = {
147+
viewModel.prefDarkTheme.set(it)
148+
onDarkThemeChange(it)
149+
},
150+
enableDynamicTheme = enableDynamicTheme,
151+
onDynamicThemeChange = {
152+
viewModel.prefEnableDynamicTheme.set(it)
153+
onDynamicThemeChange(it)
154+
}
155+
)
156+
}
157+
158+
@Preview
159+
@Composable
160+
private fun GeneralSettingsScreenPreview() {
161+
var enableUserTracking by remember { mutableStateOf(false) }
162+
var useCustomTabs by remember { mutableStateOf(true) }
163+
var useDarkTheme: DarkThemeValue.Version2 by remember { mutableStateOf(DarkThemeValue.V2FollowSystem) }
164+
var enableDynamicTheme by remember { mutableStateOf(true) }
165+
166+
StudyBuddyTheme {
167+
GeneralSettingsScreen(
168+
enableUserTracking = enableUserTracking,
169+
onEnableUserTrackingChange = { enableUserTracking = it },
170+
useCustomTabs = useCustomTabs,
171+
onUseCustomTabsChange = { useCustomTabs = it },
172+
useDarkTheme = useDarkTheme,
173+
onDarkThemeChange = { useDarkTheme = it },
174+
enableDynamicTheme = enableDynamicTheme,
175+
onDynamicThemeChange = { enableDynamicTheme = it }
176+
)
177+
}
178+
}
Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,60 @@
11
package com.edricchan.studybuddy.features.settings.general.ui.compat
22

33
import android.os.Bundle
4-
import android.util.Log
5-
import androidx.preference.SwitchPreferenceCompat
6-
import com.edricchan.studybuddy.exts.common.TAG
7-
import com.edricchan.studybuddy.features.settings.R
8-
import com.edricchan.studybuddy.ui.preference.MaterialPreferenceFragment
9-
import com.edricchan.studybuddy.ui.theming.PREF_DYNAMIC_THEME
4+
import android.view.LayoutInflater
5+
import android.view.ViewGroup
6+
import androidx.compose.foundation.layout.WindowInsets
7+
import androidx.compose.foundation.layout.navigationBars
8+
import androidx.compose.foundation.layout.windowInsetsPadding
9+
import androidx.compose.runtime.getValue
10+
import androidx.compose.ui.Modifier
11+
import androidx.compose.ui.input.nestedscroll.nestedScroll
12+
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
13+
import androidx.fragment.app.viewModels
14+
import androidx.fragment.compose.content
15+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
16+
import com.edricchan.studybuddy.features.settings.general.ui.GeneralSettingsScreen
17+
import com.edricchan.studybuddy.features.settings.general.vm.GeneralSettingsViewModel
18+
import com.edricchan.studybuddy.ui.common.fragment.BaseFragment
19+
import com.edricchan.studybuddy.ui.theming.DarkThemeOption
20+
import com.edricchan.studybuddy.ui.theming.applyDarkTheme
1021
import com.edricchan.studybuddy.ui.theming.applyDynamicTheme
22+
import com.edricchan.studybuddy.ui.theming.compose.StudyBuddyTheme
1123
import com.edricchan.studybuddy.ui.theming.isDynamicColorAvailable
24+
import dagger.hilt.android.AndroidEntryPoint
1225

13-
class GeneralSettingsFragment : MaterialPreferenceFragment() {
14-
private val logTag = TAG
26+
@AndroidEntryPoint
27+
class GeneralSettingsFragment : BaseFragment() {
28+
private val viewModel by viewModels<GeneralSettingsViewModel>()
1529

16-
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
17-
setPreferencesFromResource(R.xml.pref_general, rootKey)
30+
override fun onCreateView(
31+
inflater: LayoutInflater,
32+
container: ViewGroup?,
33+
savedInstanceState: Bundle?
34+
) = content {
35+
val nestedScrollInterop = rememberNestedScrollInteropConnection()
1836

19-
findPreference<SwitchPreferenceCompat>(PREF_DYNAMIC_THEME)?.apply {
20-
setOnPreferenceChangeListener { _, newValue ->
21-
val isDynamicThemeEnabled = newValue as? Boolean ?: false
22-
Log.d(
23-
logTag,
24-
"isDynamicThemeEnabled: $isDynamicThemeEnabled"
25-
)
37+
val dynamicTheme by viewModel.prefEnableDynamicTheme.asFlow()
38+
.collectAsStateWithLifecycle(initialValue = isDynamicColorAvailable)
2639

27-
// Update dynamic theme
28-
requireContext().applyDynamicTheme()
29-
30-
activity?.recreate()
31-
32-
true
33-
}
34-
isEnabled = isDynamicColorAvailable
40+
StudyBuddyTheme(
41+
useDynamicTheme = dynamicTheme
42+
) {
43+
GeneralSettingsScreen(
44+
modifier = Modifier
45+
.nestedScroll(nestedScrollInterop)
46+
.windowInsetsPadding(WindowInsets.navigationBars),
47+
viewModel = viewModel,
48+
onDynamicThemeChange = {
49+
requireActivity().apply {
50+
applyDynamicTheme(it)
51+
recreate()
52+
}
53+
},
54+
onDarkThemeChange = {
55+
requireActivity().applyDarkTheme(DarkThemeOption.fromValue(it))
56+
}
57+
)
3558
}
3659
}
3760
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.edricchan.studybuddy.features.settings.general.vm
2+
3+
import android.content.Context
4+
import androidx.lifecycle.ViewModel
5+
import com.edricchan.studybuddy.core.settings.appearance.DarkThemeValue
6+
import com.edricchan.studybuddy.core.settings.appearance.keyPrefDarkTheme
7+
import com.edricchan.studybuddy.core.settings.appearance.keyPrefDynamicTheme
8+
import com.edricchan.studybuddy.core.settings.appearance.keyPrefUseCustomTabs
9+
import com.edricchan.studybuddy.core.settings.tracking.keyPrefEnableUserTracking
10+
import com.edricchan.studybuddy.exts.androidx.preference.defaultSharedPreferences
11+
import com.edricchan.studybuddy.ui.theming.isDynamicColorAvailable
12+
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
13+
import com.fredporciuncula.flow.preferences.Preference
14+
import com.fredporciuncula.flow.preferences.map
15+
import dagger.hilt.android.lifecycle.HiltViewModel
16+
import dagger.hilt.android.qualifiers.ApplicationContext
17+
import javax.inject.Inject
18+
19+
@HiltViewModel
20+
class GeneralSettingsViewModel @Inject constructor(
21+
@ApplicationContext context: Context
22+
) : ViewModel() {
23+
private val appPreferences = FlowSharedPreferences(
24+
context.defaultSharedPreferences
25+
)
26+
27+
val prefEnableUserTracking: Preference<Boolean> = appPreferences.getBoolean(
28+
keyPrefEnableUserTracking,
29+
defaultValue = false
30+
)
31+
32+
val prefUseCustomTabs: Preference<Boolean> = appPreferences.getBoolean(
33+
keyPrefUseCustomTabs,
34+
defaultValue = true
35+
)
36+
37+
val prefDarkTheme: Preference<DarkThemeValue.Version2> = appPreferences.getString(
38+
keyPrefDarkTheme,
39+
defaultValue = DarkThemeValue.V2FollowSystem.value
40+
).map(
41+
mapper = DarkThemeValue.Version2::fromPrefValue,
42+
reverse = DarkThemeValue::value
43+
)
44+
45+
val prefEnableDynamicTheme: Preference<Boolean> = appPreferences.getBoolean(
46+
keyPrefDynamicTheme,
47+
defaultValue = isDynamicColorAvailable
48+
)
49+
}

features/settings/src/main/res/xml/pref_general.xml

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)