Add a settings screen.
This commit is contained in:
parent
90bf11909f
commit
a6124cbe79
28 changed files with 668 additions and 63 deletions
|
|
@ -161,6 +161,9 @@ dependencies {
|
||||||
implementation("androidx.room:room-ktx:2.6.1")
|
implementation("androidx.room:room-ktx:2.6.1")
|
||||||
ksp("androidx.room:room-compiler:2.6.1")
|
ksp("androidx.room:room-compiler:2.6.1")
|
||||||
|
|
||||||
|
// DataStore
|
||||||
|
implementation("androidx.datastore:datastore-preferences:1.1.1")
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
implementation("io.coil-kt:coil-compose:2.6.0")
|
implementation("io.coil-kt:coil-compose:2.6.0")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
|
|
@ -57,6 +56,7 @@ val LocalRollOverlay = compositionLocalOf<BlurredRollOverlayHostState> {
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
private val themeViewModel: ThemeViewModel by viewModels()
|
||||||
private val launcherViewModel: LauncherViewModel by viewModels()
|
private val launcherViewModel: LauncherViewModel by viewModels()
|
||||||
private val rollViewModel: RollOverlayViewModel by viewModels()
|
private val rollViewModel: RollOverlayViewModel by viewModels()
|
||||||
|
|
||||||
|
|
@ -72,16 +72,18 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
LexiconTheme {
|
val snack = remember { SnackbarHostState() }
|
||||||
val snack = remember { SnackbarHostState() }
|
val overlay = rememberBlurredRollOverlayHostState(
|
||||||
val overlay = rememberBlurredRollOverlayHostState(
|
viewModel = rollViewModel,
|
||||||
viewModel = rollViewModel,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalActivity provides this,
|
LocalActivity provides this,
|
||||||
LocalSnack provides snack,
|
LocalSnack provides snack,
|
||||||
LocalRollOverlay provides overlay,
|
LocalRollOverlay provides overlay,
|
||||||
|
) {
|
||||||
|
LexiconTheme(
|
||||||
|
isDarkTheme = themeViewModel.useDarkTheme.value
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
contentWindowInsets = NO_WINDOW_INSETS,
|
contentWindowInsets = NO_WINDOW_INSETS,
|
||||||
|
|
@ -106,7 +108,7 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
snackbarHost = {
|
snackbarHost = {
|
||||||
val isDarkTheme = isSystemInDarkTheme()
|
val isDarkTheme = themeViewModel.useDarkTheme.value
|
||||||
val elevation = remember {
|
val elevation = remember {
|
||||||
derivedStateOf { if (isDarkTheme) 2.dp else 0.dp }
|
derivedStateOf { if (isDarkTheme) 2.dp else 0.dp }
|
||||||
}
|
}
|
||||||
|
|
@ -145,15 +147,17 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
BackHandler(enabled = overlay.isOverlayVisible) {
|
|
||||||
overlay.hideOverlay()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HandleFetchError(
|
BackHandler(
|
||||||
snack = snack,
|
enabled = overlay.isOverlayVisible,
|
||||||
errors = launcherViewModel.error,
|
onBack = { overlay.hideOverlay() },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
HandleFetchError(
|
||||||
|
snack = snack,
|
||||||
|
errors = launcherViewModel.error,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
52
app/src/main/java/com/pixelized/rplexicon/ThemeViewModel.kt
Normal file
52
app/src/main/java/com/pixelized/rplexicon/ThemeViewModel.kt
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.pixelized.rplexicon
|
||||||
|
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.pixelized.rplexicon.data.repository.preferences.PreferencesRepository
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class ThemeViewModel @Inject constructor(
|
||||||
|
private val preferencesRepository: PreferencesRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val useDarkTheme: State<Boolean>
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
get() {
|
||||||
|
val systemDarkTheme = isSystemInDarkTheme()
|
||||||
|
val useDarkTheme = preferencesRepository.useDarkThemeFlow.collectAsState(
|
||||||
|
initial = preferencesRepository.useDarkThemeFlow.value ?: systemDarkTheme
|
||||||
|
)
|
||||||
|
return remember {
|
||||||
|
derivedStateOf {
|
||||||
|
useDarkTheme.value ?: systemDarkTheme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateDarkThemeUsage(useDarkTheme: Boolean) {
|
||||||
|
preferencesRepository.updateUseDarkTheme(useDarkTheme = useDarkTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
fun isInDarkTheme(): Boolean {
|
||||||
|
val view = LocalView.current
|
||||||
|
return if (view.isInEditMode) {
|
||||||
|
isSystemInDarkTheme()
|
||||||
|
} else {
|
||||||
|
val themeViewModel: ThemeViewModel = hiltViewModel()
|
||||||
|
themeViewModel.useDarkTheme.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,4 +7,5 @@ data class LexiconConfig(
|
||||||
val featureQuests: Boolean,
|
val featureQuests: Boolean,
|
||||||
val featureSummary: Boolean,
|
val featureSummary: Boolean,
|
||||||
val featureSearch: Boolean,
|
val featureSearch: Boolean,
|
||||||
|
val featureOther: Boolean,
|
||||||
)
|
)
|
||||||
|
|
@ -24,6 +24,7 @@ class RemoteConfigRepository @Inject constructor() {
|
||||||
featureQuests = DEFAULT[FEATURE_QUESTS] as Boolean,
|
featureQuests = DEFAULT[FEATURE_QUESTS] as Boolean,
|
||||||
featureSummary = DEFAULT[FEATURE_SUMMARY] as Boolean,
|
featureSummary = DEFAULT[FEATURE_SUMMARY] as Boolean,
|
||||||
featureSearch = DEFAULT[FEATURE_SEARCH] as Boolean,
|
featureSearch = DEFAULT[FEATURE_SEARCH] as Boolean,
|
||||||
|
featureOther = DEFAULT[FEATURE_OTHER] as Boolean,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val config: StateFlow<LexiconConfig> get() = _config
|
val config: StateFlow<LexiconConfig> get() = _config
|
||||||
|
|
@ -54,6 +55,7 @@ class RemoteConfigRepository @Inject constructor() {
|
||||||
featureQuests = firebase.getBoolean(FEATURE_QUESTS),
|
featureQuests = firebase.getBoolean(FEATURE_QUESTS),
|
||||||
featureSummary = firebase.getBoolean(FEATURE_SUMMARY),
|
featureSummary = firebase.getBoolean(FEATURE_SUMMARY),
|
||||||
featureSearch = firebase.getBoolean(FEATURE_SEARCH),
|
featureSearch = firebase.getBoolean(FEATURE_SEARCH),
|
||||||
|
featureOther = firebase.getBoolean(FEATURE_OTHER),
|
||||||
)
|
)
|
||||||
_config.value = config
|
_config.value = config
|
||||||
}
|
}
|
||||||
|
|
@ -70,6 +72,7 @@ class RemoteConfigRepository @Inject constructor() {
|
||||||
private const val FEATURE_QUESTS = "feature_quests"
|
private const val FEATURE_QUESTS = "feature_quests"
|
||||||
private const val FEATURE_SUMMARY = "feature_summary"
|
private const val FEATURE_SUMMARY = "feature_summary"
|
||||||
private const val FEATURE_SEARCH = "feature_search"
|
private const val FEATURE_SEARCH = "feature_search"
|
||||||
|
private const val FEATURE_OTHER = "feature_other"
|
||||||
|
|
||||||
private val DEFAULT: HashMap<String, Any?> = hashMapOf(
|
private val DEFAULT: HashMap<String, Any?> = hashMapOf(
|
||||||
FEATURE_ADVENTURE to false,
|
FEATURE_ADVENTURE to false,
|
||||||
|
|
@ -78,6 +81,7 @@ class RemoteConfigRepository @Inject constructor() {
|
||||||
FEATURE_QUESTS to false,
|
FEATURE_QUESTS to false,
|
||||||
FEATURE_SUMMARY to false,
|
FEATURE_SUMMARY to false,
|
||||||
FEATURE_SEARCH to false,
|
FEATURE_SEARCH to false,
|
||||||
|
FEATURE_OTHER to false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.pixelized.rplexicon.data.repository.preferences
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.emptyPreferences
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class PreferencesRepository @Inject constructor(
|
||||||
|
@ApplicationContext context: Context
|
||||||
|
) {
|
||||||
|
private val Context.dataStore by preferencesDataStore(name = "user_preferences")
|
||||||
|
private val dataStore = context.dataStore
|
||||||
|
|
||||||
|
val useDarkThemeFlow: StateFlow<Boolean?> = dataStore.data
|
||||||
|
.catch {
|
||||||
|
emit(emptyPreferences())
|
||||||
|
}.map { preferences ->
|
||||||
|
preferences[PreferencesKeys.USE_DARK_THEME]
|
||||||
|
}.stateIn(
|
||||||
|
scope = CoroutineScope(Dispatchers.Default + Job()),
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
initialValue = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun updateUseDarkTheme(useDarkTheme: Boolean) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[PreferencesKeys.USE_DARK_THEME] = useDarkTheme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object PreferencesKeys {
|
||||||
|
val USE_DARK_THEME = booleanPreferencesKey("use_dark_theme")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ fun BackgroundImage(
|
||||||
fun rememberSaturationFilter(
|
fun rememberSaturationFilter(
|
||||||
saturation: Float = 0f,
|
saturation: Float = 0f,
|
||||||
): ColorFilter {
|
): ColorFilter {
|
||||||
return remember {
|
return remember(saturation) {
|
||||||
ColorFilter.colorMatrix(
|
ColorFilter.colorMatrix(
|
||||||
ColorMatrix().also { it.setToSaturation(saturation) }
|
ColorMatrix().also { it.setToSaturation(saturation) }
|
||||||
)
|
)
|
||||||
|
|
@ -86,7 +86,7 @@ fun rememberBackgroundGradient(
|
||||||
vararg gradients: Float,
|
vararg gradients: Float,
|
||||||
): Brush {
|
): Brush {
|
||||||
val colorScheme = MaterialTheme.colorScheme
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
return remember {
|
return remember(colorScheme) {
|
||||||
Brush.verticalGradient(
|
Brush.verticalGradient(
|
||||||
colors = gradients.map { colorScheme.surface.copy(alpha = it) }
|
colors = gradients.map { colorScheme.surface.copy(alpha = it) }
|
||||||
)
|
)
|
||||||
|
|
@ -99,7 +99,7 @@ fun rememberBackgroundGradient(
|
||||||
to: Float = 1.0f,
|
to: Float = 1.0f,
|
||||||
): Brush {
|
): Brush {
|
||||||
val colorScheme = MaterialTheme.colorScheme
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
return remember {
|
return remember(colorScheme) {
|
||||||
Brush.verticalGradient(
|
Brush.verticalGradient(
|
||||||
colors = listOf(
|
colors = listOf(
|
||||||
colorScheme.surface.copy(alpha = from),
|
colorScheme.surface.copy(alpha = from),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.pixelized.rplexicon.ui.composable
|
package com.pixelized.rplexicon.ui.composable
|
||||||
|
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.grid.LazyGridState
|
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -50,4 +51,25 @@ fun rememberAnimatedShadow(
|
||||||
targetValue = shadowTarget.value,
|
targetValue = shadowTarget.value,
|
||||||
label = "animated shadow",
|
label = "animated shadow",
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberAnimatedShadow(
|
||||||
|
scrollState: ScrollState,
|
||||||
|
rest: Dp = 0.dp,
|
||||||
|
target: Dp = 4.dp,
|
||||||
|
): State<Dp> {
|
||||||
|
val shadowTarget = remember(scrollState) {
|
||||||
|
derivedStateOf {
|
||||||
|
if (scrollState.value > 0) {
|
||||||
|
target
|
||||||
|
} else {
|
||||||
|
rest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return animateDpAsState(
|
||||||
|
targetValue = shadowTarget.value,
|
||||||
|
label = "animated shadow",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -10,9 +10,9 @@ import androidx.navigation.NavOptionsBuilder
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.AUTHENTICATION_ROUTE
|
import com.pixelized.rplexicon.ui.navigation.screens.AUTHENTICATION_ROUTE
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableAdventureDetail
|
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableAdventureBooks
|
import com.pixelized.rplexicon.ui.navigation.screens.composableAdventureBooks
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableAdventureChapters
|
import com.pixelized.rplexicon.ui.navigation.screens.composableAdventureChapters
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.screens.composableAdventureDetail
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableAuthentication
|
import com.pixelized.rplexicon.ui.navigation.screens.composableAuthentication
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableCharacterSheet
|
import com.pixelized.rplexicon.ui.navigation.screens.composableCharacterSheet
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableLanding
|
import com.pixelized.rplexicon.ui.navigation.screens.composableLanding
|
||||||
|
|
@ -23,6 +23,7 @@ import com.pixelized.rplexicon.ui.navigation.screens.composableLocations
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableQuestDetail
|
import com.pixelized.rplexicon.ui.navigation.screens.composableQuestDetail
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableQuests
|
import com.pixelized.rplexicon.ui.navigation.screens.composableQuests
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableSearch
|
import com.pixelized.rplexicon.ui.navigation.screens.composableSearch
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.screens.composableSettings
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableSpellDetail
|
import com.pixelized.rplexicon.ui.navigation.screens.composableSpellDetail
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableSummary
|
import com.pixelized.rplexicon.ui.navigation.screens.composableSummary
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLanding
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLanding
|
||||||
|
|
@ -66,6 +67,7 @@ fun ScreenNavHost(
|
||||||
composableAdventureBooks()
|
composableAdventureBooks()
|
||||||
composableAdventureChapters()
|
composableAdventureChapters()
|
||||||
composableAdventureDetail()
|
composableAdventureDetail()
|
||||||
|
composableSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.pixelized.rplexicon.ui.navigation.screens
|
||||||
|
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.NavOptionsBuilder
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.animatedComposable
|
||||||
|
import com.pixelized.rplexicon.ui.screens.settings.SettingsScreen
|
||||||
|
|
||||||
|
private val ROUTE = "settings"
|
||||||
|
|
||||||
|
val SETTINGS_ROUTE = ROUTE
|
||||||
|
|
||||||
|
fun NavGraphBuilder.composableSettings() {
|
||||||
|
animatedComposable(
|
||||||
|
route = SETTINGS_ROUTE,
|
||||||
|
animation = NavigationAnimation.Push,
|
||||||
|
) {
|
||||||
|
SettingsScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavHostController.navigateToSettings(
|
||||||
|
option: NavOptionsBuilder.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
val route = ROUTE
|
||||||
|
navigate(route = route, builder = option)
|
||||||
|
}
|
||||||
|
|
@ -32,6 +32,7 @@ import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -52,7 +53,6 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.credentials.exceptions.GetCredentialCancellationException
|
import androidx.credentials.exceptions.GetCredentialCancellationException
|
||||||
import androidx.credentials.exceptions.GetCredentialProviderConfigurationException
|
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.pixelized.rplexicon.LocalActivity
|
import com.pixelized.rplexicon.LocalActivity
|
||||||
import com.pixelized.rplexicon.LocalSnack
|
import com.pixelized.rplexicon.LocalSnack
|
||||||
|
|
@ -61,6 +61,7 @@ import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
import com.pixelized.rplexicon.ui.theme.colors.GoogleColorPalette
|
import com.pixelized.rplexicon.ui.theme.colors.GoogleColorPalette
|
||||||
import com.pixelized.rplexicon.utilitary.sensor.Gyroscope
|
import com.pixelized.rplexicon.utilitary.sensor.Gyroscope
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.E
|
import kotlin.math.E
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
@ -90,6 +91,7 @@ fun AuthenticationScreen(
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val snack = LocalSnack.current
|
val snack = LocalSnack.current
|
||||||
val activity = LocalActivity.current
|
val activity = LocalActivity.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
Surface {
|
Surface {
|
||||||
PartyBackground()
|
PartyBackground()
|
||||||
|
|
@ -101,7 +103,9 @@ fun AuthenticationScreen(
|
||||||
.padding(all = 16.dp),
|
.padding(all = 16.dp),
|
||||||
version = versionVM.version,
|
version = versionVM.version,
|
||||||
onGoogleSignIn = {
|
onGoogleSignIn = {
|
||||||
authenticationVM.signIn(activity = activity)
|
scope.launch {
|
||||||
|
authenticationVM.signIn(activity = activity)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -344,7 +348,7 @@ private fun animatedWeight(
|
||||||
@Composable
|
@Composable
|
||||||
private fun rememberBackgroundGradient(): Brush {
|
private fun rememberBackgroundGradient(): Brush {
|
||||||
val colorScheme = MaterialTheme.colorScheme
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
return remember {
|
return remember(colorScheme) {
|
||||||
Brush.verticalGradient(
|
Brush.verticalGradient(
|
||||||
colors = listOf(
|
colors = listOf(
|
||||||
colorScheme.surface.copy(alpha = 0.25f),
|
colorScheme.surface.copy(alpha = 0.25f),
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ class AuthenticationViewModel @Inject constructor(
|
||||||
AuthenticationStateUio.Initial
|
AuthenticationStateUio.Initial
|
||||||
)
|
)
|
||||||
|
|
||||||
fun signIn(activity: Activity) {
|
suspend fun signIn(activity: Activity) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
// create the credential manager
|
// create the credential manager
|
||||||
val credentialManager = CredentialManager.create(
|
val credentialManager = CredentialManager.create(
|
||||||
|
|
@ -101,4 +101,14 @@ class AuthenticationViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun signOut() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
Firebase.auth.signOut()
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
authenticationState.value = AuthenticationStateUio.Initial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +58,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.pixelized.rplexicon.LocalRollOverlay
|
import com.pixelized.rplexicon.LocalRollOverlay
|
||||||
import com.pixelized.rplexicon.NO_WINDOW_INSETS
|
import com.pixelized.rplexicon.NO_WINDOW_INSETS
|
||||||
import com.pixelized.rplexicon.R
|
import com.pixelized.rplexicon.R
|
||||||
import com.pixelized.rplexicon.ui.composable.Handle
|
|
||||||
import com.pixelized.rplexicon.ui.composable.KeepOnScreen
|
import com.pixelized.rplexicon.ui.composable.KeepOnScreen
|
||||||
import com.pixelized.rplexicon.ui.composable.Loader
|
import com.pixelized.rplexicon.ui.composable.Loader
|
||||||
import com.pixelized.rplexicon.ui.composable.edit.HandleHitPointEditDialog
|
import com.pixelized.rplexicon.ui.composable.edit.HandleHitPointEditDialog
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ data class LandingItemUio(
|
||||||
QUEST,
|
QUEST,
|
||||||
MAP,
|
MAP,
|
||||||
ADVENTURE,
|
ADVENTURE,
|
||||||
|
OTHERS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexicon
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLocation
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLocation
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToQuestList
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToQuestList
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToSearch
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToSearch
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToSettings
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToSummary
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToSummary
|
||||||
import com.pixelized.rplexicon.ui.screens.authentication.VersionViewModel
|
import com.pixelized.rplexicon.ui.screens.authentication.VersionViewModel
|
||||||
import com.pixelized.rplexicon.ui.screens.landing.LandingItemUio.Feature.ADVENTURE
|
import com.pixelized.rplexicon.ui.screens.landing.LandingItemUio.Feature.ADVENTURE
|
||||||
|
|
@ -95,6 +96,7 @@ fun LandingScreen(
|
||||||
characters = viewModel.characterSheets,
|
characters = viewModel.characterSheets,
|
||||||
tools = viewModel.toolFeatures,
|
tools = viewModel.toolFeatures,
|
||||||
encyclopedia = viewModel.lexiconFeatures,
|
encyclopedia = viewModel.lexiconFeatures,
|
||||||
|
others = viewModel.otherFeatures,
|
||||||
version = versionVM.version,
|
version = versionVM.version,
|
||||||
onFeature = {
|
onFeature = {
|
||||||
when (it.feature) {
|
when (it.feature) {
|
||||||
|
|
@ -130,6 +132,10 @@ fun LandingScreen(
|
||||||
ADVENTURE -> {
|
ADVENTURE -> {
|
||||||
screen.navigateToAdventures()
|
screen.navigateToAdventures()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LandingItemUio.Feature.OTHERS -> {
|
||||||
|
screen.navigateToSettings()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -150,6 +156,7 @@ private fun LandingContent(
|
||||||
characters: State<List<List<LandingItemUio>>>,
|
characters: State<List<List<LandingItemUio>>>,
|
||||||
tools: State<List<List<LandingItemUio>>>,
|
tools: State<List<List<LandingItemUio>>>,
|
||||||
encyclopedia: State<List<List<LandingItemUio>>>,
|
encyclopedia: State<List<List<LandingItemUio>>>,
|
||||||
|
others: State<List<List<LandingItemUio>>>,
|
||||||
version: VersionViewModel.Version,
|
version: VersionViewModel.Version,
|
||||||
onFeature: (LandingItemUio) -> Unit,
|
onFeature: (LandingItemUio) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
@ -334,6 +341,44 @@ private fun LandingContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (others.value.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(top = sectionPadding),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
text = stringResource(id = R.string.landing__caterogy__other),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
others.value.forEach { group ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||||
|
) {
|
||||||
|
group.forEach { item ->
|
||||||
|
LandingItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(ratio = 1f),
|
||||||
|
imagePadding = PaddingValues(
|
||||||
|
top = 8.dp,
|
||||||
|
start = 16.dp,
|
||||||
|
end = 16.dp,
|
||||||
|
bottom = 24.dp,
|
||||||
|
),
|
||||||
|
item = item,
|
||||||
|
backgroundFilter = null,
|
||||||
|
backgroundGradientFrom = 0.0f,
|
||||||
|
backgroundGradientTo = 0.5f,
|
||||||
|
onClick = { onFeature(item) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
repeat(3 - group.size) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(alignment = Alignment.End)
|
.align(alignment = Alignment.End)
|
||||||
|
|
@ -383,8 +428,8 @@ private fun Modifier.magic(): Modifier = composed {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview(uiMode = UI_MODE_NIGHT_NO, heightDp = 1332)
|
@Preview(uiMode = UI_MODE_NIGHT_NO, heightDp = 1452)
|
||||||
@Preview(uiMode = UI_MODE_NIGHT_YES, heightDp = 1332)
|
@Preview(uiMode = UI_MODE_NIGHT_YES, heightDp = 1452)
|
||||||
private fun LandingPreview() {
|
private fun LandingPreview() {
|
||||||
LexiconTheme {
|
LexiconTheme {
|
||||||
Surface {
|
Surface {
|
||||||
|
|
@ -484,6 +529,20 @@ private fun LandingPreview() {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
others = remember {
|
||||||
|
mutableStateOf(
|
||||||
|
listOf(
|
||||||
|
listOf(
|
||||||
|
LandingItemUio(
|
||||||
|
feature = LandingItemUio.Feature.OTHERS,
|
||||||
|
title = "Settings",
|
||||||
|
subTitle = null,
|
||||||
|
icon = R.drawable.icbg_item_foundry_misc_gear_a,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
version = VersionViewModel.Version(R.string.app_name, "0.0.0", "0", true),
|
version = VersionViewModel.Version(R.string.app_name, "0.0.0", "0", true),
|
||||||
onFeature = { },
|
onFeature = { },
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -137,4 +137,30 @@ class LandingViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}.collectAsState(initial = emptyList())
|
}.collectAsState(initial = emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val otherFeatures: State<List<List<LandingItemUio>>>
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
return remember {
|
||||||
|
configRepository.config
|
||||||
|
.map { config ->
|
||||||
|
listOfNotNull(
|
||||||
|
when (config.featureOther) {
|
||||||
|
true -> LandingItemUio(
|
||||||
|
feature = LandingItemUio.Feature.OTHERS,
|
||||||
|
title = context.getString(R.string.settings__title),
|
||||||
|
subTitle = null,
|
||||||
|
icon = R.drawable.icbg_item_foundry_misc_gear_a,
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.map { items ->
|
||||||
|
items.groupBy { items.indexOf(it) / 3 }.values.toList()
|
||||||
|
}
|
||||||
|
}.collectAsState(initial = emptyList())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.animation.togetherWith
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
|
@ -84,6 +85,7 @@ import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
|
||||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCard
|
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCard
|
||||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio
|
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio
|
||||||
import com.pixelized.rplexicon.ui.screens.rolls.preview.rememberRollAlterations
|
import com.pixelized.rplexicon.ui.screens.rolls.preview.rememberRollAlterations
|
||||||
|
import com.pixelized.rplexicon.isInDarkTheme
|
||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -98,6 +100,7 @@ fun RollOverlay(
|
||||||
|
|
||||||
RollOverlayContent(
|
RollOverlayContent(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
isDarkTheme = isInDarkTheme(),
|
||||||
drawer = drawer,
|
drawer = drawer,
|
||||||
dice = viewModel.dice,
|
dice = viewModel.dice,
|
||||||
card = viewModel.card,
|
card = viewModel.card,
|
||||||
|
|
@ -145,6 +148,7 @@ fun RollOverlay(
|
||||||
@Composable
|
@Composable
|
||||||
private fun RollOverlayContent(
|
private fun RollOverlayContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
isDarkTheme: Boolean,
|
||||||
drawer: DrawerState,
|
drawer: DrawerState,
|
||||||
dice: State<RollDiceUio?>,
|
dice: State<RollDiceUio?>,
|
||||||
card: State<ThrowsCardUio?>,
|
card: State<ThrowsCardUio?>,
|
||||||
|
|
@ -321,6 +325,7 @@ private fun RollOverlayContent(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = if (enableDrawer.value) 32.dp else 0.dp)
|
.padding(bottom = if (enableDrawer.value) 32.dp else 0.dp)
|
||||||
.padding(all = 16.dp),
|
.padding(all = 16.dp),
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
throws = it,
|
throws = it,
|
||||||
showDetail = showDetail,
|
showDetail = showDetail,
|
||||||
onClick = onCard,
|
onClick = onCard,
|
||||||
|
|
@ -358,6 +363,7 @@ private fun RollOverlayPreview(
|
||||||
Surface {
|
Surface {
|
||||||
RollOverlayContent(
|
RollOverlayContent(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
isDarkTheme = isSystemInDarkTheme(),
|
||||||
drawer = rememberDrawerState(initialValue = preview.drawer),
|
drawer = rememberDrawerState(initialValue = preview.drawer),
|
||||||
dice = preview.dice,
|
dice = preview.dice,
|
||||||
card = preview.card,
|
card = preview.card,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
|
@ -52,6 +51,7 @@ import com.pixelized.rplexicon.R
|
||||||
import com.pixelized.rplexicon.ui.agsl.dancingColor
|
import com.pixelized.rplexicon.ui.agsl.dancingColor
|
||||||
import com.pixelized.rplexicon.ui.agsl.rememberTimeState
|
import com.pixelized.rplexicon.ui.agsl.rememberTimeState
|
||||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio.Throw.Type
|
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio.Throw.Type
|
||||||
|
import com.pixelized.rplexicon.isInDarkTheme
|
||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.annotatedSpan
|
import com.pixelized.rplexicon.utilitary.extentions.annotatedSpan
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||||
|
|
@ -98,12 +98,12 @@ data class ThrowsCardUio(
|
||||||
@Composable
|
@Composable
|
||||||
fun ThrowsCard(
|
fun ThrowsCard(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
isDarkTheme: Boolean = isInDarkTheme(),
|
||||||
throws: ThrowsCardUio,
|
throws: ThrowsCardUio,
|
||||||
showDetail: State<Boolean>,
|
showDetail: State<Boolean>,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val isDarkMode = isSystemInDarkTheme()
|
|
||||||
val colorScheme = MaterialTheme.colorScheme
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
val typography = MaterialTheme.typography
|
val typography = MaterialTheme.typography
|
||||||
val highlight = remember { SpanStyle(color = colorScheme.primary) }
|
val highlight = remember { SpanStyle(color = colorScheme.primary) }
|
||||||
|
|
@ -118,10 +118,13 @@ fun ThrowsCard(
|
||||||
Surface(
|
Surface(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.ddBorder(inner = inner, outline = remember { CutCornerShape(size = 16.dp) })
|
.ddBorder(
|
||||||
|
inner = inner,
|
||||||
|
outline = remember { CutCornerShape(size = 16.dp) },
|
||||||
|
)
|
||||||
.clip(shape = inner)
|
.clip(shape = inner)
|
||||||
.clickable(onClick = onClick),
|
.clickable(onClick = onClick),
|
||||||
tonalElevation = if (isDarkMode) 4.dp else 0.dp,
|
tonalElevation = if (isDarkTheme) 4.dp else 0.dp,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
package com.pixelized.rplexicon.ui.screens.settings
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.pixelized.rplexicon.R
|
||||||
|
import com.pixelized.rplexicon.ThemeViewModel
|
||||||
|
import com.pixelized.rplexicon.ui.composable.rememberAnimatedShadow
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.rootOption
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToAuthentication
|
||||||
|
import com.pixelized.rplexicon.ui.screens.authentication.AuthenticationViewModel
|
||||||
|
import com.pixelized.rplexicon.ui.screens.settings.composable.ButtonPreference
|
||||||
|
import com.pixelized.rplexicon.ui.screens.settings.composable.SwitchPreference
|
||||||
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsScreen(
|
||||||
|
themeViewModel: ThemeViewModel = hiltViewModel(),
|
||||||
|
authenticationViewModel: AuthenticationViewModel = hiltViewModel(),
|
||||||
|
) {
|
||||||
|
val screen = LocalScreenNavHost.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
SettingsContent(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.systemBarsPadding(),
|
||||||
|
isInDarkTheme = themeViewModel.useDarkTheme.value,
|
||||||
|
onBack = {
|
||||||
|
screen.popBackStack()
|
||||||
|
},
|
||||||
|
onThemeChange = {
|
||||||
|
scope.launch {
|
||||||
|
themeViewModel.updateDarkThemeUsage(useDarkTheme = it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLogout = {
|
||||||
|
scope.launch {
|
||||||
|
authenticationViewModel.signOut()
|
||||||
|
screen.navigateToAuthentication(option = rootOption())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun SettingsContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isInDarkTheme: Boolean,
|
||||||
|
scrollState: ScrollState = rememberScrollState(),
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onThemeChange: (Boolean) -> Unit,
|
||||||
|
onLogout: () -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
modifier = modifier,
|
||||||
|
topBar = {
|
||||||
|
val shadow = rememberAnimatedShadow(scrollState = scrollState)
|
||||||
|
TopAppBar(
|
||||||
|
modifier = Modifier.shadow(elevation = shadow.value),
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_arrow_back_ios_new_24),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(id = R.string.settings__title))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = { paddings ->
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.verticalScroll(state = scrollState)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(paddingValues = paddings),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
ButtonPreference(
|
||||||
|
title = stringResource(id = R.string.settings__logout__title),
|
||||||
|
description = stringResource(id = R.string.settings__logout__description),
|
||||||
|
action = stringResource(id = R.string.settings__logout__action),
|
||||||
|
onClick = onLogout,
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider()
|
||||||
|
|
||||||
|
SwitchPreference(
|
||||||
|
title = stringResource(id = R.string.settings__dark_theme__title),
|
||||||
|
description = stringResource(id = R.string.settings__dark_theme__description),
|
||||||
|
value = isInDarkTheme,
|
||||||
|
onCheckedChange = onThemeChange,
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
private fun SettingsPreview() {
|
||||||
|
LexiconTheme {
|
||||||
|
SettingsContent(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
isInDarkTheme = false,
|
||||||
|
onBack = {},
|
||||||
|
onThemeChange = {},
|
||||||
|
onLogout = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
package com.pixelized.rplexicon.ui.screens.settings.composable
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.rplexicon.R
|
||||||
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ButtonPreference(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
paddingValues: PaddingValues = PaddingValues(
|
||||||
|
start = 16.dp,
|
||||||
|
end = 8.dp,
|
||||||
|
top = 8.dp,
|
||||||
|
bottom = 8.dp,
|
||||||
|
),
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
action: String,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(paddingValues = paddingValues)
|
||||||
|
.height(IntrinsicSize.Min)
|
||||||
|
.then(other = modifier),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(weight = 1f),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
text = title
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
text = description
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = onClick,
|
||||||
|
) {
|
||||||
|
Text(text = action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||||
|
private fun ButtonPreferencePreview() {
|
||||||
|
LexiconTheme {
|
||||||
|
ButtonPreference(
|
||||||
|
title = stringResource(id = R.string.settings__logout__title),
|
||||||
|
description = stringResource(id = R.string.settings__logout__description),
|
||||||
|
action = stringResource(id = R.string.settings__logout__action),
|
||||||
|
onClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package com.pixelized.rplexicon.ui.screens.settings.composable
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SwitchPreference(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
paddingValues: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
value: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(paddingValues = paddingValues)
|
||||||
|
.height(IntrinsicSize.Min)
|
||||||
|
.then(other = modifier),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(weight = 1f),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
text = title
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
text = description
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = value,
|
||||||
|
onCheckedChange = onCheckedChange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
private fun TogglePreferencePreview() {
|
||||||
|
LexiconTheme {
|
||||||
|
Surface {
|
||||||
|
SwitchPreference(
|
||||||
|
title = "Title",
|
||||||
|
description = "description",
|
||||||
|
value = true,
|
||||||
|
onCheckedChange = { },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
|
@ -40,12 +41,12 @@ data class LexiconTheme(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LexiconTheme(
|
fun LexiconTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
isDarkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val lexiconTheme = remember(density) {
|
val lexiconTheme = remember(density, isDarkTheme) {
|
||||||
val colorScheme = when (darkTheme) {
|
val colorScheme = when (isDarkTheme) {
|
||||||
true -> darkColorScheme()
|
true -> darkColorScheme()
|
||||||
else -> lightColorScheme()
|
else -> lightColorScheme()
|
||||||
}
|
}
|
||||||
|
|
@ -58,29 +59,29 @@ fun LexiconTheme(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val view = LocalView.current
|
CompositionLocalProvider(
|
||||||
if (!view.isInEditMode) {
|
LocalLexiconTheme provides lexiconTheme,
|
||||||
SideEffect {
|
) {
|
||||||
val window = (view.context as Activity).window
|
val theme = LocalLexiconTheme.current
|
||||||
window.statusBarColor = lexiconTheme.colorScheme.status.toArgb()
|
val view = LocalView.current
|
||||||
window.navigationBarColor = lexiconTheme.colorScheme.navigation.toArgb()
|
|
||||||
WindowCompat.getInsetsController(window, view).let {
|
|
||||||
it.isAppearanceLightStatusBars = !darkTheme
|
|
||||||
it.isAppearanceLightNavigationBars = !darkTheme
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialTheme(
|
if (!view.isInEditMode) {
|
||||||
colorScheme = lexiconTheme.colorScheme.base,
|
SideEffect {
|
||||||
shapes = lexiconTheme.shapes.base,
|
val window = (view.context as Activity).window
|
||||||
typography = lexiconTheme.typography.base,
|
window.statusBarColor = theme.colorScheme.status.toArgb()
|
||||||
content = {
|
window.navigationBarColor = theme.colorScheme.navigation.toArgb()
|
||||||
CompositionLocalProvider(
|
WindowCompat.getInsetsController(window, view).let {
|
||||||
LocalLexiconTheme provides lexiconTheme,
|
it.isAppearanceLightStatusBars = !isDarkTheme
|
||||||
) {
|
it.isAppearanceLightNavigationBars = !isDarkTheme
|
||||||
content()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = theme.colorScheme.base,
|
||||||
|
typography = theme.typography.base,
|
||||||
|
shapes = theme.shapes.base,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,7 +9,6 @@ import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
|
@ -43,6 +42,7 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.google.accompanist.placeholder.PlaceholderHighlight
|
import com.google.accompanist.placeholder.PlaceholderHighlight
|
||||||
import com.google.accompanist.placeholder.placeholder
|
import com.google.accompanist.placeholder.placeholder
|
||||||
|
import com.pixelized.rplexicon.isInDarkTheme
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||||
|
|
||||||
fun Modifier.placeholder(
|
fun Modifier.placeholder(
|
||||||
|
|
@ -114,7 +114,7 @@ fun Modifier.ddBorder(
|
||||||
inner: Shape,
|
inner: Shape,
|
||||||
innerWidth: Dp = 1.dp,
|
innerWidth: Dp = 1.dp,
|
||||||
): Modifier = composed {
|
): Modifier = composed {
|
||||||
val isDarkTheme = isSystemInDarkTheme()
|
val isDarkTheme = isInDarkTheme()
|
||||||
val elevation = remember { derivedStateOf { if (isDarkTheme) 2.dp else 0.dp } }
|
val elevation = remember { derivedStateOf { if (isDarkTheme) 2.dp else 0.dp } }
|
||||||
val colorScheme = MaterialTheme.lexicon.colorScheme
|
val colorScheme = MaterialTheme.lexicon.colorScheme
|
||||||
this then Modifier
|
this then Modifier
|
||||||
|
|
@ -147,7 +147,8 @@ fun Modifier.ddBorder(
|
||||||
|
|
||||||
fun Modifier.lexiconShadow(): Modifier {
|
fun Modifier.lexiconShadow(): Modifier {
|
||||||
return this then composed {
|
return this then composed {
|
||||||
if (isSystemInDarkTheme()) {
|
val isDarkTheme = isInDarkTheme()
|
||||||
|
if (isDarkTheme) {
|
||||||
val color = DividerDefaults.color
|
val color = DividerDefaults.color
|
||||||
drawWithContent {
|
drawWithContent {
|
||||||
drawContent()
|
drawContent()
|
||||||
|
|
|
||||||
BIN
app/src/main/res/drawable/icbg_item_foundry_misc_gear_a.png
Normal file
BIN
app/src/main/res/drawable/icbg_item_foundry_misc_gear_a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
|
|
@ -239,6 +239,14 @@
|
||||||
<string name="landing__caterogy__character">Feuilles de personnage</string>
|
<string name="landing__caterogy__character">Feuilles de personnage</string>
|
||||||
<string name="landing__caterogy__encyclopedia">Encyclopédie</string>
|
<string name="landing__caterogy__encyclopedia">Encyclopédie</string>
|
||||||
<string name="landing__caterogy__tools">Outils</string>
|
<string name="landing__caterogy__tools">Outils</string>
|
||||||
|
<string name="landing__caterogy__other">Autre</string>
|
||||||
|
|
||||||
<string name="adventures_title">Histoires & Péripéties</string>
|
<string name="adventures_title">Histoires & Péripéties</string>
|
||||||
|
|
||||||
|
<string name="settings__title">Paramêtres</string>
|
||||||
|
<string name="settings__dark_theme__title">Theme sombre</string>
|
||||||
|
<string name="settings__dark_theme__description">Utilise un fond noir pour économiser la batterie.</string>
|
||||||
|
<string name="settings__logout__title">Authentification</string>
|
||||||
|
<string name="settings__logout__description">Déconnectez vous de votre compte google.</string>
|
||||||
|
<string name="settings__logout__action">Se déconnectez</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -50,6 +50,7 @@
|
||||||
<string name="landing__caterogy__character">Character\'s sheets</string>
|
<string name="landing__caterogy__character">Character\'s sheets</string>
|
||||||
<string name="landing__caterogy__tools">Tools</string>
|
<string name="landing__caterogy__tools">Tools</string>
|
||||||
<string name="landing__caterogy__encyclopedia">Encyclopedia</string>
|
<string name="landing__caterogy__encyclopedia">Encyclopedia</string>
|
||||||
|
<string name="landing__caterogy__other">Other</string>
|
||||||
<string name="landing__character_brulkhai" translatable="false">Brulkhai</string>
|
<string name="landing__character_brulkhai" translatable="false">Brulkhai</string>
|
||||||
<string name="landing__character_leandre" translatable="false">Léandre</string>
|
<string name="landing__character_leandre" translatable="false">Léandre</string>
|
||||||
<string name="landing__character_nelia" translatable="false">Nelia</string>
|
<string name="landing__character_nelia" translatable="false">Nelia</string>
|
||||||
|
|
@ -246,4 +247,11 @@
|
||||||
<string name="summary__title">Game Master</string>
|
<string name="summary__title">Game Master</string>
|
||||||
|
|
||||||
<string name="adventures_title">Stories & Adventures</string>
|
<string name="adventures_title">Stories & Adventures</string>
|
||||||
|
|
||||||
|
<string name="settings__title">Settings</string>
|
||||||
|
<string name="settings__dark_theme__title">Dark theme</string>
|
||||||
|
<string name="settings__dark_theme__description">Use black background to save battery.</string>
|
||||||
|
<string name="settings__logout__title">Authentication</string>
|
||||||
|
<string name="settings__logout__description">Logout from your google account.</string>
|
||||||
|
<string name="settings__logout__action">Logout</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue