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,34 +17,38 @@ 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
@Composable internal class GuidesStep(
internal fun GuidesStep( private val onRestoreBackup: () -> Unit,
onRestoreBackup: () -> Unit, ) : OnboardingStep {
) { override val isComplete: Boolean = true
val handler = LocalUriHandler.current
Column( @Composable
modifier = Modifier.padding(16.dp), override fun Content() {
verticalArrangement = Arrangement.spacedBy(8.dp), val handler = LocalUriHandler.current
) {
Text(stringResource(MR.strings.onboarding_guides_new_user, stringResource(MR.strings.app_name))) Column(
Button( modifier = Modifier.padding(16.dp),
modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp),
onClick = { handler.openUri(GETTING_STARTED_URL) },
) { ) {
Text(stringResource(MR.strings.getting_started_guide)) Text(stringResource(MR.strings.onboarding_guides_new_user, stringResource(MR.strings.app_name)))
} Button(
modifier = Modifier.fillMaxWidth(),
onClick = { handler.openUri(GETTING_STARTED_URL) },
) {
Text(stringResource(MR.strings.getting_started_guide))
}
HorizontalDivider( HorizontalDivider(
color = MaterialTheme.colorScheme.onPrimaryContainer, color = MaterialTheme.colorScheme.onPrimaryContainer,
) )
Text(stringResource(MR.strings.onboarding_guides_returning_user, stringResource(MR.strings.app_name))) Text(stringResource(MR.strings.onboarding_guides_returning_user, stringResource(MR.strings.app_name)))
Button( Button(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
onClick = onRestoreBackup, onClick = onRestoreBackup,
) { ) {
Text(stringResource(MR.strings.pref_restore_backup)) Text(stringResource(MR.strings.pref_restore_backup))
}
} }
} }
} }
@ -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,16 +55,12 @@ 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 { } else {
// TODO: this is kind of janky currentStep++
if (currentStep == 1 && !storagePreferences.baseStorageDirectory().isSet()) {
context.toast(MR.strings.onboarding_storage_selection_required)
} else {
currentStep++
}
} }
}, },
) { ) {
@ -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,46 +7,66 @@ 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
@Composable internal class StorageStep : OnboardingStep {
internal fun StorageStep(
storagePref: Preference<String>,
) {
val context = LocalContext.current
val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref)
Column( private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory()
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
stringResource(
MR.strings.onboarding_storage_info,
stringResource(MR.strings.app_name),
SettingsDataScreen.storageLocationText(storagePref),
),
)
Button( private var _isComplete by mutableStateOf(false)
modifier = Modifier.fillMaxWidth(),
onClick = { override val isComplete: Boolean
try { get() = _isComplete
pickStorageLocation.launch(null)
} catch (e: ActivityNotFoundException) { @Composable
context.toast(MR.strings.file_picker_error) override fun Content() {
} val context = LocalContext.current
}, val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref)
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
Text(stringResource(MR.strings.onboarding_storage_action_select)) Text(
stringResource(
MR.strings.onboarding_storage_info,
stringResource(MR.strings.app_name),
SettingsDataScreen.storageLocationText(storagePref),
),
)
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
try {
pickStorageLocation.launch(null)
} catch (e: ActivityNotFoundException) {
context.toast(MR.strings.file_picker_error)
}
},
) {
Text(stringResource(MR.strings.onboarding_storage_action_select))
}
}
LaunchedEffect(Unit) {
storagePref.changes()
.collectLatest { _isComplete = storagePref.isSet() }
} }
} }
} }

View file

@ -8,33 +8,40 @@ 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
@Composable internal class ThemeStep : OnboardingStep {
internal fun ThemeStep(
uiPreferences: UiPreferences,
) {
val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme() override val isComplete: Boolean = true
val appTheme by appThemePref.collectAsState()
val amoledPref = uiPreferences.themeDarkAmoled() private val uiPreferences: UiPreferences = Injekt.get()
val amoled by amoledPref.collectAsState()
Column { @Composable
AppThemeModePreferenceWidget( override fun Content() {
value = themeMode, val themeModePref = uiPreferences.themeMode()
onItemClick = { val themeMode by themeModePref.collectAsState()
themeModePref.set(it)
setAppCompatDelegateThemeMode(it)
},
)
AppThemePreferenceWidget( val appThemePref = uiPreferences.appTheme()
value = appTheme, val appTheme by appThemePref.collectAsState()
amoled = amoled,
onItemClick = { appThemePref.set(it) }, 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

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