Skip to content

Commit

Permalink
Add dark theme setting for settings screen
Browse files Browse the repository at this point in the history
  • Loading branch information
HiroyukiYagihashi committed Mar 10, 2021
1 parent ec1ca5e commit d456b6c
Show file tree
Hide file tree
Showing 21 changed files with 499 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.github.droidkaigi.feeder.data

import com.russhwolf.settings.ExperimentalSettingsApi
import com.russhwolf.settings.MockSettings
import com.russhwolf.settings.coroutines.FlowSettings
import com.russhwolf.settings.coroutines.toFlowSettings
import io.github.droidkaigi.feeder.Theme
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
Expand Down Expand Up @@ -57,10 +59,22 @@ abstract class UserDataStore {
val idToken: StateFlow<String?> = mutableIdToken
suspend fun setIdToken(token: String) = mutableIdToken.emit(token)

fun theme(): Flow<Theme?> {
return flowSettings.getStringOrNullFlow(KEY_THEME).map { it?.let { Theme.valueOf(it) } }
}

suspend fun changeTheme(theme: Theme) {
flowSettings.putString(
KEY_THEME,
theme.name
)
}

companion object {
private const val KEY_FAVORITES = "KEY_FAVORITES"
private const val KEY_AUTHENTICATED = "KEY_AUTHENTICATED"
private const val KEY_DEVICE_ID = "KEY_DEVICE_ID"
private const val KEY_THEME = "KEY_THEME"
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.github.droidkaigi.feeder.data

import io.github.droidkaigi.feeder.Theme
import io.github.droidkaigi.feeder.repository.ThemeRepository
import kotlinx.coroutines.flow.Flow

open class ThemeRepositoryImpl(
private val dataStore: UserDataStore,
) : ThemeRepository {
override suspend fun changeTheme(theme: Theme) {
dataStore.changeTheme(theme)
}

override fun theme(): Flow<Theme?> {
return dataStore.theme()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.github.droidkaigi.feeder.data

import io.github.droidkaigi.feeder.repository.ThemeRepository
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
internal class DaggerThemeRepositoryImpl @Inject constructor(
dataDataStore: UserDataStore,
) : ThemeRepositoryImpl(
dataDataStore,
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import io.github.droidkaigi.feeder.repository.DeviceRepository
import io.github.droidkaigi.feeder.repository.FeedRepository
import io.github.droidkaigi.feeder.repository.ThemeRepository

@InstallIn(SingletonComponent::class)
@Module
Expand Down Expand Up @@ -36,4 +37,11 @@ class DataModule {
): DeviceRepository {
return daggerRepository
}

@Provides
internal fun provideThemeRepository(
daggerRepository: DaggerThemeRepositoryImpl
): ThemeRepository {
return daggerRepository
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.github.droidkaigi.feeder

enum class Theme {
SYSTEM,
BATTERY,
DARK,
LIGHT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.github.droidkaigi.feeder.repository

import io.github.droidkaigi.feeder.FeedContents
import io.github.droidkaigi.feeder.FeedItem
import io.github.droidkaigi.feeder.Theme
import kotlinx.coroutines.flow.Flow

interface ThemeRepository {
suspend fun changeTheme(theme: Theme)

fun theme(): Flow<Theme?>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.droidkaigi.feeder.core

import android.content.Context
import io.github.droidkaigi.feeder.AppError
import io.github.droidkaigi.feeder.Theme

fun AppError.getReadableMessage(context: Context): String = when (this) {
is AppError.ApiException.ServerException -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.github.droidkaigi.feeder.core.theme

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
import dev.chrisbanes.accompanist.insets.ProvideWindowInsets
import io.github.droidkaigi.feeder.Theme

private val DarkColorPalette = darkColors(
primary = blue200,
Expand All @@ -21,21 +23,36 @@ private val LightColorPalette = lightColors(

@Composable
fun ConferenceAppFeederTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
theme: Theme? = Theme.SYSTEM,
content: @Composable
() -> Unit,
) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
ProvideWindowInsets {
MaterialTheme(
colors = colors,
colors = colorPalette(theme),
typography = typography,
shapes = shapes,
content = content
)
}
}

@Composable
private fun colorPalette(theme: Theme?): Colors {
return when (theme) {
Theme.SYSTEM -> systemColorPalette()
Theme.BATTERY -> DarkColorPalette
Theme.DARK -> DarkColorPalette
Theme.LIGHT -> LightColorPalette
else -> throw IllegalArgumentException("should not happen")
}
}

@Composable
private fun systemColorPalette(): Colors {
return if (isSystemInDarkTheme()) {
DarkColorPalette
} else {
LightColorPalette
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.github.droidkaigi.feeder.core.theme

import android.content.Context
import io.github.droidkaigi.feeder.Theme
import io.github.droidkaigi.feeder.core.R

fun Theme.getTitle(context: Context): String = when (this) {
Theme.SYSTEM -> context.getString(R.string.system)
Theme.BATTERY -> context.getString(R.string.battery)
Theme.DARK -> context.getString(R.string.dark)
Theme.LIGHT -> context.getString(R.string.light)
else -> context.getString(R.string.error_unknown)
}
4 changes: 4 additions & 0 deletions uicomponent-compose/core/src/main/res/values-ja/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@
<string name="error_network">ネットワークエラーが発生しました。もう一度お試しください。</string>
<string name="error_server">サーバーエラーが発生しました。もう一度お試しください。</string>
<string name="error_unknown">予期しないエラーが発生しました。もう一度お試しください。</string>
<string name="system">端末設定に従う</string>
<string name="battery">省電力モードの時のみ</string>
<string name="dark">ダーク</string>
<string name="light">ライト</string>
</resources>
4 changes: 4 additions & 0 deletions uicomponent-compose/core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@
<string name="error_network">A network error has occurred.Please try again.</string>
<string name="error_server">A server error has occurred.Please try again.</string>
<string name="error_unknown">An unknown error has occurred.Please try again.</string>
<string name="system">system</string>
<string name="battery">battery</string>
<string name="dark">dark</string>
<string name="light">light</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import dev.chrisbanes.accompanist.insets.toPaddingValues
import io.github.droidkaigi.feeder.FeedContents
import io.github.droidkaigi.feeder.FeedItem
import io.github.droidkaigi.feeder.Filters
import io.github.droidkaigi.feeder.Theme
import io.github.droidkaigi.feeder.core.getReadableMessage
import io.github.droidkaigi.feeder.core.theme.ConferenceAppFeederTheme
import io.github.droidkaigi.feeder.core.use
Expand Down Expand Up @@ -288,7 +289,7 @@ fun FeedItemRow(
@Preview(showBackground = true)
@Composable
fun PreviewFeedScreen() {
ConferenceAppFeederTheme(false) {
ConferenceAppFeederTheme(Theme.LIGHT) {
ProvideFeedViewModel(viewModel = fakeFeedViewModel()) {
FeedScreen(
selectedTab = FeedTabs.Home,
Expand All @@ -304,7 +305,7 @@ fun PreviewFeedScreen() {
@Preview(showBackground = true)
@Composable
fun PreviewFeedScreenWithStartBlog() {
ConferenceAppFeederTheme(false) {
ConferenceAppFeederTheme(Theme.LIGHT) {
ProvideFeedViewModel(viewModel = fakeFeedViewModel()) {
FeedScreen(
selectedTab = FeedTabs.FilteredFeed.Blog,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,46 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.lifecycle.viewmodel.compose.viewModel
import dev.chrisbanes.accompanist.insets.navigationBarsPadding
import io.github.droidkaigi.feeder.core.theme.ConferenceAppFeederTheme
import io.github.droidkaigi.feeder.core.use
import io.github.droidkaigi.feeder.viewmodel.RealDroidKaigiAppViewModel

@Composable
fun DroidKaigiApp(firstSplashScreenState: SplashState = SplashState.Shown) {
ConferenceAppFeederTheme {
var splashShown by rememberSaveable {
mutableStateOf(firstSplashScreenState)
}
val transition = updateTransition(splashShown)
val splashAlpha: Float by transition.animateFloat(
transitionSpec = { tween(durationMillis = 100) }
) { state ->
if (state == SplashState.Shown) 1f else 0f
}
val contentAlpha: Float by transition.animateFloat(
transitionSpec = { tween(durationMillis = 300) }
) { state ->
if (state == SplashState.Shown) 0f else 1f
}
ProvideDroidKaigiAppViewModel(viewModel = droidKaigiAppViewModel()) {
val (state) = use(droidKaigiAppViewModel())

ConferenceAppFeederTheme(state.theme) {
var splashShown by rememberSaveable {
mutableStateOf(firstSplashScreenState)
}
val transition = updateTransition(splashShown)
val splashAlpha: Float by transition.animateFloat(
transitionSpec = { tween(durationMillis = 100) }
) { state ->
if (state == SplashState.Shown) 1f else 0f
}
val contentAlpha: Float by transition.animateFloat(
transitionSpec = { tween(durationMillis = 300) }
) { state ->
if (state == SplashState.Shown) 0f else 1f
}

Box {
LandingScreen(
modifier = Modifier.alpha(splashAlpha),
) {
splashShown = SplashState.Completed
Box {
LandingScreen(
modifier = Modifier.alpha(splashAlpha),
) {
splashShown = SplashState.Completed
}
}
AppContent(
modifier = Modifier
.alpha(contentAlpha)
.navigationBarsPadding(bottom = false)
)
}
AppContent(
modifier = Modifier
.alpha(contentAlpha)
.navigationBarsPadding(bottom = false)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.github.droidkaigi.feeder

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import io.github.droidkaigi.feeder.core.UnidirectionalViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow

interface DroidKaigiAppViewModel :
UnidirectionalViewModel<
DroidKaigiAppViewModel.Event,
DroidKaigiAppViewModel.Effect,
DroidKaigiAppViewModel.State>
{
data class State(
val theme: Theme? = Theme.SYSTEM,
)

sealed class Effect {
data class ErrorMessage(val appError: AppError) : Effect()
}

sealed class Event

override val state: StateFlow<State>
override val effect: Flow<Effect>
override fun event(event: Event)
}

private val LocalDroidKaigiAppViewModel = compositionLocalOf<DroidKaigiAppViewModel> {
error("not LocalDroidKaigiAppViewModel provided")
}

@Composable
fun ProvideDroidKaigiAppViewModel(viewModel: DroidKaigiAppViewModel, block: @Composable () ->
Unit) {
CompositionLocalProvider(LocalDroidKaigiAppViewModel provides viewModel, content = block)
}

@Composable
fun droidKaigiAppViewModel() = LocalDroidKaigiAppViewModel.current
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fun LandingScreen(modifier: Modifier = Modifier, onTimeout: () -> Unit) {
@Preview(showBackground = true)
@Composable
fun PreviewLandingScreen() {
ConferenceAppFeederTheme(false) {
ConferenceAppFeederTheme {
LandingScreen(
onTimeout = {}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ package io.github.droidkaigi.feeder.viewmodel

import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import io.github.droidkaigi.feeder.ProvideDroidKaigiAppViewModel
import io.github.droidkaigi.feeder.feed.ProvideFeedViewModel
import io.github.droidkaigi.feeder.setting.ProvideSettingViewModel
import io.github.droidkaigi.feeder.staff.ProvideStaffViewModel
import io.github.droidkaigi.feeder.staff.fakeStaffViewModel

@Composable
fun ProvideViewModels(content: @Composable () -> Unit) {
ProvideFeedViewModel(viewModel<RealFeedViewModel>()) {
ProvideStaffViewModel(viewModel = fakeStaffViewModel()) {
content()
ProvideDroidKaigiAppViewModel(viewModel<RealDroidKaigiAppViewModel>()) {
ProvideFeedViewModel(viewModel<RealFeedViewModel>()) {
ProvideSettingViewModel(viewModel<RealSettingViewModel>()) {
ProvideStaffViewModel(viewModel = fakeStaffViewModel()) {
content()
}
}
}
}
}
Loading

0 comments on commit d456b6c

Please sign in to comment.