Skip to content

Commit

Permalink
GeneralSettingsFragment: Rewrite to Jetpack Compose (#368, #581)
Browse files Browse the repository at this point in the history
* Add GeneralSettingsViewModel
* Remove existing preference layout
* Remove languages preference
  • Loading branch information
EdricChan03 committed Dec 14, 2024
1 parent 8f390f1 commit f915370
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package com.edricchan.studybuddy.features.settings.general.ui

import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.edricchan.studybuddy.core.settings.appearance.DarkThemeValue
import com.edricchan.studybuddy.features.settings.R
import com.edricchan.studybuddy.features.settings.general.vm.GeneralSettingsViewModel
import com.edricchan.studybuddy.ui.preference.compose.ListDialogPreference
import com.edricchan.studybuddy.ui.preference.compose.PreferenceCategory
import com.edricchan.studybuddy.ui.preference.compose.twostate.SwitchPreference
import com.edricchan.studybuddy.ui.theming.compose.StudyBuddyTheme
import com.edricchan.studybuddy.ui.theming.isDynamicColorAvailable


@get:StringRes
val DarkThemeValue.Version2.labelResource
get() = when (this) {
DarkThemeValue.V2Always -> R.string.pref_dark_theme_entry_always
DarkThemeValue.V2Never -> R.string.pref_dark_theme_entry_never
DarkThemeValue.V2FollowSystem -> R.string.pref_dark_theme_entry_system
}

@Composable
fun GeneralSettingsScreen(
modifier: Modifier = Modifier,
enableUserTracking: Boolean,
onEnableUserTrackingChange: (Boolean) -> Unit,
useCustomTabs: Boolean,
onUseCustomTabsChange: (Boolean) -> Unit,
useDarkTheme: DarkThemeValue.Version2,
onDarkThemeChange: (DarkThemeValue.Version2) -> Unit,
enableDynamicTheme: Boolean,
onDynamicThemeChange: (Boolean) -> Unit,
isDynamicThemeAvailable: Boolean = isDynamicColorAvailable
) = Column(modifier = modifier.verticalScroll(rememberScrollState())) {
SwitchPreference(
icon = {
Icon(
painterResource(R.drawable.ic_bug_report_outline_24dp),
contentDescription = null
)
},
title = {
Text(text = stringResource(R.string.pref_enable_crashlytics_user_tracking_title))
},
subtitle = { Text(text = stringResource(R.string.pref_enable_crashlytics_user_tracking_summary)) },
checked = enableUserTracking,
onCheckedChange = onEnableUserTrackingChange
)

SwitchPreference(
icon = {
Icon(
painterResource(R.drawable.ic_open_in_browser_24dp),
contentDescription = null
)
},
title = {
Text(text = stringResource(R.string.pref_use_custom_tabs_title))
},
checked = useCustomTabs,
onCheckedChange = onUseCustomTabsChange
)

PreferenceCategory(
title = { Text(text = stringResource(R.string.pref_category_theme)) }
) {
ListDialogPreference(
icon = {
Icon(
painterResource(R.drawable.ic_dark_mode_outline_24dp),
contentDescription = null
)
},
title = { Text(text = stringResource(R.string.pref_dark_theme_title)) },
subtitle = {
// FIXME: Remove !! operator
Text(text = stringResource(useDarkTheme.labelResource))
},
values = DarkThemeValue.Version2.entries,
value = useDarkTheme,
onValueChanged = onDarkThemeChange,
valueLabel = { value ->
Text(text = stringResource(value.labelResource))
}
)

if (isDynamicThemeAvailable) {
SwitchPreference(
icon = {
Icon(
painterResource(R.drawable.ic_auto_awesome_outline_24dp),
contentDescription = null
)
},
title = { Text(text = stringResource(R.string.pref_dynamic_theme_title)) },
subtitle = { Text(text = stringResource(R.string.pref_dynamic_theme_summary)) },
checked = enableDynamicTheme,
onCheckedChange = onDynamicThemeChange
)
}
}
}

@Composable
fun GeneralSettingsScreen(
modifier: Modifier = Modifier,
viewModel: GeneralSettingsViewModel,
onDynamicThemeChange: (Boolean) -> Unit = {},
onDarkThemeChange: (DarkThemeValue) -> Unit = {}
) {
val enableUserTracking by viewModel.prefEnableUserTracking.asFlow().collectAsStateWithLifecycle(
initialValue = false
)
val useCustomTabs by viewModel.prefUseCustomTabs.asFlow().collectAsStateWithLifecycle(
initialValue = true
)
val useDarkTheme by viewModel.prefDarkTheme.asFlow().collectAsStateWithLifecycle(
initialValue = DarkThemeValue.V2FollowSystem
)
val enableDynamicTheme by viewModel.prefEnableDynamicTheme.asFlow().collectAsStateWithLifecycle(
initialValue = isDynamicColorAvailable
)

GeneralSettingsScreen(
modifier = modifier,
enableUserTracking = enableUserTracking,
onEnableUserTrackingChange = viewModel.prefEnableUserTracking::set,
useCustomTabs = useCustomTabs,
onUseCustomTabsChange = viewModel.prefUseCustomTabs::set,
useDarkTheme = useDarkTheme,
onDarkThemeChange = {
viewModel.prefDarkTheme.set(it)
onDarkThemeChange(it)
},
enableDynamicTheme = enableDynamicTheme,
onDynamicThemeChange = {
viewModel.prefEnableDynamicTheme.set(it)
onDynamicThemeChange(it)
}
)
}

@Preview
@Composable
private fun GeneralSettingsScreenPreview() {
var enableUserTracking by remember { mutableStateOf(false) }
var useCustomTabs by remember { mutableStateOf(true) }
var useDarkTheme: DarkThemeValue.Version2 by remember { mutableStateOf(DarkThemeValue.V2FollowSystem) }
var enableDynamicTheme by remember { mutableStateOf(true) }

StudyBuddyTheme {
GeneralSettingsScreen(
enableUserTracking = enableUserTracking,
onEnableUserTrackingChange = { enableUserTracking = it },
useCustomTabs = useCustomTabs,
onUseCustomTabsChange = { useCustomTabs = it },
useDarkTheme = useDarkTheme,
onDarkThemeChange = { useDarkTheme = it },
enableDynamicTheme = enableDynamicTheme,
onDynamicThemeChange = { enableDynamicTheme = it }
)
}
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,60 @@
package com.edricchan.studybuddy.features.settings.general.ui.compat

import android.os.Bundle
import android.util.Log
import androidx.preference.SwitchPreferenceCompat
import com.edricchan.studybuddy.exts.common.TAG
import com.edricchan.studybuddy.features.settings.R
import com.edricchan.studybuddy.ui.preference.MaterialPreferenceFragment
import com.edricchan.studybuddy.ui.theming.PREF_DYNAMIC_THEME
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.fragment.app.viewModels
import androidx.fragment.compose.content
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.edricchan.studybuddy.features.settings.general.ui.GeneralSettingsScreen
import com.edricchan.studybuddy.features.settings.general.vm.GeneralSettingsViewModel
import com.edricchan.studybuddy.ui.common.fragment.BaseFragment
import com.edricchan.studybuddy.ui.theming.DarkThemeOption
import com.edricchan.studybuddy.ui.theming.applyDarkTheme
import com.edricchan.studybuddy.ui.theming.applyDynamicTheme
import com.edricchan.studybuddy.ui.theming.compose.StudyBuddyTheme
import com.edricchan.studybuddy.ui.theming.isDynamicColorAvailable
import dagger.hilt.android.AndroidEntryPoint

class GeneralSettingsFragment : MaterialPreferenceFragment() {
private val logTag = TAG
@AndroidEntryPoint
class GeneralSettingsFragment : BaseFragment() {
private val viewModel by viewModels<GeneralSettingsViewModel>()

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_general, rootKey)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = content {
val nestedScrollInterop = rememberNestedScrollInteropConnection()

findPreference<SwitchPreferenceCompat>(PREF_DYNAMIC_THEME)?.apply {
setOnPreferenceChangeListener { _, newValue ->
val isDynamicThemeEnabled = newValue as? Boolean ?: false
Log.d(
logTag,
"isDynamicThemeEnabled: $isDynamicThemeEnabled"
)
val dynamicTheme by viewModel.prefEnableDynamicTheme.asFlow()
.collectAsStateWithLifecycle(initialValue = isDynamicColorAvailable)

// Update dynamic theme
requireContext().applyDynamicTheme()

activity?.recreate()

true
}
isEnabled = isDynamicColorAvailable
StudyBuddyTheme(
useDynamicTheme = dynamicTheme
) {
GeneralSettingsScreen(
modifier = Modifier
.nestedScroll(nestedScrollInterop)
.windowInsetsPadding(WindowInsets.navigationBars),
viewModel = viewModel,
onDynamicThemeChange = {
requireActivity().apply {
applyDynamicTheme(it)
recreate()
}
},
onDarkThemeChange = {
requireActivity().applyDarkTheme(DarkThemeOption.fromValue(it))
}
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.edricchan.studybuddy.features.settings.general.vm

import android.content.Context
import androidx.lifecycle.ViewModel
import com.edricchan.studybuddy.core.settings.appearance.DarkThemeValue
import com.edricchan.studybuddy.core.settings.appearance.keyPrefDarkTheme
import com.edricchan.studybuddy.core.settings.appearance.keyPrefDynamicTheme
import com.edricchan.studybuddy.core.settings.appearance.keyPrefUseCustomTabs
import com.edricchan.studybuddy.core.settings.tracking.keyPrefEnableUserTracking
import com.edricchan.studybuddy.exts.androidx.preference.defaultSharedPreferences
import com.edricchan.studybuddy.ui.theming.isDynamicColorAvailable
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.fredporciuncula.flow.preferences.Preference
import com.fredporciuncula.flow.preferences.map
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject

@HiltViewModel
class GeneralSettingsViewModel @Inject constructor(
@ApplicationContext context: Context
) : ViewModel() {
private val appPreferences = FlowSharedPreferences(
context.defaultSharedPreferences
)

val prefEnableUserTracking: Preference<Boolean> = appPreferences.getBoolean(
keyPrefEnableUserTracking,
defaultValue = false
)

val prefUseCustomTabs: Preference<Boolean> = appPreferences.getBoolean(
keyPrefUseCustomTabs,
defaultValue = true
)

val prefDarkTheme: Preference<DarkThemeValue.Version2> = appPreferences.getString(
keyPrefDarkTheme,
defaultValue = DarkThemeValue.V2FollowSystem.value
).map(
mapper = DarkThemeValue.Version2::fromPrefValue,
reverse = DarkThemeValue::value
)

val prefEnableDynamicTheme: Preference<Boolean> = appPreferences.getBoolean(
keyPrefDynamicTheme,
defaultValue = isDynamicColorAvailable
)
}
40 changes: 0 additions & 40 deletions features/settings/src/main/res/xml/pref_general.xml

This file was deleted.

0 comments on commit f915370

Please sign in to comment.