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.presentation.core.i18n.stringResource
@Composable
internal fun GuidesStep(
onRestoreBackup: () -> Unit,
) {
val handler = LocalUriHandler.current
internal class GuidesStep(
private val onRestoreBackup: () -> Unit,
) : OnboardingStep {
override val isComplete: Boolean = true
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(stringResource(MR.strings.onboarding_guides_new_user, stringResource(MR.strings.app_name)))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { handler.openUri(GETTING_STARTED_URL) },
@Composable
override fun Content() {
val handler = LocalUriHandler.current
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
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(
color = MaterialTheme.colorScheme.onPrimaryContainer,
)
HorizontalDivider(
color = MaterialTheme.colorScheme.onPrimaryContainer,
)
Text(stringResource(MR.strings.onboarding_guides_returning_user, stringResource(MR.strings.app_name)))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = onRestoreBackup,
) {
Text(stringResource(MR.strings.pref_restore_backup))
Text(stringResource(MR.strings.onboarding_guides_returning_user, stringResource(MR.strings.app_name)))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = onRestoreBackup,
) {
Text(stringResource(MR.strings.pref_restore_backup))
}
}
}
}
@ -57,6 +61,6 @@ private fun GuidesStepPreview() {
TachiyomiTheme {
GuidesStep(
onRestoreBackup = {},
)
).Content()
}
}

View File

@ -13,15 +13,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
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.rememberSlideDistance
import tachiyomi.domain.storage.service.StoragePreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
@ -29,24 +26,21 @@ import tachiyomi.presentation.core.screens.InfoScreen
@Composable
fun OnboardingScreen(
storagePreferences: StoragePreferences,
uiPreferences: UiPreferences,
onComplete: () -> Unit,
onRestoreBackup: () -> Unit,
) {
val context = LocalContext.current
val slideDistance = rememberSlideDistance()
var currentStep by remember { mutableIntStateOf(0) }
val steps: List<@Composable () -> Unit> = remember {
var currentStep by rememberSaveable { mutableIntStateOf(0) }
val steps = remember {
listOf(
{ ThemeStep(uiPreferences = uiPreferences) },
{ StorageStep(storagePref = storagePreferences.baseStorageDirectory()) },
ThemeStep(),
StorageStep(),
// 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-- })
@ -61,16 +55,12 @@ fun OnboardingScreen(
MR.strings.onboarding_action_next
},
),
canAccept = steps[currentStep].isComplete,
onAcceptClick = {
if (isLastStep) {
onComplete()
} else {
// TODO: this is kind of janky
if (currentStep == 1 && !storagePreferences.baseStorageDirectory().isSet()) {
context.toast(MR.strings.onboarding_storage_selection_required)
} else {
currentStep++
}
currentStep++
}
},
) {
@ -91,7 +81,7 @@ fun OnboardingScreen(
},
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.material3.Text
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.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 kotlinx.coroutines.flow.collectLatest
import tachiyomi.domain.storage.service.StoragePreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Button
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Composable
internal fun StorageStep(
storagePref: Preference<String>,
) {
val context = LocalContext.current
val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref)
internal class StorageStep : OnboardingStep {
Column(
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),
),
)
private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory()
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
try {
pickStorageLocation.launch(null)
} catch (e: ActivityNotFoundException) {
context.toast(MR.strings.file_picker_error)
}
},
private var _isComplete by mutableStateOf(false)
override val isComplete: Boolean
get() = _isComplete
@Composable
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.AppThemePreferenceWidget
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Composable
internal fun ThemeStep(
uiPreferences: UiPreferences,
) {
val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState()
internal class ThemeStep : OnboardingStep {
val appThemePref = uiPreferences.appTheme()
val appTheme by appThemePref.collectAsState()
override val isComplete: Boolean = true
val amoledPref = uiPreferences.themeDarkAmoled()
val amoled by amoledPref.collectAsState()
private val uiPreferences: UiPreferences = Injekt.get()
Column {
AppThemeModePreferenceWidget(
value = themeMode,
onItemClick = {
themeModePref.set(it)
setAppCompatDelegateThemeMode(it)
},
)
@Composable
override fun Content() {
val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState()
AppThemePreferenceWidget(
value = appTheme,
amoled = amoled,
onItemClick = { appThemePref.set(it) },
)
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

@ -349,7 +349,7 @@ class MainActivity : BaseActivity() {
val navigator = LocalNavigator.currentOrThrow
LaunchedEffect(Unit) {
if (!preferences.shownOnboardingFlow().get()) {
if (!preferences.shownOnboardingFlow().get() && navigator.lastItem !is 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.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 eu.kanade.tachiyomi.ui.setting.SettingsScreen
import tachiyomi.domain.storage.service.StoragePreferences
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -20,8 +18,6 @@ class OnboardingScreen : Screen() {
val navigator = LocalNavigator.currentOrThrow
val basePreferences = remember { Injekt.get<BasePreferences>() }
val storagePreferences = remember { Injekt.get<StoragePreferences>() }
val uiPreferences = remember { Injekt.get<UiPreferences>() }
val finishOnboarding = {
basePreferences.shownOnboardingFlow().set(true)
@ -29,8 +25,6 @@ class OnboardingScreen : Screen() {
}
OnboardingScreen(
storagePreferences = storagePreferences,
uiPreferences = uiPreferences,
onComplete = { finishOnboarding() },
onRestoreBackup = {
finishOnboarding()

View File

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