Refactor onboarding steps

(cherry picked from commit 2ca3ab077192a7e5e2e7a5fb00c303a5a633372e)
This commit is contained in:
Ivan Iskandar 2023-12-16 22:48:34 +07:00 committed by arkon
parent e36a2c68f1
commit 65e1e2cf4f
8 changed files with 133 additions and 104 deletions

View File

@ -17,10 +17,13 @@ import eu.kanade.presentation.theme.TachiyomiTheme
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
internal class GuidesStep(
private val onRestoreBackup: () -> Unit,
) : OnboardingStep {
override val isComplete: Boolean = true
@Composable @Composable
internal fun GuidesStep( override fun Content() {
onRestoreBackup: () -> Unit,
) {
val handler = LocalUriHandler.current val handler = LocalUriHandler.current
Column( Column(
@ -48,6 +51,7 @@ internal fun GuidesStep(
} }
} }
} }
}
const val GETTING_STARTED_URL = "https://tachiyomi.org/docs/guides/getting-started" const val GETTING_STARTED_URL = "https://tachiyomi.org/docs/guides/getting-started"
@ -57,6 +61,6 @@ private fun GuidesStepPreview() {
TachiyomiTheme { TachiyomiTheme {
GuidesStep( GuidesStep(
onRestoreBackup = {}, onRestoreBackup = {},
) ).Content()
} }
} }

View File

@ -13,15 +13,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.util.system.toast
import soup.compose.material.motion.animation.materialSharedAxisX import soup.compose.material.motion.animation.materialSharedAxisX
import soup.compose.material.motion.animation.rememberSlideDistance import soup.compose.material.motion.animation.rememberSlideDistance
import tachiyomi.domain.storage.service.StoragePreferences
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@ -29,24 +26,21 @@ import tachiyomi.presentation.core.screens.InfoScreen
@Composable @Composable
fun OnboardingScreen( fun OnboardingScreen(
storagePreferences: StoragePreferences,
uiPreferences: UiPreferences,
onComplete: () -> Unit, onComplete: () -> Unit,
onRestoreBackup: () -> Unit, onRestoreBackup: () -> Unit,
) { ) {
val context = LocalContext.current
val slideDistance = rememberSlideDistance() val slideDistance = rememberSlideDistance()
var currentStep by remember { mutableIntStateOf(0) } var currentStep by rememberSaveable { mutableIntStateOf(0) }
val steps: List<@Composable () -> Unit> = remember { val steps = remember {
listOf( listOf(
{ ThemeStep(uiPreferences = uiPreferences) }, ThemeStep(),
{ StorageStep(storagePref = storagePreferences.baseStorageDirectory()) }, StorageStep(),
// TODO: prompt for notification permissions when bumping target to Android 13 // TODO: prompt for notification permissions when bumping target to Android 13
{ GuidesStep(onRestoreBackup = onRestoreBackup) }, GuidesStep(onRestoreBackup = onRestoreBackup),
) )
} }
val isLastStep = currentStep == steps.size - 1 val isLastStep = currentStep == steps.lastIndex
BackHandler(enabled = currentStep != 0, onBack = { currentStep-- }) BackHandler(enabled = currentStep != 0, onBack = { currentStep-- })
@ -61,17 +55,13 @@ fun OnboardingScreen(
MR.strings.onboarding_action_next MR.strings.onboarding_action_next
}, },
), ),
canAccept = steps[currentStep].isComplete,
onAcceptClick = { onAcceptClick = {
if (isLastStep) { if (isLastStep) {
onComplete() onComplete()
} else {
// TODO: this is kind of janky
if (currentStep == 1 && !storagePreferences.baseStorageDirectory().isSet()) {
context.toast(MR.strings.onboarding_storage_selection_required)
} else { } else {
currentStep++ currentStep++
} }
}
}, },
) { ) {
Box( Box(
@ -91,7 +81,7 @@ fun OnboardingScreen(
}, },
label = "stepContent", label = "stepContent",
) { ) {
steps[it]() steps[it].Content()
} }
} }
} }

View File

@ -0,0 +1,11 @@
package eu.kanade.presentation.more.onboarding
import androidx.compose.runtime.Composable
internal interface OnboardingStep {
val isComplete: Boolean
@Composable
fun Content()
}

View File

@ -7,20 +7,34 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.more.settings.screen.SettingsDataScreen import eu.kanade.presentation.more.settings.screen.SettingsDataScreen
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import tachiyomi.core.preference.Preference import kotlinx.coroutines.flow.collectLatest
import tachiyomi.domain.storage.service.StoragePreferences
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Button import tachiyomi.presentation.core.components.material.Button
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
internal class StorageStep : OnboardingStep {
private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory()
private var _isComplete by mutableStateOf(false)
override val isComplete: Boolean
get() = _isComplete
@Composable @Composable
internal fun StorageStep( override fun Content() {
storagePref: Preference<String>,
) {
val context = LocalContext.current val context = LocalContext.current
val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref) val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref)
@ -49,4 +63,10 @@ internal fun StorageStep(
Text(stringResource(MR.strings.onboarding_storage_action_select)) Text(stringResource(MR.strings.onboarding_storage_action_select))
} }
} }
LaunchedEffect(Unit) {
storagePref.changes()
.collectLatest { _isComplete = storagePref.isSet() }
}
}
} }

View File

@ -8,11 +8,17 @@ import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget 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 tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
internal class ThemeStep : OnboardingStep {
override val isComplete: Boolean = true
private val uiPreferences: UiPreferences = Injekt.get()
@Composable @Composable
internal fun ThemeStep( override fun Content() {
uiPreferences: UiPreferences,
) {
val themeModePref = uiPreferences.themeMode() val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState() val themeMode by themeModePref.collectAsState()
@ -38,3 +44,4 @@ internal fun ThemeStep(
) )
} }
} }
}

View File

@ -349,7 +349,7 @@ class MainActivity : BaseActivity() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (!preferences.shownOnboardingFlow().get()) { if (!preferences.shownOnboardingFlow().get() && navigator.lastItem !is OnboardingScreen) {
navigator.push(OnboardingScreen()) navigator.push(OnboardingScreen())
} }
} }

View File

@ -5,11 +5,9 @@ import androidx.compose.runtime.remember
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.more.onboarding.OnboardingScreen import eu.kanade.presentation.more.onboarding.OnboardingScreen
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.ui.setting.SettingsScreen import eu.kanade.tachiyomi.ui.setting.SettingsScreen
import tachiyomi.domain.storage.service.StoragePreferences
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -20,8 +18,6 @@ class OnboardingScreen : Screen() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val basePreferences = remember { Injekt.get<BasePreferences>() } val basePreferences = remember { Injekt.get<BasePreferences>() }
val storagePreferences = remember { Injekt.get<StoragePreferences>() }
val uiPreferences = remember { Injekt.get<UiPreferences>() }
val finishOnboarding = { val finishOnboarding = {
basePreferences.shownOnboardingFlow().set(true) basePreferences.shownOnboardingFlow().set(true)
@ -29,8 +25,6 @@ class OnboardingScreen : Screen() {
} }
OnboardingScreen( OnboardingScreen(
storagePreferences = storagePreferences,
uiPreferences = uiPreferences,
onComplete = { finishOnboarding() }, onComplete = { finishOnboarding() },
onRestoreBackup = { onRestoreBackup = {
finishOnboarding() finishOnboarding()

View File

@ -13,6 +13,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Newspaper import androidx.compose.material.icons.outlined.Newspaper
import androidx.compose.material3.Button
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarDefaults import androidx.compose.material3.NavigationBarDefaults
@ -38,6 +39,7 @@ fun InfoScreen(
subtitleText: String, subtitleText: String,
acceptText: String, acceptText: String,
onAcceptClick: () -> Unit, onAcceptClick: () -> Unit,
canAccept: Boolean = true,
rejectText: String? = null, rejectText: String? = null,
onRejectClick: (() -> Unit)? = null, onRejectClick: (() -> Unit)? = null,
content: @Composable ColumnScope.() -> Unit, content: @Composable ColumnScope.() -> Unit,
@ -63,8 +65,9 @@ fun InfoScreen(
vertical = MaterialTheme.padding.small, vertical = MaterialTheme.padding.small,
), ),
) { ) {
androidx.compose.material3.Button( Button(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
enabled = canAccept,
onClick = onAcceptClick, onClick = onAcceptClick,
) { ) {
Text(text = acceptText) Text(text = acceptText)