Skip to content

Commit

Permalink
Merge pull request #777 from mona-apk/issue-406_dynamic_color
Browse files Browse the repository at this point in the history
[issue-406] Introduce Dynamic Color
  • Loading branch information
takahirom authored Sep 29, 2022
2 parents 2bed7dd + 88275c5 commit 15d31df
Show file tree
Hide file tree
Showing 14 changed files with 210 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
Expand All @@ -54,6 +55,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
Expand All @@ -71,6 +73,8 @@ import io.github.droidkaigi.confsched2022.feature.map.MapNavGraph
import io.github.droidkaigi.confsched2022.feature.map.mapGraph
import io.github.droidkaigi.confsched2022.feature.sessions.SessionsNavGraph
import io.github.droidkaigi.confsched2022.feature.sessions.sessionsNavGraph
import io.github.droidkaigi.confsched2022.feature.setting.AppUiModel
import io.github.droidkaigi.confsched2022.feature.setting.KaigiAppViewModel
import io.github.droidkaigi.confsched2022.feature.setting.SettingNavGraph
import io.github.droidkaigi.confsched2022.feature.setting.settingNavGraph
import io.github.droidkaigi.confsched2022.feature.sponsors.SponsorsNavGraph
Expand All @@ -91,11 +95,14 @@ import kotlinx.coroutines.launch
@Composable
fun KaigiApp(
windowSizeClass: WindowSizeClass,
kaigiAppViewModel: KaigiAppViewModel = hiltViewModel(),
kaigiAppScaffoldState: KaigiAppScaffoldState = rememberKaigiAppScaffoldState(),
kaigiExternalNavigationController: KaigiExternalNavigationController =
rememberKaigiExternalNavigationController(),
) {
KaigiTheme {
val appUiModel: AppUiModel by kaigiAppViewModel.uiModel

KaigiTheme(isDynamicColorEnabled = appUiModel.isDynamicColorEnabled) {
val usePersistentNavigationDrawer = windowSizeClass.usePersistentNavigationDrawer
KaigiAppDrawer(
kaigiAppScaffoldState = kaigiAppScaffoldState,
Expand Down Expand Up @@ -152,8 +159,10 @@ fun KaigiApp(
onNavigationIconClick = kaigiAppScaffoldState::onNavigationClick,
)
settingNavGraph(
showNavigationIcon,
kaigiAppScaffoldState::onNavigationClick
appUiModel = appUiModel,
showNavigationIcon = true,
onDynamicColorToggle = kaigiAppViewModel::onDynamicColorToggle,
onNavigationIconClick = kaigiAppScaffoldState::onNavigationClick
)
sponsorsNavGraph(
showNavigationIcon = showNavigationIcon,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.droidkaigi.confsched2022.data.setting.di

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import io.github.droidkaigi.confsched2022.data.SettingsDatastore
import io.github.droidkaigi.confsched2022.data.setting.DataDynamicColorSettingRepository
import io.github.droidkaigi.confsched2022.model.DynamicColorSettingRepository
import javax.inject.Singleton

@InstallIn(SingletonComponent::class)
@Module
public class SettingDataModule {
@Provides
@Singleton
public fun provideSessionsRepository(
settingsDatastore: SettingsDatastore
): DynamicColorSettingRepository {
return DataDynamicColorSettingRepository(
settingsDatastore = settingsDatastore
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.droidkaigi.confsched2022.data

import com.russhwolf.settings.coroutines.FlowSettings
import io.github.droidkaigi.confsched2022.model.DroidKaigi2022Day
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -61,10 +62,26 @@ public class SettingsDatastore(private val flowSettings: FlowSettings) {
)
}

public fun dynamicColorEnabled(): Flow<Boolean> {
return flowSettings.getBooleanFlow(
key = KEY_DYNAMIC_COLOR,
// The trick
defaultValue = DroidKaigi2022Day.defaultDyamicThemeDate()
)
}

public suspend fun setDynamicColorEnabled(dynamicColorEnabled: Boolean) {
flowSettings.putBoolean(
KEY_DYNAMIC_COLOR,
dynamicColorEnabled,
)
}

public companion object {
public const val NAME: String = "PREFERENCES_NAME"
private const val KEY_AUTHENTICATED = "KEY_AUTHENTICATED"
private const val KEY_DEVICE_ID = "KEY_DEVICE_ID"
private const val KEY_DYNAMIC_COLOR = "KEY_DYNAMIC_COLOR"
private const val KEY = "favorites"
private const val DELIMITER = ","
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.github.droidkaigi.confsched2022.data.setting

import io.github.droidkaigi.confsched2022.data.SettingsDatastore
import io.github.droidkaigi.confsched2022.model.DynamicColorSettingRepository
import kotlinx.coroutines.flow.Flow

public class DataDynamicColorSettingRepository(
private val settingsDatastore: SettingsDatastore
) : DynamicColorSettingRepository {
override fun dynamicEnabledFlow(): Flow<Boolean> {
return settingsDatastore.dynamicColorEnabled()
}

override suspend fun setDynamicColorEnabled(dynamicColorEnabled: Boolean) {
settingsDatastore.setDynamicColorEnabled(dynamicColorEnabled = dynamicColorEnabled)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.github.droidkaigi.confsched2022.designsystem.theme

import android.annotation.SuppressLint
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext

private val DarkColorPalette = darkColorScheme(
primary = Color(KaigiColors.primaryKeyColor80),
Expand Down Expand Up @@ -31,16 +34,21 @@ private val DarkColorPalette = darkColorScheme(
outline = Color(KaigiColors.neutralVariantKeyColor60),
)

@SuppressLint("NewApi")
@Composable
fun KaigiTheme(
public fun KaigiTheme(
// Currently, we are not supporting light theme
// darkTheme: Boolean = isSystemInDarkTheme(),
isDynamicColorEnabled: Boolean = false,
content: @Composable () -> Unit
) {
val colorScheme = if (isDynamicColorEnabled) {
dynamicDarkColorScheme(LocalContext.current)
} else {
DarkColorPalette
}

MaterialTheme(
colorScheme = DarkColorPalette,
typography = Typography,
shapes = Shapes,
content = content
colorScheme = colorScheme, typography = Typography, shapes = Shapes, content = content
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.droidkaigi.confsched2022.model

import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
Expand Down Expand Up @@ -47,6 +48,10 @@ public enum class DroidKaigi2022Day(
time in it.start..it.end
}
}

public fun defaultDyamicThemeDate(): Boolean {
return Day1.start < Clock.System.now()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.github.droidkaigi.confsched2022.model

import kotlinx.coroutines.flow.Flow

public interface DynamicColorSettingRepository {
public fun dynamicEnabledFlow(): Flow<Boolean>
public suspend fun setDynamicColorEnabled(dynamicColorEnabled: Boolean)
}
1 change: 1 addition & 0 deletions core/model/src/commonMain/resources/MR/base/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@

<!-- Setting -->
<string name="setting_title">設定</string>
<string name="setting_item_dynamic_color">ダイナミックカラー</string>
<string name="setting_item_dark_mode">ダークモード</string>
<string name="setting_item_language">言語設定</string>
<string name="setting_language_system">システムのデフォルト</string>
Expand Down
1 change: 1 addition & 0 deletions core/model/src/commonMain/resources/MR/en/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@

<!-- Setting -->
<string name="setting_title">Setting</string>
<string name="setting_item_dynamic_color">Dynamic Color</string>
<string name="setting_item_dark_mode">Dark Mode</string>
<string name="setting_item_language">Language</string>
<string name="setting_language_system">System Default</string>
Expand Down
2 changes: 2 additions & 0 deletions core/model/src/commonMain/resources/MR/zh/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@

<!-- Setting -->
<string name="setting_title">设置</string>
<!-- TODO:Translation into Chinese -->
<string name="setting_item_dynamic_color">Dynamic Color</string>
<string name="setting_item_dark_mode">深色模式</string>
<string name="setting_item_language">语言设置</string>
<string name="setting_language_system">系统默认设置</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.droidkaigi.confsched2022.feature.setting

data class AppUiModel(
val isDynamicColorEnabled: Boolean,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.github.droidkaigi.confsched2022.feature.setting

import android.os.Build
import android.os.Build.VERSION_CODES
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.cash.molecule.AndroidUiDispatcher
import app.cash.molecule.RecompositionClock.ContextClock
import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.droidkaigi.confsched2022.model.DroidKaigi2022Day
import io.github.droidkaigi.confsched2022.model.DynamicColorSettingRepository
import io.github.droidkaigi.confsched2022.ui.moleculeComposeState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class KaigiAppViewModel @Inject constructor(
private val dynamicColorSettingRepository: DynamicColorSettingRepository,
) : ViewModel() {
private val moleculeScope =
CoroutineScope(viewModelScope.coroutineContext + AndroidUiDispatcher.Main)

private val dynamicColorEnabledFlow: Flow<Boolean> =
dynamicColorSettingRepository.dynamicEnabledFlow()

val uiModel: State<AppUiModel> = moleculeScope.moleculeComposeState(clock = ContextClock) {
val dynamicColorSettingEnabled by dynamicColorEnabledFlow.collectAsState(
initial = DroidKaigi2022Day.defaultDyamicThemeDate()
)
AppUiModel(isDynamicColorEnabled = dynamicColorSettingEnabled && isSupportedDynamicColor())
}

fun onDynamicColorToggle(isDynamic: Boolean) {
viewModelScope.launch {
dynamicColorSettingRepository.setDynamicColorEnabled(isDynamic)
}
}

@ChecksSdkIntAtLeast(api = VERSION_CODES.S)
private fun isSupportedDynamicColor(): Boolean {
return Build.VERSION.SDK_INT >= VERSION_CODES.S
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.droidkaigi.confsched2022.feature.setting

import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -11,11 +13,13 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectable
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Colorize
import androidx.compose.material.icons.filled.Language
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
Expand All @@ -35,20 +39,26 @@ import io.github.droidkaigi.confsched2022.strings.Strings

@Composable
fun SettingScreenRoot(
appUiModel: AppUiModel,
onDynamicColorToggle: (Boolean) -> Unit,
showNavigationIcon: Boolean = true,
onNavigationIconClick: () -> Unit = {}
) {
Setting(
appUiModel = appUiModel,
showNavigationIcon = showNavigationIcon,
onNavigationIconClick = onNavigationIconClick
onNavigationIconClick = onNavigationIconClick,
onDynamicColorToggle = onDynamicColorToggle,
)
}

@Composable
fun Setting(
appUiModel: AppUiModel,
showNavigationIcon: Boolean,
onNavigationIconClick: () -> Unit,
onDynamicColorToggle: (Boolean) -> Unit,
modifier: Modifier = Modifier,
onNavigationIconClick: () -> Unit
) {
KaigiScaffold(
modifier = modifier,
Expand All @@ -72,6 +82,12 @@ fun Setting(
horizontalAlignment = Alignment.Start
) {
LanguageSetting()
if (VERSION.SDK_INT >= VERSION_CODES.S) {
DynamicColorSetting(
isDynamicColorEnabled = appUiModel.isDynamicColorEnabled,
onDynamicColorToggle = onDynamicColorToggle,
)
}
}
}
}
Expand All @@ -84,9 +100,9 @@ private fun LanguageSetting(

Row(
modifier = modifier
.padding(vertical = 8.dp)
.fillMaxWidth()
.clickable { openDialog.value = true },
.clickable { openDialog.value = true }
.padding(16.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Expand Down Expand Up @@ -159,11 +175,41 @@ private fun LanguageSelector(
}
}

@Composable
private fun DynamicColorSetting(
isDynamicColorEnabled: Boolean,
onDynamicColorToggle: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.padding(16.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(28.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(imageVector = Icons.Default.Colorize, contentDescription = null)
Text(
text = stringResource(resource = Strings.setting_item_dynamic_color),
modifier = Modifier.weight(1f),
)
Switch(
checked = isDynamicColorEnabled,
onCheckedChange = {
onDynamicColorToggle(it)
},
)
}
}

@Preview
@Composable
private fun SettingPreview() {
KaigiTheme {
SettingScreenRoot()
SettingScreenRoot(
appUiModel = AppUiModel(false),
onDynamicColorToggle = {}
)
}
}

Expand Down
Loading

0 comments on commit 15d31df

Please sign in to comment.