Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[issue-406] Introduce Dynamic Color #777

Merged
merged 19 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 sharedSettingState: AppUiModel by kaigiAppViewModel.uiModel

KaigiTheme(isDynamicColorEnabled = sharedSettingState.isDynamicColorEnabled) {
val usePersistentNavigationDrawer = windowSizeClass.usePersistentNavigationDrawer
KaigiAppDrawer(
kaigiAppScaffoldState = kaigiAppScaffoldState,
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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems difficult to apply dynamicLightColorScheme because Color.White is set in Text Composables 🤔

} 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,8 @@
package io.github.droidkaigi.confsched2022.feature.setting

import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -11,20 +14,25 @@ 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.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.os.LocaleListCompat
import androidx.hilt.navigation.compose.hiltViewModel
import dev.icerock.moko.resources.StringResource
import dev.icerock.moko.resources.compose.stringResource
import io.github.droidkaigi.confsched2022.designsystem.components.KaigiScaffold
Expand All @@ -35,20 +43,28 @@ import io.github.droidkaigi.confsched2022.strings.Strings

@Composable
fun SettingScreenRoot(
viewModel: KaigiAppViewModel =
hiltViewModel(viewModelStoreOwner = LocalContext.current as AppCompatActivity),
showNavigationIcon: Boolean = true,
onNavigationIconClick: () -> Unit = {}
) {
val state: AppUiModel by viewModel.uiModel

Setting(
appUiModel = state,
showNavigationIcon = showNavigationIcon,
onNavigationIconClick = onNavigationIconClick
onNavigationIconClick = onNavigationIconClick,
onDynamicColorToggle = viewModel::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 +88,12 @@ fun Setting(
horizontalAlignment = Alignment.Start
) {
LanguageSetting()
if (VERSION.SDK_INT >= VERSION_CODES.S) {
DynamicColorSetting(
isDynamicColorEnabled = appUiModel.isDynamicColorEnabled,
onDynamicColorToggle = onDynamicColorToggle,
)
}
}
}
}
Expand All @@ -84,9 +106,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,6 +181,34 @@ private fun LanguageSelector(
}
}

// TODO
@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() {
Expand Down