diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e7af39eb..9ea450fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ agp = "8.11.1" amplify = "2.29.2" appcompat = "1.6.1" androidx-core = "1.9.0" +androidx-datastore = "1.1.7" androidx-junit = "1.1.4" androidx-activity = "1.6.1" androidx-navigation = "2.5.3" @@ -83,6 +84,9 @@ test-roborazzi = { module = "io.github.takahirom.roborazzi:roborazzi", version.r test-roborazzi-compose = { module = "io.github.takahirom.roborazzi:roborazzi-compose", version.ref = "roborazzi" } test-turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } +# Dependencies for sample apps +samples-androidx-datastore-prefs = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" } + # Dependencies for convention plugins plugin-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "agp" } plugin-binary-compatibility = { module = "org.jetbrains.kotlinx:binary-compatibility-validator", version.ref = "binary-compatibility" } diff --git a/samples/authenticator/app/.gitignore b/samples/authenticator/app/.gitignore index 7915f57c..f103701d 100644 --- a/samples/authenticator/app/.gitignore +++ b/samples/authenticator/app/.gitignore @@ -1,4 +1,4 @@ /build /buildNative **/awsconfiguration.json -**/amplifyconfiguration.json \ No newline at end of file +**/amplifyconfiguration**.json \ No newline at end of file diff --git a/samples/authenticator/app/build.gradle.kts b/samples/authenticator/app/build.gradle.kts index bdc4523f..95bd323c 100644 --- a/samples/authenticator/app/build.gradle.kts +++ b/samples/authenticator/app/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { implementation(libs.bundles.compose) implementation(libs.androidx.lifecycle) implementation(libs.androidx.activity.compose) + implementation(libs.samples.androidx.datastore.prefs) coreLibraryDesugaring(libs.android.desugar) } diff --git a/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/MainActivity.kt b/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/MainActivity.kt index a03c0f22..f3f731c3 100644 --- a/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/MainActivity.kt +++ b/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/MainActivity.kt @@ -18,7 +18,6 @@ package com.amplifyframework.ui.sample.authenticator import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -40,28 +39,28 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.amplifyframework.ui.authenticator.AuthenticatorState import com.amplifyframework.ui.authenticator.SignedInState import com.amplifyframework.ui.authenticator.rememberAuthenticatorState import com.amplifyframework.ui.authenticator.ui.Authenticator +import com.amplifyframework.ui.sample.authenticator.data.ThemeDatastore import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { - @OptIn(ExperimentalMaterial3Api::class) + private val themeDatastore by lazy { ThemeDatastore(this) } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - val inDarkMode = isSystemInDarkTheme() - var currentTheme by remember { mutableStateOf(SupportedTheme.Default) } - var darkMode by remember { mutableStateOf(inDarkMode) } + val currentTheme by themeDatastore.theme.collectAsState(SupportedTheme.Default) + val darkMode by themeDatastore.darkMode.collectAsState(false) val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) + val scope = rememberCoroutineScope() val authenticatorState = rememberAuthenticatorState() @@ -75,8 +74,8 @@ class MainActivity : ComponentActivity() { modifier = Modifier.padding(16.dp), currentTheme = currentTheme, darkMode = darkMode, - onChangeCurrentTheme = { currentTheme = it }, - onChangeDarkMode = { darkMode = it } + onChangeCurrentTheme = { scope.launch { themeDatastore.saveTheme(it) } }, + onChangeDarkMode = { scope.launch { themeDatastore.saveDarkMode(it) } } ) } } @@ -91,10 +90,7 @@ class MainActivity : ComponentActivity() { @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SampleAppContent( - drawerState: DrawerState, - authenticatorState: AuthenticatorState -) { +fun SampleAppContent(drawerState: DrawerState, authenticatorState: AuthenticatorState) { val scope = rememberCoroutineScope() Scaffold( topBar = { diff --git a/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/ThemeSelector.kt b/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/ThemeSelector.kt index d0c6e1f9..822778b1 100644 --- a/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/ThemeSelector.kt +++ b/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/ThemeSelector.kt @@ -18,6 +18,7 @@ package com.amplifyframework.ui.sample.authenticator import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.Switch import androidx.compose.material3.Text @@ -30,7 +31,8 @@ import com.amplifyframework.ui.sample.authenticator.theme.default.AppTheme enum class SupportedTheme { Default, - Amplify + Amplify, + Dynamic } @Composable @@ -42,7 +44,8 @@ fun ThemeSelector( onChangeDarkMode: (Boolean) -> Unit ) { Column(modifier = modifier) { - SupportedTheme.values().forEach { theme -> + Text("Theme", style = MaterialTheme.typography.titleMedium) + SupportedTheme.entries.forEach { theme -> Row(verticalAlignment = Alignment.CenterVertically) { RadioButton(selected = theme == currentTheme, onClick = { onChangeCurrentTheme(theme) }) Text(modifier = Modifier.padding(start = 8.dp), text = theme.name) @@ -58,7 +61,8 @@ fun ThemeSelector( @Composable fun ApplyTheme(theme: SupportedTheme, darkMode: Boolean, content: @Composable () -> Unit) { when (theme) { - SupportedTheme.Default -> AppTheme(darkTheme = darkMode, content = content) + SupportedTheme.Default -> AppTheme(darkTheme = darkMode, dynamicColor = false, content = content) SupportedTheme.Amplify -> AmplifyTheme(useDarkTheme = darkMode, content = content) + SupportedTheme.Dynamic -> AppTheme(darkTheme = darkMode, dynamicColor = true, content = content) } } diff --git a/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/data/ThemeDatastore.kt b/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/data/ThemeDatastore.kt new file mode 100644 index 00000000..123dc171 --- /dev/null +++ b/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/data/ThemeDatastore.kt @@ -0,0 +1,43 @@ +package com.amplifyframework.ui.sample.authenticator.data + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import com.amplifyframework.ui.sample.authenticator.SupportedTheme +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class ThemeDatastore(context: Context) { + + private val Context.dataStore: DataStore by preferencesDataStore(name = "theme") + + private val datastore = context.dataStore + + private val darkModeKey = booleanPreferencesKey("darkMode") + private val themeKey = stringPreferencesKey("theme") + + suspend fun saveTheme(theme: SupportedTheme) { + datastore.edit { settings -> + settings[themeKey] = theme.name + } + } + + suspend fun saveDarkMode(darkMode: Boolean) { + datastore.edit { settings -> + settings[darkModeKey] = darkMode + } + } + + val darkMode: Flow = datastore.data.map { settings -> + settings[darkModeKey] ?: false + } + val theme: Flow = datastore.data.map { settings -> + val name = settings[themeKey] ?: SupportedTheme.Default.name + SupportedTheme.valueOf(name) + } + +} \ No newline at end of file diff --git a/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/theme/amplifytheme/Theme.kt b/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/theme/amplifytheme/Theme.kt index f85bbe5c..301fa0c3 100644 --- a/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/theme/amplifytheme/Theme.kt +++ b/samples/authenticator/app/src/main/java/com/amplifyframework/ui/sample/authenticator/theme/amplifytheme/Theme.kt @@ -15,11 +15,16 @@ package com.amplifyframework.ui.sample.authenticator.theme.amplifytheme +import android.app.Activity import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalView +import androidx.core.view.ViewCompat private val LightColors = lightColorScheme( primary = md_theme_light_primary, @@ -82,16 +87,21 @@ private val DarkColors = darkColorScheme( ) @Composable -fun AmplifyTheme( - useDarkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit -) { +fun AmplifyTheme(useDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { val colors = if (!useDarkTheme) { LightColors } else { DarkColors } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + (view.context as Activity).window.statusBarColor = colors.primary.toArgb() + ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = useDarkTheme + } + } + MaterialTheme( colorScheme = colors, content = content