More onboarding screen additions

This commit is contained in:
arkon 2023-12-09 17:49:35 -05:00
parent 8b57169e92
commit e3404cd3d3
8 changed files with 114 additions and 19 deletions

View File

@ -2,7 +2,7 @@ package eu.kanade.presentation.crash
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box 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.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.BugReport import androidx.compose.material.icons.outlined.BugReport
@ -47,7 +47,7 @@ fun CrashScreen(
modifier = Modifier modifier = Modifier
.padding(vertical = MaterialTheme.padding.small) .padding(vertical = MaterialTheme.padding.small)
.clip(MaterialTheme.shapes.small) .clip(MaterialTheme.shapes.small)
.fillMaxWidth() .fillMaxSize()
.background(MaterialTheme.colorScheme.surfaceVariant), .background(MaterialTheme.colorScheme.surfaceVariant),
) { ) {
Text( Text(

View File

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

View File

@ -1,18 +1,27 @@
package eu.kanade.presentation.more.onboarding package eu.kanade.presentation.more.onboarding
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedContent 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.Icons
import androidx.compose.material.icons.outlined.RocketLaunch import androidx.compose.material.icons.outlined.RocketLaunch
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable 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.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
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.domain.storage.service.StoragePreferences
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.InfoScreen import tachiyomi.presentation.core.screens.InfoScreen
@ -21,16 +30,20 @@ fun OnboardingScreen(
storagePreferences: StoragePreferences, storagePreferences: StoragePreferences,
uiPreferences: UiPreferences, uiPreferences: UiPreferences,
onComplete: () -> Unit, onComplete: () -> Unit,
onRestoreBackup: () -> Unit,
) { ) {
var currentStep by remember { mutableIntStateOf(0) } var currentStep by remember { mutableIntStateOf(0) }
val steps: List<@Composable () -> Unit> = listOf( val steps: List<@Composable () -> Unit> = listOf(
{ ThemeStep(uiPreferences = uiPreferences) }, { ThemeStep(uiPreferences = uiPreferences) },
{ StorageStep(storagePref = storagePreferences.baseStorageDirectory()) }, { StorageStep(storagePref = storagePreferences.baseStorageDirectory()) },
// 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) },
) )
val isLastStep = currentStep == steps.size - 1 val isLastStep = currentStep == steps.size - 1
val slideDistance = rememberSlideDistance() val slideDistance = rememberSlideDistance()
BackHandler(enabled = currentStep != 0, onBack = { currentStep-- })
InfoScreen( InfoScreen(
icon = Icons.Outlined.RocketLaunch, icon = Icons.Outlined.RocketLaunch,
headingText = stringResource(MR.strings.onboarding_heading), headingText = stringResource(MR.strings.onboarding_heading),
@ -52,17 +65,25 @@ fun OnboardingScreen(
rejectText = stringResource(MR.strings.onboarding_action_skip), rejectText = stringResource(MR.strings.onboarding_action_skip),
onRejectClick = onComplete, onRejectClick = onComplete,
) { ) {
AnimatedContent( Box(
targetState = currentStep, modifier = Modifier
transitionSpec = { .padding(vertical = MaterialTheme.padding.small)
materialSharedAxisX( .clip(MaterialTheme.shapes.small)
forward = true, .fillMaxSize()
slideDistance = slideDistance, .background(MaterialTheme.colorScheme.surfaceVariant),
)
},
label = "stepContent",
) { ) {
steps[it]() AnimatedContent(
targetState = currentStep,
transitionSpec = {
materialSharedAxisX(
forward = true,
slideDistance = slideDistance,
)
},
label = "stepContent",
) {
steps[it]()
}
} }
} }
} }

View File

@ -3,8 +3,11 @@ package eu.kanade.presentation.more.onboarding
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column 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.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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
@ -22,11 +25,19 @@ internal fun StorageStep(
val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref) val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref)
Column( Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.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( Button(
modifier = Modifier.fillMaxWidth(),
onClick = { onClick = {
try { try {
pickStorageLocation.launch(null) pickStorageLocation.launch(null)
@ -35,7 +46,7 @@ internal fun StorageStep(
} }
}, },
) { ) {
Text(SettingsDataScreen.storageLocationText(storagePref)) Text(stringResource(MR.strings.onboarding_storage_action_select))
} }
} }
} }

View File

@ -110,6 +110,10 @@ object SettingsDataScreen : SearchableSettings {
val context = LocalContext.current val context = LocalContext.current
val storageDir by storageDirPref.collectAsState() val storageDir by storageDirPref.collectAsState()
if (storageDir == storageDirPref.defaultValue()) {
return stringResource(MR.strings.no_location_set)
}
return remember(storageDir) { return remember(storageDir) {
val file = UniFile.fromUri(context, storageDir.toUri()) val file = UniFile.fromUri(context, storageDir.toUri())
file?.filePath ?: file?.uri?.toString() file?.filePath ?: file?.uri?.toString()

View File

@ -33,6 +33,7 @@ import eu.kanade.presentation.library.LibrarySettingsDialog
import eu.kanade.presentation.library.components.LibraryContent import eu.kanade.presentation.library.components.LibraryContent
import eu.kanade.presentation.library.components.LibraryToolbar import eu.kanade.presentation.library.components.LibraryToolbar
import eu.kanade.presentation.manga.components.LibraryBottomActionMenu 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.presentation.util.Tab
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
@ -163,7 +164,7 @@ object LibraryTab : Tab {
EmptyScreenAction( EmptyScreenAction(
stringRes = MR.strings.getting_started_guide, stringRes = MR.strings.getting_started_guide,
icon = Icons.AutoMirrored.Outlined.HelpOutline, icon = Icons.AutoMirrored.Outlined.HelpOutline,
onClick = { handler.openUri("https://tachiyomi.org/docs/guides/getting-started") }, onClick = { handler.openUri(GETTING_STARTED_URL) },
), ),
), ),
) )

View File

@ -8,6 +8,7 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.ui.UiPreferences 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 tachiyomi.domain.storage.service.StoragePreferences 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
@ -22,12 +23,18 @@ class OnboardingScreen : Screen() {
val storagePreferences = remember { Injekt.get<StoragePreferences>() } val storagePreferences = remember { Injekt.get<StoragePreferences>() }
val uiPreferences = remember { Injekt.get<UiPreferences>() } val uiPreferences = remember { Injekt.get<UiPreferences>() }
val finishOnboarding = {
basePreferences.shownOnboardingFlow().set(true)
navigator.pop()
}
OnboardingScreen( OnboardingScreen(
storagePreferences = storagePreferences, storagePreferences = storagePreferences,
uiPreferences = uiPreferences, uiPreferences = uiPreferences,
onComplete = { onComplete = { finishOnboarding() },
basePreferences.shownOnboardingFlow().set(true) onRestoreBackup = {
navigator.pop() finishOnboarding()
navigator.push(SettingsScreen.toDataAndStorageScreen())
}, },
) )
} }

View File

@ -180,7 +180,10 @@
<string name="onboarding_action_next">Next</string> <string name="onboarding_action_next">Next</string>
<string name="onboarding_action_finish">Get started</string> <string name="onboarding_action_finish">Get started</string>
<string name="onboarding_action_skip">Skip</string> <string name="onboarding_action_skip">Skip</string>
<string name="onboarding_storage_info">Select a storage location where chapter downloads, backups, and local source content will be stored.</string> <string name="onboarding_storage_info">Select a folder where %1$s will store chapter downloads, backups, and more.\n\nA dedicated folder is recommended.\n\nSelected folder: %2$s</string>
<string name="onboarding_storage_action_select">Select a folder</string>
<string name="onboarding_guides_new_user">New to %s? We recommend checking out the getting started guide.</string>
<string name="onboarding_guides_returning_user">Already used %s before?</string>
<!-- Preferences --> <!-- Preferences -->
<!-- Subsections --> <!-- Subsections -->
@ -439,6 +442,7 @@
<string name="pref_remove_after_read">After reading automatically delete</string> <string name="pref_remove_after_read">After reading automatically delete</string>
<string name="pref_remove_bookmarked_chapters">Allow deleting bookmarked chapters</string> <string name="pref_remove_bookmarked_chapters">Allow deleting bookmarked chapters</string>
<string name="pref_remove_exclude_categories">Excluded categories</string> <string name="pref_remove_exclude_categories">Excluded categories</string>
<string name="no_location_set">No storage location set</string>
<string name="invalid_location">Invalid location: %s</string> <string name="invalid_location">Invalid location: %s</string>
<string name="disabled">Disabled</string> <string name="disabled">Disabled</string>
<string name="last_read_chapter">Last read chapter</string> <string name="last_read_chapter">Last read chapter</string>