Skip to content

Commit

Permalink
update: 优化功能
Browse files Browse the repository at this point in the history
  • Loading branch information
hefengbao committed Jul 18, 2024
1 parent 07ee306 commit a063053
Show file tree
Hide file tree
Showing 18 changed files with 580 additions and 11 deletions.
125 changes: 120 additions & 5 deletions app/src/main/java/com/hefengbao/jingmo/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,45 @@
package com.hefengbao.jingmo

import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.compose.rememberNavController
import com.hefengbao.jingmo.data.model.theme.DarkThemeConfig
import com.hefengbao.jingmo.data.model.theme.ThemeBrand
import com.hefengbao.jingmo.route.AppNavHost
import com.hefengbao.jingmo.ui.theme.AppTheme
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)

val viewModel: MainActivityViewModel by viewModels()
Expand All @@ -48,12 +60,63 @@ class MainActivity : ComponentActivity() {
// This also sets up the initial system bar style based on the platform theme
enableEdgeToEdge()

var uiState: MainActivityUiState by mutableStateOf(MainActivityUiState.Loading)

// Update the uiState
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState
.onEach { uiState = it }
.collect()
}
}

// Keep the splash screen on-screen until the UI state is loaded. This condition is
// evaluated each time the app needs to be redrawn so it should be fast to avoid blocking
// the UI.
splashScreen.setKeepOnScreenCondition {
when (uiState) {
MainActivityUiState.Loading -> true
is MainActivityUiState.Success -> false
}
}

// Turn off the decor fitting system windows, which allows us to handle insets,
// including IME animations, and go edge-to-edge
// This also sets up the initial system bar style based on the platform theme
enableEdgeToEdge()

setContent {
val darkTheme = shouldUseDarkTheme(uiState)

// Update the edge to edge configuration to match the theme
// This is the same parameters as the default enableEdgeToEdge call, but we manually
// resolve whether or not to show dark theme using uiState, since it can be different
// than the configuration's dark theme value based on the user preference.
DisposableEffect(darkTheme) {
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.auto(
android.graphics.Color.TRANSPARENT,
android.graphics.Color.TRANSPARENT,
) { darkTheme },
navigationBarStyle = SystemBarStyle.auto(
lightScrim,
darkScrim,
) { darkTheme },
)
onDispose {}
}

val appNavController = rememberNavController()


val showLanding by viewModel.showLanding.collectAsState(initial = true)

AppTheme {
AppTheme(
darkTheme = darkTheme,
androidTheme = shouldUseAndroidTheme(uiState),
disableDynamicTheming = shouldDisableDynamicTheming(uiState),
) {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
Expand All @@ -69,7 +132,6 @@ class MainActivity : ComponentActivity() {
}

destination?.let {
Log.i("MainActivity", "destination")
appNavController.navigate(it)
}
}
Expand All @@ -92,4 +154,57 @@ private fun LandingScreen(
.align(Alignment.Center),
)
}
}
}

/**
* Returns `true` if the Android theme should be used, as a function of the [uiState].
*/
@Composable
private fun shouldUseAndroidTheme(
uiState: MainActivityUiState,
): Boolean = when (uiState) {
MainActivityUiState.Loading -> false
is MainActivityUiState.Success -> when (uiState.userData.themeBrand) {
ThemeBrand.DEFAULT -> false
ThemeBrand.ANDROID -> true
}
}

/**
* Returns `true` if the dynamic color is disabled, as a function of the [uiState].
*/
@Composable
private fun shouldDisableDynamicTheming(
uiState: MainActivityUiState,
): Boolean = when (uiState) {
MainActivityUiState.Loading -> false
is MainActivityUiState.Success -> !uiState.userData.useDynamicColor
}

/**
* Returns `true` if dark theme should be used, as a function of the [uiState] and the
* current system context.
*/
@Composable
private fun shouldUseDarkTheme(
uiState: MainActivityUiState,
): Boolean = when (uiState) {
MainActivityUiState.Loading -> isSystemInDarkTheme()
is MainActivityUiState.Success -> when (uiState.userData.darkThemeConfig) {
DarkThemeConfig.FOLLOW_SYSTEM -> isSystemInDarkTheme()
DarkThemeConfig.LIGHT -> false
DarkThemeConfig.DARK -> true
}
}

/**
* The default light scrim, as defined by androidx and the platform:
* https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=35-38;drc=27e7d52e8604a080133e8b842db10c89b4482598
*/
private val lightScrim = android.graphics.Color.argb(0xe6, 0xFF, 0xFF, 0xFF)

/**
* The default dark scrim, as defined by androidx and the platform:
* https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=40-44;drc=27e7d52e8604a080133e8b842db10c89b4482598
*/
private val darkScrim = android.graphics.Color.argb(0x80, 0x1b, 0x1b, 0x1b)
27 changes: 25 additions & 2 deletions app/src/main/java/com/hefengbao/jingmo/MainActivityViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ package com.hefengbao.jingmo

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.hefengbao.jingmo.data.model.UserData
import com.hefengbao.jingmo.data.repository.settings.PreferenceRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject

Expand All @@ -33,13 +38,31 @@ class MainActivityViewModel @Inject constructor(
}
}

private val _showLanding: MutableStateFlow<Boolean> = MutableStateFlow(true)
var showLanding: SharedFlow<Boolean> = _showLanding
val uiState: StateFlow<MainActivityUiState> = preferenceRepository.getAppStatus().map {
MainActivityUiState.Success(
userData = UserData(
themeBrand = it.themeBrand,
darkThemeConfig = it.darkThemeConfig,
useDynamicColor = it.useDynamicColor,
)
)
}.stateIn(
scope = viewModelScope,
initialValue = MainActivityUiState.Loading,
started = SharingStarted.WhileSubscribed(5_000),
)

private val _showLanding: MutableStateFlow<Boolean> = MutableStateFlow(true)
val showLanding: SharedFlow<Boolean> = _showLanding
fun closeLanding() {
viewModelScope.launch {
delay(1500)
_showLanding.value = false
}
}
}

sealed interface MainActivityUiState {
data object Loading : MainActivityUiState
data class Success(val userData: UserData) : MainActivityUiState
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ package com.hefengbao.jingmo.data.datastore
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.hefengbao.jingmo.common.Constant
import com.hefengbao.jingmo.data.model.AppStatus
import com.hefengbao.jingmo.data.model.theme.DarkThemeConfig
import com.hefengbao.jingmo.data.model.theme.ThemeBrand
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

Expand All @@ -40,13 +43,23 @@ private suspend fun setString(context: Context, key: Preferences.Key<String>, va
context.app.edit { it[key] = value }
}

private suspend fun setBoolean(context: Context, key: Preferences.Key<Boolean>, value: Boolean) {
context.app.edit { it[key] = value }
}

class AppPreference(
private val context: Context
) {
val appStatus: Flow<AppStatus> = context.app.data.map {
AppStatus(
captureTextColor = it[PREF_CAPTURE_TEXT_COLOR] ?: "white",
captureBackgroundColor = it[PREF_CAPTURE_BACKGROUND_COLOR] ?: "#065279",
themeBrand = ThemeBrand.from(it[PREF_THEME_BRAND] ?: ThemeBrand.DEFAULT.name),
darkThemeConfig = DarkThemeConfig.from(
it[PREF_DARK_THEME_CONFIG] ?: DarkThemeConfig.FOLLOW_SYSTEM.name
),
useDynamicColor = it[PREF_USE_DYNAMIC_COLOR] ?: false,
showSyncDataTip = it[PREF_SHOW_SYNC_DATA_TIP] ?: true
)
}

Expand All @@ -56,9 +69,25 @@ class AppPreference(
suspend fun setCaptureBackgroundColor(color: String) =
setString(context, PREF_CAPTURE_BACKGROUND_COLOR, color)

suspend fun setThemeBrand(brand: ThemeBrand) =
setString(context, PREF_THEME_BRAND, brand.name)

suspend fun setDarkThemeConfig(config: DarkThemeConfig) =
setString(context, PREF_DARK_THEME_CONFIG, config.name)

suspend fun setUseDynamicColor(useDynamicColor: Boolean) =
setBoolean(context, PREF_USE_DYNAMIC_COLOR, useDynamicColor)

suspend fun setShowSyncDataTip(show: Boolean) =
setBoolean(context, PREF_SHOW_SYNC_DATA_TIP, show)

companion object {
private val PREF_CAPTURE_TEXT_COLOR = stringPreferencesKey("key_capture_text_color")
private val PREF_CAPTURE_BACKGROUND_COLOR =
stringPreferencesKey("key_capture_background_color")
private val PREF_THEME_BRAND = stringPreferencesKey("key_theme_brand")
private val PREF_DARK_THEME_CONFIG = stringPreferencesKey("key_dark_theme_config")
private val PREF_USE_DYNAMIC_COLOR = booleanPreferencesKey("key_use_dynamic_color")
private val PREF_SHOW_SYNC_DATA_TIP = booleanPreferencesKey("key_show_sync_data_tip")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@

package com.hefengbao.jingmo.data.model

import com.hefengbao.jingmo.data.model.theme.DarkThemeConfig
import com.hefengbao.jingmo.data.model.theme.ThemeBrand

data class AppStatus(
val captureTextColor: String,
val captureBackgroundColor: String,
val themeBrand: ThemeBrand,
val darkThemeConfig: DarkThemeConfig,
val useDynamicColor: Boolean,
val showSyncDataTip: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,4 @@ data class UserData(
val themeBrand: ThemeBrand,
val darkThemeConfig: DarkThemeConfig,
val useDynamicColor: Boolean,
val shouldHideOnboarding: Boolean,
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@ package com.hefengbao.jingmo.data.model.theme
enum class DarkThemeConfig {
FOLLOW_SYSTEM,
LIGHT,
DARK,
DARK;

companion object {
infix fun from(value: String): DarkThemeConfig =
DarkThemeConfig.entries.firstOrNull { it.name == value } ?: FOLLOW_SYSTEM
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@ package com.hefengbao.jingmo.data.model.theme

enum class ThemeBrand {
DEFAULT,
ANDROID,
ANDROID;

companion object {
infix fun from(value: String): ThemeBrand =
ThemeBrand.entries.firstOrNull { it.name == value } ?: DEFAULT
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ interface PreferenceRepository {
fun getAppStatus(): Flow<AppStatus>
suspend fun setCaptureTextColor(color: String)
suspend fun setCaptureBackgroundColor(color: String)
suspend fun setShowSyncDataTip(show: Boolean)
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,6 @@ class PreferenceRepositoryImpl @Inject constructor(

override suspend fun setCaptureBackgroundColor(color: String) =
app.setCaptureBackgroundColor(color)

override suspend fun setShowSyncDataTip(show: Boolean) = app.setShowSyncDataTip(show)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.hefengbao.jingmo.data.repository.settings

import com.hefengbao.jingmo.data.model.AppStatus
import com.hefengbao.jingmo.data.model.theme.DarkThemeConfig
import com.hefengbao.jingmo.data.model.theme.ThemeBrand
import kotlinx.coroutines.flow.Flow

interface ThemeRepository {
val appStatus: Flow<AppStatus>

/**
* Sets the desired theme brand.
*/
suspend fun setThemeBrand(themeBrand: ThemeBrand)

/**
* Sets the desired dark theme config.
*/
suspend fun setDarkThemeConfig(darkThemeConfig: DarkThemeConfig)

/**
* Sets the preferred dynamic color config.
*/
suspend fun setDynamicColorPreference(useDynamicColor: Boolean)
}
Loading

0 comments on commit a063053

Please sign in to comment.