diff --git a/app/src/main/java/eu/kanade/presentation/more/onboarding/GuidesStep.kt b/app/src/main/java/eu/kanade/presentation/more/onboarding/GuidesStep.kt index 5899dae554..77e0e7b88b 100644 --- a/app/src/main/java/eu/kanade/presentation/more/onboarding/GuidesStep.kt +++ b/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 +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() } } diff --git a/app/src/main/java/eu/kanade/presentation/more/onboarding/OnboardingScreen.kt b/app/src/main/java/eu/kanade/presentation/more/onboarding/OnboardingScreen.kt index ef42a68fce..c7a3d85866 100644 --- a/app/src/main/java/eu/kanade/presentation/more/onboarding/OnboardingScreen.kt +++ b/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() } } } diff --git a/app/src/main/java/eu/kanade/presentation/more/onboarding/OnboardingStep.kt b/app/src/main/java/eu/kanade/presentation/more/onboarding/OnboardingStep.kt new file mode 100644 index 0000000000..81b0a9f915 --- /dev/null +++ b/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() +} diff --git a/app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt b/app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt index 062e5a7c99..74a1be4ae7 100644 --- a/app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt +++ b/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, -) { - 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().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() } } } } diff --git a/app/src/main/java/eu/kanade/presentation/more/onboarding/ThemeStep.kt b/app/src/main/java/eu/kanade/presentation/more/onboarding/ThemeStep.kt index 69951e0b53..dfd7517dcc 100644 --- a/app/src/main/java/eu/kanade/presentation/more/onboarding/ThemeStep.kt +++ b/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() +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) }, + ) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index d6750551bb..78c688c2ef 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/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()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/OnboardingScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/OnboardingScreen.kt index 72e0919548..038df42ca9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/OnboardingScreen.kt +++ b/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() } - val storagePreferences = remember { Injekt.get() } - val uiPreferences = remember { Injekt.get() } val finishOnboarding = { basePreferences.shownOnboardingFlow().set(true) @@ -29,8 +25,6 @@ class OnboardingScreen : Screen() { } OnboardingScreen( - storagePreferences = storagePreferences, - uiPreferences = uiPreferences, onComplete = { finishOnboarding() }, onRestoreBackup = { finishOnboarding() diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt index 46736d51e1..e3b65079f4 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt +++ b/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)