diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7ef7610..1bc83ce 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -161,6 +161,9 @@ dependencies { implementation("androidx.room:room-ktx:2.6.1") ksp("androidx.room:room-compiler:2.6.1") + // DataStore + implementation("androidx.datastore:datastore-preferences:1.1.1") + // Image implementation("io.coil-kt:coil-compose:2.6.0") } diff --git a/app/src/main/java/com/pixelized/rplexicon/MainActivity.kt b/app/src/main/java/com/pixelized/rplexicon/MainActivity.kt index b2a26e8..4b4883b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/MainActivity.kt +++ b/app/src/main/java/com/pixelized/rplexicon/MainActivity.kt @@ -6,7 +6,6 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent import androidx.activity.viewModels -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding @@ -57,6 +56,7 @@ val LocalRollOverlay = compositionLocalOf { @AndroidEntryPoint class MainActivity : ComponentActivity() { + private val themeViewModel: ThemeViewModel by viewModels() private val launcherViewModel: LauncherViewModel by viewModels() private val rollViewModel: RollOverlayViewModel by viewModels() @@ -72,16 +72,18 @@ class MainActivity : ComponentActivity() { } setContent { - LexiconTheme { - val snack = remember { SnackbarHostState() } - val overlay = rememberBlurredRollOverlayHostState( - viewModel = rollViewModel, - ) + val snack = remember { SnackbarHostState() } + val overlay = rememberBlurredRollOverlayHostState( + viewModel = rollViewModel, + ) - CompositionLocalProvider( - LocalActivity provides this, - LocalSnack provides snack, - LocalRollOverlay provides overlay, + CompositionLocalProvider( + LocalActivity provides this, + LocalSnack provides snack, + LocalRollOverlay provides overlay, + ) { + LexiconTheme( + isDarkTheme = themeViewModel.useDarkTheme.value ) { Scaffold( contentWindowInsets = NO_WINDOW_INSETS, @@ -106,7 +108,7 @@ class MainActivity : ComponentActivity() { } }, snackbarHost = { - val isDarkTheme = isSystemInDarkTheme() + val isDarkTheme = themeViewModel.useDarkTheme.value val elevation = remember { derivedStateOf { if (isDarkTheme) 2.dp else 0.dp } } @@ -145,15 +147,17 @@ class MainActivity : ComponentActivity() { } } ) - BackHandler(enabled = overlay.isOverlayVisible) { - overlay.hideOverlay() - } - } - HandleFetchError( - snack = snack, - errors = launcherViewModel.error, - ) + BackHandler( + enabled = overlay.isOverlayVisible, + onBack = { overlay.hideOverlay() }, + ) + + HandleFetchError( + snack = snack, + errors = launcherViewModel.error, + ) + } } } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ThemeViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ThemeViewModel.kt new file mode 100644 index 0000000..8d0c834 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ThemeViewModel.kt @@ -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 + @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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/data/model/LexiconConfig.kt b/app/src/main/java/com/pixelized/rplexicon/data/model/LexiconConfig.kt index 314d00b..6bb2902 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/model/LexiconConfig.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/model/LexiconConfig.kt @@ -7,4 +7,5 @@ data class LexiconConfig( val featureQuests: Boolean, val featureSummary: Boolean, val featureSearch: Boolean, + val featureOther: Boolean, ) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/data/repository/firebase/RemoteConfigRepository.kt b/app/src/main/java/com/pixelized/rplexicon/data/repository/firebase/RemoteConfigRepository.kt index ecd905d..ddb9f55 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/repository/firebase/RemoteConfigRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/repository/firebase/RemoteConfigRepository.kt @@ -24,6 +24,7 @@ class RemoteConfigRepository @Inject constructor() { featureQuests = DEFAULT[FEATURE_QUESTS] as Boolean, featureSummary = DEFAULT[FEATURE_SUMMARY] as Boolean, featureSearch = DEFAULT[FEATURE_SEARCH] as Boolean, + featureOther = DEFAULT[FEATURE_OTHER] as Boolean, ) ) val config: StateFlow get() = _config @@ -54,6 +55,7 @@ class RemoteConfigRepository @Inject constructor() { featureQuests = firebase.getBoolean(FEATURE_QUESTS), featureSummary = firebase.getBoolean(FEATURE_SUMMARY), featureSearch = firebase.getBoolean(FEATURE_SEARCH), + featureOther = firebase.getBoolean(FEATURE_OTHER), ) _config.value = config } @@ -70,6 +72,7 @@ class RemoteConfigRepository @Inject constructor() { private const val FEATURE_QUESTS = "feature_quests" private const val FEATURE_SUMMARY = "feature_summary" private const val FEATURE_SEARCH = "feature_search" + private const val FEATURE_OTHER = "feature_other" private val DEFAULT: HashMap = hashMapOf( FEATURE_ADVENTURE to false, @@ -78,6 +81,7 @@ class RemoteConfigRepository @Inject constructor() { FEATURE_QUESTS to false, FEATURE_SUMMARY to false, FEATURE_SEARCH to false, + FEATURE_OTHER to false, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/data/repository/preferences/PreferencesRepository.kt b/app/src/main/java/com/pixelized/rplexicon/data/repository/preferences/PreferencesRepository.kt new file mode 100644 index 0000000..4cb8962 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/data/repository/preferences/PreferencesRepository.kt @@ -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 = 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") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/edit/HandleHitPointEditDialog.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/edit/HandleHitPointEditDialog.kt index 87a40ec..51d952f 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/composable/edit/HandleHitPointEditDialog.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/composable/edit/HandleHitPointEditDialog.kt @@ -4,6 +4,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/images/BackgroundImage.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/images/BackgroundImage.kt index c9a05c2..b81c802 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/composable/images/BackgroundImage.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/composable/images/BackgroundImage.kt @@ -74,7 +74,7 @@ fun BackgroundImage( fun rememberSaturationFilter( saturation: Float = 0f, ): ColorFilter { - return remember { + return remember(saturation) { ColorFilter.colorMatrix( ColorMatrix().also { it.setToSaturation(saturation) } ) @@ -86,7 +86,7 @@ fun rememberBackgroundGradient( vararg gradients: Float, ): Brush { val colorScheme = MaterialTheme.colorScheme - return remember { + return remember(colorScheme) { Brush.verticalGradient( colors = gradients.map { colorScheme.surface.copy(alpha = it) } ) @@ -99,7 +99,7 @@ fun rememberBackgroundGradient( to: Float = 1.0f, ): Brush { val colorScheme = MaterialTheme.colorScheme - return remember { + return remember(colorScheme) { Brush.verticalGradient( colors = listOf( colorScheme.surface.copy(alpha = from), diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/rememberAnimatedShadow.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/rememberAnimatedShadow.kt index 0689e32..0ff30b4 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/composable/rememberAnimatedShadow.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/composable/rememberAnimatedShadow.kt @@ -1,6 +1,7 @@ package com.pixelized.rplexicon.ui.composable import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.runtime.Composable @@ -50,4 +51,25 @@ fun rememberAnimatedShadow( targetValue = shadowTarget.value, label = "animated shadow", ) +} + +@Composable +fun rememberAnimatedShadow( + scrollState: ScrollState, + rest: Dp = 0.dp, + target: Dp = 4.dp, +): State { + val shadowTarget = remember(scrollState) { + derivedStateOf { + if (scrollState.value > 0) { + target + } else { + rest + } + } + } + return animateDpAsState( + targetValue = shadowTarget.value, + label = "animated shadow", + ) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/ScreenNavHost.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/ScreenNavHost.kt index 1dfc751..7466a63 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/ScreenNavHost.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/ScreenNavHost.kt @@ -10,9 +10,9 @@ import androidx.navigation.NavOptionsBuilder import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController 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.composableAdventureChapters +import com.pixelized.rplexicon.ui.navigation.screens.composableAdventureDetail import com.pixelized.rplexicon.ui.navigation.screens.composableAuthentication import com.pixelized.rplexicon.ui.navigation.screens.composableCharacterSheet 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.composableQuests 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.composableSummary import com.pixelized.rplexicon.ui.navigation.screens.navigateToLanding @@ -66,6 +67,7 @@ fun ScreenNavHost( composableAdventureBooks() composableAdventureChapters() composableAdventureDetail() + composableSettings() } } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableSettings.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableSettings.kt new file mode 100644 index 0000000..ddac6fd --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableSettings.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt index 968b454..8ebb5e7 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt @@ -32,6 +32,7 @@ import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment 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.window.Dialog import androidx.credentials.exceptions.GetCredentialCancellationException -import androidx.credentials.exceptions.GetCredentialProviderConfigurationException import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.LocalActivity 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.utilitary.sensor.Gyroscope import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import kotlin.math.E import kotlin.math.max import kotlin.math.min @@ -90,6 +91,7 @@ fun AuthenticationScreen( val context = LocalContext.current val snack = LocalSnack.current val activity = LocalActivity.current + val scope = rememberCoroutineScope() Surface { PartyBackground() @@ -101,7 +103,9 @@ fun AuthenticationScreen( .padding(all = 16.dp), version = versionVM.version, onGoogleSignIn = { - authenticationVM.signIn(activity = activity) + scope.launch { + authenticationVM.signIn(activity = activity) + } }, ) @@ -344,7 +348,7 @@ private fun animatedWeight( @Composable private fun rememberBackgroundGradient(): Brush { val colorScheme = MaterialTheme.colorScheme - return remember { + return remember(colorScheme) { Brush.verticalGradient( colors = listOf( colorScheme.surface.copy(alpha = 0.25f), diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationViewModel.kt index 235fa8b..0b8b500 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationViewModel.kt @@ -36,8 +36,8 @@ class AuthenticationViewModel @Inject constructor( AuthenticationStateUio.Initial ) - fun signIn(activity: Activity) { - viewModelScope.launch(Dispatchers.IO) { + suspend fun signIn(activity: Activity) { + withContext(Dispatchers.IO) { try { // create the credential manager 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 + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt index 7b2ab19..4f82d7b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt @@ -58,7 +58,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.LocalRollOverlay import com.pixelized.rplexicon.NO_WINDOW_INSETS 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.Loader import com.pixelized.rplexicon.ui.composable.edit.HandleHitPointEditDialog diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingItem.kt index 9a1dd7f..2dafc4e 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingItem.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingItem.kt @@ -58,6 +58,7 @@ data class LandingItemUio( QUEST, MAP, ADVENTURE, + OTHERS, } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingScreen.kt index af06096..cf1b25b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingScreen.kt @@ -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.navigateToQuestList 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.screens.authentication.VersionViewModel import com.pixelized.rplexicon.ui.screens.landing.LandingItemUio.Feature.ADVENTURE @@ -95,6 +96,7 @@ fun LandingScreen( characters = viewModel.characterSheets, tools = viewModel.toolFeatures, encyclopedia = viewModel.lexiconFeatures, + others = viewModel.otherFeatures, version = versionVM.version, onFeature = { when (it.feature) { @@ -130,6 +132,10 @@ fun LandingScreen( ADVENTURE -> { screen.navigateToAdventures() } + + LandingItemUio.Feature.OTHERS -> { + screen.navigateToSettings() + } } } ) @@ -150,6 +156,7 @@ private fun LandingContent( characters: State>>, tools: State>>, encyclopedia: State>>, + others: State>>, version: VersionViewModel.Version, 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( modifier = Modifier .align(alignment = Alignment.End) @@ -383,8 +428,8 @@ private fun Modifier.magic(): Modifier = composed { } @Composable -@Preview(uiMode = UI_MODE_NIGHT_NO, heightDp = 1332) -@Preview(uiMode = UI_MODE_NIGHT_YES, heightDp = 1332) +@Preview(uiMode = UI_MODE_NIGHT_NO, heightDp = 1452) +@Preview(uiMode = UI_MODE_NIGHT_YES, heightDp = 1452) private fun LandingPreview() { LexiconTheme { 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), onFeature = { }, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingViewModel.kt index 8ebacd3..4cd4d2b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingViewModel.kt @@ -137,4 +137,30 @@ class LandingViewModel @Inject constructor( } }.collectAsState(initial = emptyList()) } + + val otherFeatures: State>> + @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()) + } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt index 263038b..c693afc 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt @@ -15,6 +15,7 @@ import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box 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.ThrowsCardUio 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.utilitary.extentions.lexicon import kotlinx.coroutines.launch @@ -98,6 +100,7 @@ fun RollOverlay( RollOverlayContent( modifier = Modifier.fillMaxSize(), + isDarkTheme = isInDarkTheme(), drawer = drawer, dice = viewModel.dice, card = viewModel.card, @@ -145,6 +148,7 @@ fun RollOverlay( @Composable private fun RollOverlayContent( modifier: Modifier = Modifier, + isDarkTheme: Boolean, drawer: DrawerState, dice: State, card: State, @@ -321,6 +325,7 @@ private fun RollOverlayContent( modifier = Modifier .padding(bottom = if (enableDrawer.value) 32.dp else 0.dp) .padding(all = 16.dp), + isDarkTheme = isDarkTheme, throws = it, showDetail = showDetail, onClick = onCard, @@ -358,6 +363,7 @@ private fun RollOverlayPreview( Surface { RollOverlayContent( modifier = Modifier.fillMaxSize(), + isDarkTheme = isSystemInDarkTheme(), drawer = rememberDrawerState(initialValue = preview.drawer), dice = preview.dice, card = preview.card, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/ThrowsCard.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/ThrowsCard.kt index cac2352..6a48f5b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/ThrowsCard.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/ThrowsCard.kt @@ -11,7 +11,6 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column 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.rememberTimeState 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.utilitary.extentions.annotatedSpan import com.pixelized.rplexicon.utilitary.extentions.lexicon @@ -98,12 +98,12 @@ data class ThrowsCardUio( @Composable fun ThrowsCard( modifier: Modifier = Modifier, + isDarkTheme: Boolean = isInDarkTheme(), throws: ThrowsCardUio, showDetail: State, onClick: () -> Unit, ) { val density = LocalDensity.current - val isDarkMode = isSystemInDarkTheme() val colorScheme = MaterialTheme.colorScheme val typography = MaterialTheme.typography val highlight = remember { SpanStyle(color = colorScheme.primary) } @@ -118,10 +118,13 @@ fun ThrowsCard( Surface( modifier = modifier .fillMaxWidth() - .ddBorder(inner = inner, outline = remember { CutCornerShape(size = 16.dp) }) + .ddBorder( + inner = inner, + outline = remember { CutCornerShape(size = 16.dp) }, + ) .clip(shape = inner) .clickable(onClick = onClick), - tonalElevation = if (isDarkMode) 4.dp else 0.dp, + tonalElevation = if (isDarkTheme) 4.dp else 0.dp, ) { Column( modifier = Modifier diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/SettingsScreen.kt new file mode 100644 index 0000000..24d0a5e --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/SettingsScreen.kt @@ -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 = {}, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/composable/ButtonPreference.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/composable/ButtonPreference.kt new file mode 100644 index 0000000..60189e4 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/composable/ButtonPreference.kt @@ -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 = {}, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/composable/SwitchPreference.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/composable/SwitchPreference.kt new file mode 100644 index 0000000..093dfb0 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/composable/SwitchPreference.kt @@ -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 = { }, + ) + } + } +} diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/theme/Theme.kt b/app/src/main/java/com/pixelized/rplexicon/ui/theme/Theme.kt index e447206..8c1f85f 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/theme/Theme.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/theme/Theme.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalDensity @@ -40,12 +41,12 @@ data class LexiconTheme( @Composable fun LexiconTheme( - darkTheme: Boolean = isSystemInDarkTheme(), + isDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { val density = LocalDensity.current - val lexiconTheme = remember(density) { - val colorScheme = when (darkTheme) { + val lexiconTheme = remember(density, isDarkTheme) { + val colorScheme = when (isDarkTheme) { true -> darkColorScheme() else -> lightColorScheme() } @@ -58,29 +59,29 @@ fun LexiconTheme( ) } - val view = LocalView.current - if (!view.isInEditMode) { - SideEffect { - val window = (view.context as Activity).window - window.statusBarColor = lexiconTheme.colorScheme.status.toArgb() - window.navigationBarColor = lexiconTheme.colorScheme.navigation.toArgb() - WindowCompat.getInsetsController(window, view).let { - it.isAppearanceLightStatusBars = !darkTheme - it.isAppearanceLightNavigationBars = !darkTheme - } - } - } + CompositionLocalProvider( + LocalLexiconTheme provides lexiconTheme, + ) { + val theme = LocalLexiconTheme.current + val view = LocalView.current - MaterialTheme( - colorScheme = lexiconTheme.colorScheme.base, - shapes = lexiconTheme.shapes.base, - typography = lexiconTheme.typography.base, - content = { - CompositionLocalProvider( - LocalLexiconTheme provides lexiconTheme, - ) { - content() + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = theme.colorScheme.status.toArgb() + window.navigationBarColor = theme.colorScheme.navigation.toArgb() + WindowCompat.getInsetsController(window, view).let { + it.isAppearanceLightStatusBars = !isDarkTheme + it.isAppearanceLightNavigationBars = !isDarkTheme + } } } - ) + + MaterialTheme( + colorScheme = theme.colorScheme.base, + typography = theme.typography.base, + shapes = theme.shapes.base, + content = content, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/modifier/ModifierEx.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/modifier/ModifierEx.kt index 24e2dfc..96b7f8b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/modifier/ModifierEx.kt +++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/modifier/ModifierEx.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTransformGestures import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding @@ -43,6 +42,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.google.accompanist.placeholder.PlaceholderHighlight import com.google.accompanist.placeholder.placeholder +import com.pixelized.rplexicon.isInDarkTheme import com.pixelized.rplexicon.utilitary.extentions.lexicon fun Modifier.placeholder( @@ -114,7 +114,7 @@ fun Modifier.ddBorder( inner: Shape, innerWidth: Dp = 1.dp, ): Modifier = composed { - val isDarkTheme = isSystemInDarkTheme() + val isDarkTheme = isInDarkTheme() val elevation = remember { derivedStateOf { if (isDarkTheme) 2.dp else 0.dp } } val colorScheme = MaterialTheme.lexicon.colorScheme this then Modifier @@ -147,7 +147,8 @@ fun Modifier.ddBorder( fun Modifier.lexiconShadow(): Modifier { return this then composed { - if (isSystemInDarkTheme()) { + val isDarkTheme = isInDarkTheme() + if (isDarkTheme) { val color = DividerDefaults.color drawWithContent { drawContent() diff --git a/app/src/main/res/drawable/icbg_item_foundry_misc_gear_a.png b/app/src/main/res/drawable/icbg_item_foundry_misc_gear_a.png new file mode 100644 index 0000000..72b9040 Binary files /dev/null and b/app/src/main/res/drawable/icbg_item_foundry_misc_gear_a.png differ diff --git a/app/src/main/res/drawable/icbg_portrait_of_a_tiefling_unfaded.png b/app/src/main/res/drawable/icbg_portrait_of_a_tiefling_unfaded.png new file mode 100644 index 0000000..99f61b9 Binary files /dev/null and b/app/src/main/res/drawable/icbg_portrait_of_a_tiefling_unfaded.png differ diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f355a7e..712b73f 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -239,6 +239,14 @@ Feuilles de personnage Encyclopédie Outils + Autre Histoires & Péripéties + + Paramêtres + Theme sombre + Utilise un fond noir pour économiser la batterie. + Authentification + Déconnectez vous de votre compte google. + Se déconnectez \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b47864f..da354c3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,6 +50,7 @@ Character\'s sheets Tools Encyclopedia + Other Brulkhai Léandre Nelia @@ -246,4 +247,11 @@ Game Master Stories & Adventures + + Settings + Dark theme + Use black background to save battery. + Authentication + Logout from your google account. + Logout \ No newline at end of file