Add basic onboarding screen (#10199)

This commit is contained in:
arkon 2023-12-09 16:50:02 -05:00 committed by GitHub
parent ab9a26f6bd
commit 8b57169e92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 343 additions and 67 deletions

View File

@ -24,6 +24,8 @@ class BasePreferences(
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType) fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
enum class ExtensionInstaller(val titleRes: StringResource) { enum class ExtensionInstaller(val titleRes: StringResource) {
LEGACY(MR.strings.ext_installer_legacy), LEGACY(MR.strings.ext_installer_legacy),
PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller), PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller),

View File

@ -0,0 +1,68 @@
package eu.kanade.presentation.more.onboarding
import androidx.compose.animation.AnimatedContent
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.RocketLaunch
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import eu.kanade.domain.ui.UiPreferences
import soup.compose.material.motion.animation.materialSharedAxisX
import soup.compose.material.motion.animation.rememberSlideDistance
import tachiyomi.domain.storage.service.StoragePreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.InfoScreen
@Composable
fun OnboardingScreen(
storagePreferences: StoragePreferences,
uiPreferences: UiPreferences,
onComplete: () -> Unit,
) {
var currentStep by remember { mutableIntStateOf(0) }
val steps: List<@Composable () -> Unit> = listOf(
{ ThemeStep(uiPreferences = uiPreferences) },
{ StorageStep(storagePref = storagePreferences.baseStorageDirectory()) },
// TODO: prompt for notification permissions when bumping target to Android 13
)
val isLastStep = currentStep == steps.size - 1
val slideDistance = rememberSlideDistance()
InfoScreen(
icon = Icons.Outlined.RocketLaunch,
headingText = stringResource(MR.strings.onboarding_heading),
subtitleText = stringResource(MR.strings.onboarding_description),
acceptText = stringResource(
if (isLastStep) {
MR.strings.onboarding_action_finish
} else {
MR.strings.onboarding_action_next
},
),
onAcceptClick = {
if (!isLastStep) {
currentStep++
} else {
onComplete()
}
},
rejectText = stringResource(MR.strings.onboarding_action_skip),
onRejectClick = onComplete,
) {
AnimatedContent(
targetState = currentStep,
transitionSpec = {
materialSharedAxisX(
forward = true,
slideDistance = slideDistance,
)
},
label = "stepContent",
) {
steps[it]()
}
}
}

View File

@ -0,0 +1,41 @@
package eu.kanade.presentation.more.onboarding
import android.content.ActivityNotFoundException
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.more.settings.screen.SettingsDataScreen
import eu.kanade.tachiyomi.util.system.toast
import tachiyomi.core.preference.Preference
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Button
import tachiyomi.presentation.core.i18n.stringResource
@Composable
internal fun StorageStep(
storagePref: Preference<String>,
) {
val context = LocalContext.current
val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref)
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(stringResource(MR.strings.onboarding_storage_info))
Button(
onClick = {
try {
pickStorageLocation.launch(null)
} catch (e: ActivityNotFoundException) {
context.toast(MR.strings.file_picker_error)
}
},
) {
Text(SettingsDataScreen.storageLocationText(storagePref))
}
}
}

View File

@ -0,0 +1,40 @@
package eu.kanade.presentation.more.onboarding
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
import tachiyomi.presentation.core.util.collectAsState
@Composable
internal fun ThemeStep(
uiPreferences: UiPreferences,
) {
val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme()
val appTheme by appThemePref.collectAsState()
val amoledPref = uiPreferences.themeDarkAmoled()
val amoled by amoledPref.collectAsState()
Column {
AppThemeModePreferenceWidget(
value = themeMode,
onItemClick = {
themeModePref.set(it)
setAppCompatDelegateThemeMode(it)
},
)
AppThemePreferenceWidget(
value = appTheme,
amoled = amoled,
onItemClick = { appThemePref.set(it) },
)
}
}

View File

@ -43,6 +43,7 @@ import eu.kanade.tachiyomi.network.PREF_DOH_NJALLA
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101 import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9 import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9
import eu.kanade.tachiyomi.network.PREF_DOH_SHECAN import eu.kanade.tachiyomi.network.PREF_DOH_SHECAN
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
import eu.kanade.tachiyomi.util.CrashLogUtil import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.system.isPreviewBuildType import eu.kanade.tachiyomi.util.system.isPreviewBuildType
import eu.kanade.tachiyomi.util.system.isReleaseBuildType import eu.kanade.tachiyomi.util.system.isReleaseBuildType
@ -110,6 +111,10 @@ object SettingsAdvancedScreen : SearchableSettings {
title = stringResource(MR.strings.pref_debug_info), title = stringResource(MR.strings.pref_debug_info),
onClick = { navigator.push(DebugInfoScreen()) }, onClick = { navigator.push(DebugInfoScreen()) },
), ),
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_onboarding_guide),
onClick = { navigator.push(OnboardingScreen()) },
),
), ),
) )
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

View File

@ -2,8 +2,8 @@ package eu.kanade.presentation.more.settings.screen
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.ReadOnlyComposable
@ -19,13 +19,11 @@ import eu.kanade.domain.ui.model.TabletUiMode
import eu.kanade.domain.ui.model.ThemeMode import eu.kanade.domain.ui.model.ThemeMode
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.merge
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import tachiyomi.core.i18n.stringResource import tachiyomi.core.i18n.stringResource
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
@ -43,72 +41,59 @@ object SettingsAppearanceScreen : SearchableSettings {
@Composable @Composable
override fun getPreferences(): List<Preference> { override fun getPreferences(): List<Preference> {
val context = LocalContext.current
val uiPreferences = remember { Injekt.get<UiPreferences>() } val uiPreferences = remember { Injekt.get<UiPreferences>() }
return listOf( return listOf(
getThemeGroup(context = context, uiPreferences = uiPreferences), getThemeGroup(uiPreferences = uiPreferences),
getDisplayGroup(context = context, uiPreferences = uiPreferences), getDisplayGroup(uiPreferences = uiPreferences),
) )
} }
@Composable @Composable
private fun getThemeGroup( private fun getThemeGroup(
context: Context,
uiPreferences: UiPreferences, uiPreferences: UiPreferences,
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val context = LocalContext.current
val themeModePref = uiPreferences.themeMode() val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState() val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme() val appThemePref = uiPreferences.appTheme()
val appTheme by appThemePref.collectAsState()
val amoledPref = uiPreferences.themeDarkAmoled() val amoledPref = uiPreferences.themeDarkAmoled()
val amoled by amoledPref.collectAsState() val amoled by amoledPref.collectAsState()
LaunchedEffect(themeMode) {
setAppCompatDelegateThemeMode(themeMode)
}
LaunchedEffect(Unit) {
merge(appThemePref.changes(), amoledPref.changes())
.drop(2)
.collectLatest { (context as? Activity)?.let { ActivityCompat.recreate(it) } }
}
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_theme), title = stringResource(MR.strings.pref_category_theme),
preferenceItems = listOf( preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
pref = themeModePref,
title = stringResource(MR.strings.pref_theme_mode),
entries = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mapOf(
ThemeMode.SYSTEM to stringResource(MR.strings.theme_system),
ThemeMode.LIGHT to stringResource(MR.strings.theme_light),
ThemeMode.DARK to stringResource(MR.strings.theme_dark),
)
} else {
mapOf(
ThemeMode.LIGHT to stringResource(MR.strings.theme_light),
ThemeMode.DARK to stringResource(MR.strings.theme_dark),
)
},
),
Preference.PreferenceItem.CustomPreference( Preference.PreferenceItem.CustomPreference(
title = stringResource(MR.strings.pref_app_theme), title = stringResource(MR.strings.pref_app_theme),
) { item -> ) {
val value by appThemePref.collectAsState() Column {
AppThemePreferenceWidget( AppThemeModePreferenceWidget(
title = item.title, value = themeMode,
value = value, onItemClick = {
amoled = amoled, themeModePref.set(it)
onItemClick = { appThemePref.set(it) }, setAppCompatDelegateThemeMode(it)
) },
)
AppThemePreferenceWidget(
value = appTheme,
amoled = amoled,
onItemClick = { appThemePref.set(it) },
)
}
}, },
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
pref = amoledPref, pref = amoledPref,
title = stringResource(MR.strings.pref_dark_theme_pure_black), title = stringResource(MR.strings.pref_dark_theme_pure_black),
enabled = themeMode != ThemeMode.LIGHT, enabled = themeMode != ThemeMode.LIGHT,
onValueChanged = {
(context as? Activity)?.let { ActivityCompat.recreate(it) }
true
},
), ),
), ),
) )
@ -116,9 +101,10 @@ object SettingsAppearanceScreen : SearchableSettings {
@Composable @Composable
private fun getDisplayGroup( private fun getDisplayGroup(
context: Context,
uiPreferences: UiPreferences, uiPreferences: UiPreferences,
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val context = LocalContext.current
val langs = remember { getLangs(context) } val langs = remember { getLangs(context) }
var currentLanguage by remember { var currentLanguage by remember {
mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "") mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "")

View File

@ -7,6 +7,7 @@ import android.net.Uri
import android.os.Environment import android.os.Environment
import android.text.format.Formatter import android.text.format.Formatter
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -80,13 +81,12 @@ object SettingsDataScreen : SearchableSettings {
} }
@Composable @Composable
private fun getStorageLocationPref( fun storageLocationPicker(
storagePreferences: StoragePreferences, storageDirPref: tachiyomi.core.preference.Preference<String>,
): Preference.PreferenceItem.TextPreference { ): ManagedActivityResultLauncher<Uri?, Uri?> {
val context = LocalContext.current val context = LocalContext.current
val storageDirPref = storagePreferences.baseStorageDirectory()
val storageDir by storageDirPref.collectAsState() return rememberLauncherForActivityResult(
val pickStorageLocation = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree(), contract = ActivityResultContracts.OpenDocumentTree(),
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
@ -101,13 +101,31 @@ object SettingsDataScreen : SearchableSettings {
Injekt.get<DownloadCache>().invalidateCache() Injekt.get<DownloadCache>().invalidateCache()
} }
} }
}
@Composable
fun storageLocationText(
storageDirPref: tachiyomi.core.preference.Preference<String>,
): String {
val context = LocalContext.current
val storageDir by storageDirPref.collectAsState()
return remember(storageDir) {
val file = UniFile.fromUri(context, storageDir.toUri())
file?.filePath ?: file?.uri?.toString()
} ?: stringResource(MR.strings.invalid_location, storageDir)
}
@Composable
private fun getStorageLocationPref(
storagePreferences: StoragePreferences,
): Preference.PreferenceItem.TextPreference {
val context = LocalContext.current
val pickStorageLocation = storageLocationPicker(storagePreferences.baseStorageDirectory())
return Preference.PreferenceItem.TextPreference( return Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_storage_location), title = stringResource(MR.strings.pref_storage_location),
subtitle = remember(storageDir) { subtitle = storageLocationText(storagePreferences.baseStorageDirectory()),
val file = UniFile.fromUri(context, storageDir.toUri())
file?.filePath ?: file?.uri?.toString()
} ?: stringResource(MR.strings.invalid_location, storageDir),
onClick = { onClick = {
try { try {
pickStorageLocation.launch(null) pickStorageLocation.launch(null)

View File

@ -0,0 +1,56 @@
package eu.kanade.presentation.more.settings.widget
import android.os.Build
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import eu.kanade.domain.ui.model.ThemeMode
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
private val options = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mapOf(
ThemeMode.SYSTEM to MR.strings.theme_system,
ThemeMode.LIGHT to MR.strings.theme_light,
ThemeMode.DARK to MR.strings.theme_dark,
)
} else {
mapOf(
ThemeMode.LIGHT to MR.strings.theme_light,
ThemeMode.DARK to MR.strings.theme_dark,
)
}
@Composable
internal fun AppThemeModePreferenceWidget(
value: ThemeMode,
onItemClick: (ThemeMode) -> Unit,
) {
BasePreferenceWidget(
subcomponent = {
MultiChoiceSegmentedButtonRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = PrefsHorizontalPadding),
) {
options.onEachIndexed { index, (mode, labelRes) ->
SegmentedButton(
checked = mode == value,
onCheckedChange = { onItemClick(mode) },
shape = SegmentedButtonDefaults.itemShape(
index,
options.size,
),
) {
Text(stringResource(labelRes))
}
}
}
},
)
}

View File

@ -1,5 +1,6 @@
package eu.kanade.presentation.more.settings.widget package eu.kanade.presentation.more.settings.widget
import android.app.Activity
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -36,9 +37,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import eu.kanade.domain.ui.model.AppTheme import eu.kanade.domain.ui.model.AppTheme
import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.presentation.theme.TachiyomiTheme
@ -51,13 +54,11 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
@Composable @Composable
internal fun AppThemePreferenceWidget( internal fun AppThemePreferenceWidget(
title: String,
value: AppTheme, value: AppTheme,
amoled: Boolean, amoled: Boolean,
onItemClick: (AppTheme) -> Unit, onItemClick: (AppTheme) -> Unit,
) { ) {
BasePreferenceWidget( BasePreferenceWidget(
title = title,
subcomponent = { subcomponent = {
AppThemesList( AppThemesList(
currentTheme = value, currentTheme = value,
@ -74,6 +75,7 @@ private fun AppThemesList(
amoled: Boolean, amoled: Boolean,
onItemClick: (AppTheme) -> Unit, onItemClick: (AppTheme) -> Unit,
) { ) {
val context = LocalContext.current
val appThemes = remember { val appThemes = remember {
AppTheme.entries AppTheme.entries
.filterNot { it.titleRes == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) } .filterNot { it.titleRes == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) }
@ -97,7 +99,10 @@ private fun AppThemesList(
) { ) {
AppThemePreviewItem( AppThemePreviewItem(
selected = currentTheme == appTheme, selected = currentTheme == appTheme,
onClick = { onItemClick(appTheme) }, onClick = {
onItemClick(appTheme)
(context as? Activity)?.let { ActivityCompat.recreate(it) }
},
) )
} }

View File

@ -126,12 +126,12 @@ object HomeScreen : Screen() {
materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) togetherWith materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) togetherWith
materialFadeThroughOut(durationMillis = TabFadeDuration) materialFadeThroughOut(durationMillis = TabFadeDuration)
}, },
content = { label = "tabContent",
tabNavigator.saveableState(key = "currentTab", it) { ) {
it.Content() tabNavigator.saveableState(key = "currentTab", it) {
} it.Content()
}, }
) }
} }
} }
} }

View File

@ -73,6 +73,7 @@ import eu.kanade.tachiyomi.ui.deeplink.DeepLinkScreen
import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.home.HomeScreen
import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim
import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.openInBrowser
@ -251,6 +252,7 @@ class MainActivity : BaseActivity() {
HandleOnNewIntent(context = context, navigator = navigator) HandleOnNewIntent(context = context, navigator = navigator)
CheckForUpdates() CheckForUpdates()
ShowOnboarding()
} }
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) } var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
@ -342,6 +344,17 @@ class MainActivity : BaseActivity() {
} }
} }
@Composable
private fun ShowOnboarding() {
val navigator = LocalNavigator.currentOrThrow
LaunchedEffect(Unit) {
if (!preferences.shownOnboardingFlow().get()) {
navigator.push(OnboardingScreen())
}
}
}
/** /**
* Sets custom splash screen exit animation on devices prior to Android 12. * Sets custom splash screen exit animation on devices prior to Android 12.
* *

View File

@ -0,0 +1,34 @@
package eu.kanade.tachiyomi.ui.more
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.more.onboarding.OnboardingScreen
import eu.kanade.presentation.util.Screen
import tachiyomi.domain.storage.service.StoragePreferences
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class OnboardingScreen : Screen() {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val basePreferences = remember { Injekt.get<BasePreferences>() }
val storagePreferences = remember { Injekt.get<StoragePreferences>() }
val uiPreferences = remember { Injekt.get<UiPreferences>() }
OnboardingScreen(
storagePreferences = storagePreferences,
uiPreferences = uiPreferences,
onComplete = {
basePreferences.shownOnboardingFlow().set(true)
navigator.pop()
},
)
}
}

View File

@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.stateIn
* Local-copy implementation of PreferenceStore mostly for test and preview purposes * Local-copy implementation of PreferenceStore mostly for test and preview purposes
*/ */
class InMemoryPreferenceStore( class InMemoryPreferenceStore(
private val initialPreferences: Sequence<InMemoryPreference<*>> = sequenceOf(), initialPreferences: Sequence<InMemoryPreference<*>> = sequenceOf(),
) : PreferenceStore { ) : PreferenceStore {
private val preferences: Map<String, Preference<*>> = private val preferences: Map<String, Preference<*>> =

View File

@ -173,6 +173,15 @@
<!-- Shortcuts--> <!-- Shortcuts-->
<string name="app_not_available">App not available</string> <string name="app_not_available">App not available</string>
<!-- Onboarding -->
<string name="pref_onboarding_guide">Onboarding guide</string>
<string name="onboarding_heading">Welcome!</string>
<string name="onboarding_description">Let\'s set some things up first. You can always change these in the settings later too.</string>
<string name="onboarding_action_next">Next</string>
<string name="onboarding_action_finish">Get started</string>
<string name="onboarding_action_skip">Skip</string>
<string name="onboarding_storage_info">Select a storage location where chapter downloads, backups, and local source content will be stored.</string>
<!-- Preferences --> <!-- Preferences -->
<!-- Subsections --> <!-- Subsections -->
<string name="pref_category_general">General</string> <string name="pref_category_general">General</string>
@ -196,11 +205,10 @@
<!-- General section --> <!-- General section -->
<string name="pref_category_theme">Theme</string> <string name="pref_category_theme">Theme</string>
<string name="pref_theme_mode">Dark mode</string>
<string name="theme_system">Follow system</string>
<string name="theme_light">Off</string>
<string name="theme_dark">On</string>
<string name="pref_app_theme">App theme</string> <string name="pref_app_theme">App theme</string>
<string name="theme_system">System</string>
<string name="theme_light">Light</string>
<string name="theme_dark">Dark</string>
<string name="theme_monet">Dynamic</string> <string name="theme_monet">Dynamic</string>
<string name="theme_greenapple">Green Apple</string> <string name="theme_greenapple">Green Apple</string>
<string name="theme_lavender">Lavender</string> <string name="theme_lavender">Lavender</string>