Browse Source

Refactor onboarding steps

(cherry picked from commit 2ca3ab077192a7e5e2e7a5fb00c303a5a633372e)
Ivan Iskandar 1 năm trước cách đây
mục cha
commit
65e1e2cf4f

+ 30 - 26
app/src/main/java/eu/kanade/presentation/more/onboarding/GuidesStep.kt

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

+ 10 - 20
app/src/main/java/eu/kanade/presentation/more/onboarding/OnboardingScreen.kt

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

+ 11 - 0
app/src/main/java/eu/kanade/presentation/more/onboarding/OnboardingStep.kt

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

+ 50 - 30
app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt

@@ -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)
-
-    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),
-            ),
-        )
-
-        Button(
-            modifier = Modifier.fillMaxWidth(),
-            onClick = {
-                try {
-                    pickStorageLocation.launch(null)
-                } catch (e: ActivityNotFoundException) {
-                    context.toast(MR.strings.file_picker_error)
-                }
-            },
+internal class StorageStep : OnboardingStep {
+
+    private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory()
+
+    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() }
         }
     }
 }

+ 34 - 27
app/src/main/java/eu/kanade/presentation/more/onboarding/ThemeStep.kt

@@ -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()
-
-    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) },
-        )
+internal class ThemeStep : OnboardingStep {
+
+    override val isComplete: Boolean = true
+
+    private val uiPreferences: UiPreferences = Injekt.get()
+
+    @Composable
+    override fun Content() {
+        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) },
+            )
+        }
     }
 }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

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

+ 0 - 6
app/src/main/java/eu/kanade/tachiyomi/ui/more/OnboardingScreen.kt

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

+ 4 - 1
presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt

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