diff --git a/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt b/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt index f90aeb252c..5504f13cc6 100644 --- a/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt @@ -2,7 +2,7 @@ package eu.kanade.presentation.crash import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.BugReport @@ -47,7 +47,7 @@ fun CrashScreen( modifier = Modifier .padding(vertical = MaterialTheme.padding.small) .clip(MaterialTheme.shapes.small) - .fillMaxWidth() + .fillMaxSize() .background(MaterialTheme.colorScheme.surfaceVariant), ) { Text( 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 new file mode 100644 index 0000000000..8976a00d28 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/onboarding/GuidesStep.kt @@ -0,0 +1,47 @@ +package eu.kanade.presentation.more.onboarding + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.unit.dp +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)) + } + + HorizontalDivider() + + 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)) + } + } +} + +const val GETTING_STARTED_URL = "https://tachiyomi.org/docs/guides/getting-started" 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 0bfdfbb93e..7facba4033 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 @@ -1,18 +1,27 @@ package eu.kanade.presentation.more.onboarding +import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.RocketLaunch +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import eu.kanade.domain.ui.UiPreferences 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 import tachiyomi.presentation.core.screens.InfoScreen @@ -21,16 +30,20 @@ fun OnboardingScreen( storagePreferences: StoragePreferences, uiPreferences: UiPreferences, onComplete: () -> Unit, + onRestoreBackup: () -> Unit, ) { var currentStep by remember { mutableIntStateOf(0) } val steps: List<@Composable () -> Unit> = listOf( { ThemeStep(uiPreferences = uiPreferences) }, { StorageStep(storagePref = storagePreferences.baseStorageDirectory()) }, // TODO: prompt for notification permissions when bumping target to Android 13 + { GuidesStep(onRestoreBackup = onRestoreBackup) }, ) val isLastStep = currentStep == steps.size - 1 val slideDistance = rememberSlideDistance() + BackHandler(enabled = currentStep != 0, onBack = { currentStep-- }) + InfoScreen( icon = Icons.Outlined.RocketLaunch, headingText = stringResource(MR.strings.onboarding_heading), @@ -52,17 +65,25 @@ fun OnboardingScreen( rejectText = stringResource(MR.strings.onboarding_action_skip), onRejectClick = onComplete, ) { - AnimatedContent( - targetState = currentStep, - transitionSpec = { - materialSharedAxisX( - forward = true, - slideDistance = slideDistance, - ) - }, - label = "stepContent", + Box( + modifier = Modifier + .padding(vertical = MaterialTheme.padding.small) + .clip(MaterialTheme.shapes.small) + .fillMaxSize() + .background(MaterialTheme.colorScheme.surfaceVariant), ) { - steps[it]() + AnimatedContent( + targetState = currentStep, + transitionSpec = { + materialSharedAxisX( + forward = true, + slideDistance = slideDistance, + ) + }, + label = "stepContent", + ) { + steps[it]() + } } } } 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 b4204a0e46..062e5a7c99 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 @@ -3,8 +3,11 @@ package eu.kanade.presentation.more.onboarding import android.content.ActivityNotFoundException import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +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.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import eu.kanade.presentation.more.settings.screen.SettingsDataScreen @@ -22,11 +25,19 @@ internal fun StorageStep( val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref) Column( + modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - Text(stringResource(MR.strings.onboarding_storage_info)) + Text( + stringResource( + MR.strings.onboarding_storage_info, + stringResource(MR.strings.app_name), + SettingsDataScreen.storageLocationText(storagePref), + ), + ) Button( + modifier = Modifier.fillMaxWidth(), onClick = { try { pickStorageLocation.launch(null) @@ -35,7 +46,7 @@ internal fun StorageStep( } }, ) { - Text(SettingsDataScreen.storageLocationText(storagePref)) + Text(stringResource(MR.strings.onboarding_storage_action_select)) } } } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index e273f05e56..988bc98a39 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -110,6 +110,10 @@ object SettingsDataScreen : SearchableSettings { val context = LocalContext.current val storageDir by storageDirPref.collectAsState() + if (storageDir == storageDirPref.defaultValue()) { + return stringResource(MR.strings.no_location_set) + } + return remember(storageDir) { val file = UniFile.fromUri(context, storageDir.toUri()) file?.filePath ?: file?.uri?.toString() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt index 0721b7132c..e54dd97d68 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt @@ -33,6 +33,7 @@ import eu.kanade.presentation.library.LibrarySettingsDialog import eu.kanade.presentation.library.components.LibraryContent import eu.kanade.presentation.library.components.LibraryToolbar import eu.kanade.presentation.manga.components.LibraryBottomActionMenu +import eu.kanade.presentation.more.onboarding.GETTING_STARTED_URL import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.library.LibraryUpdateJob @@ -163,7 +164,7 @@ object LibraryTab : Tab { EmptyScreenAction( stringRes = MR.strings.getting_started_guide, icon = Icons.AutoMirrored.Outlined.HelpOutline, - onClick = { handler.openUri("https://tachiyomi.org/docs/guides/getting-started") }, + onClick = { handler.openUri(GETTING_STARTED_URL) }, ), ), ) 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 0cbd7e6254..037edd9f56 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 @@ -8,6 +8,7 @@ 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 @@ -22,12 +23,18 @@ class OnboardingScreen : Screen() { val storagePreferences = remember { Injekt.get() } val uiPreferences = remember { Injekt.get() } + val finishOnboarding = { + basePreferences.shownOnboardingFlow().set(true) + navigator.pop() + } + OnboardingScreen( storagePreferences = storagePreferences, uiPreferences = uiPreferences, - onComplete = { - basePreferences.shownOnboardingFlow().set(true) - navigator.pop() + onComplete = { finishOnboarding() }, + onRestoreBackup = { + finishOnboarding() + navigator.push(SettingsScreen.toDataAndStorageScreen()) }, ) } diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml index fb190bda9b..9b86c899a0 100644 --- a/i18n/src/commonMain/resources/MR/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/base/strings.xml @@ -180,7 +180,10 @@ Next Get started Skip - Select a storage location where chapter downloads, backups, and local source content will be stored. + Select a folder where %1$s will store chapter downloads, backups, and more.\n\nA dedicated folder is recommended.\n\nSelected folder: %2$s + Select a folder + New to %s? We recommend checking out the getting started guide. + Already used %s before? @@ -439,6 +442,7 @@ After reading automatically delete Allow deleting bookmarked chapters Excluded categories + No storage location set Invalid location: %s Disabled Last read chapter