Add a settings screen.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2024-06-10 09:23:48 +02:00
parent 90bf11909f
commit a6124cbe79
28 changed files with 668 additions and 63 deletions

View file

@ -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")
} }

View file

@ -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,
)
}
} }
} }
} }

View 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
}
}

View file

@ -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,
) )

View file

@ -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,
) )
} }
} }

View file

@ -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")
}
}

View file

@ -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

View file

@ -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),

View file

@ -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",
)
} }

View file

@ -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()
} }
} }
} }

View file

@ -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)
}

View file

@ -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),

View file

@ -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
}
}
}
} }

View file

@ -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

View file

@ -58,6 +58,7 @@ data class LandingItemUio(
QUEST, QUEST,
MAP, MAP,
ADVENTURE, ADVENTURE,
OTHERS,
} }
} }

View file

@ -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 = { },
) )

View file

@ -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())
}
} }

View file

@ -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,

View file

@ -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

View file

@ -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 = {},
)
}
}

View file

@ -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 = {},
)
}
}

View file

@ -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 = { },
)
}
}
}

View file

@ -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,
)
}
} }

View file

@ -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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -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 &amp; Péripéties</string> <string name="adventures_title">Histoires &amp; 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>

View file

@ -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 &amp; Adventures</string> <string name="adventures_title">Stories &amp; 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>