Add basic onboarding screen (#10199)
This commit is contained in:
parent
ab9a26f6bd
commit
8b57169e92
@ -24,6 +24,8 @@ class BasePreferences(
|
|||||||
|
|
||||||
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
||||||
|
|
||||||
|
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
|
||||||
|
|
||||||
enum class ExtensionInstaller(val titleRes: StringResource) {
|
enum class ExtensionInstaller(val titleRes: StringResource) {
|
||||||
LEGACY(MR.strings.ext_installer_legacy),
|
LEGACY(MR.strings.ext_installer_legacy),
|
||||||
PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller),
|
PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller),
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
package eu.kanade.presentation.more.onboarding
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.RocketLaunch
|
||||||
|
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 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.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.screens.InfoScreen
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OnboardingScreen(
|
||||||
|
storagePreferences: StoragePreferences,
|
||||||
|
uiPreferences: UiPreferences,
|
||||||
|
onComplete: () -> 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
|
||||||
|
)
|
||||||
|
val isLastStep = currentStep == steps.size - 1
|
||||||
|
val slideDistance = rememberSlideDistance()
|
||||||
|
|
||||||
|
InfoScreen(
|
||||||
|
icon = Icons.Outlined.RocketLaunch,
|
||||||
|
headingText = stringResource(MR.strings.onboarding_heading),
|
||||||
|
subtitleText = stringResource(MR.strings.onboarding_description),
|
||||||
|
acceptText = stringResource(
|
||||||
|
if (isLastStep) {
|
||||||
|
MR.strings.onboarding_action_finish
|
||||||
|
} else {
|
||||||
|
MR.strings.onboarding_action_next
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onAcceptClick = {
|
||||||
|
if (!isLastStep) {
|
||||||
|
currentStep++
|
||||||
|
} else {
|
||||||
|
onComplete()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rejectText = stringResource(MR.strings.onboarding_action_skip),
|
||||||
|
onRejectClick = onComplete,
|
||||||
|
) {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = currentStep,
|
||||||
|
transitionSpec = {
|
||||||
|
materialSharedAxisX(
|
||||||
|
forward = true,
|
||||||
|
slideDistance = slideDistance,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = "stepContent",
|
||||||
|
) {
|
||||||
|
steps[it]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
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.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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 tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.material.Button
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun StorageStep(
|
||||||
|
storagePref: Preference<String>,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Text(stringResource(MR.strings.onboarding_storage_info))
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
try {
|
||||||
|
pickStorageLocation.launch(null)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
context.toast(MR.strings.file_picker_error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(SettingsDataScreen.storageLocationText(storagePref))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package eu.kanade.presentation.more.onboarding
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
|
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
|
||||||
|
|
||||||
|
@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) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -43,6 +43,7 @@ import eu.kanade.tachiyomi.network.PREF_DOH_NJALLA
|
|||||||
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101
|
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9
|
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_SHECAN
|
import eu.kanade.tachiyomi.network.PREF_DOH_SHECAN
|
||||||
|
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
|
||||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
@ -110,6 +111,10 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_debug_info),
|
title = stringResource(MR.strings.pref_debug_info),
|
||||||
onClick = { navigator.push(DebugInfoScreen()) },
|
onClick = { navigator.push(DebugInfoScreen()) },
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.TextPreference(
|
||||||
|
title = stringResource(MR.strings.pref_onboarding_guide),
|
||||||
|
onClick = { navigator.push(OnboardingScreen()) },
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
@ -2,8 +2,8 @@ package eu.kanade.presentation.more.settings.screen
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
@ -19,13 +19,11 @@ import eu.kanade.domain.ui.model.TabletUiMode
|
|||||||
import eu.kanade.domain.ui.model.ThemeMode
|
import eu.kanade.domain.ui.model.ThemeMode
|
||||||
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
|
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.flow.drop
|
|
||||||
import kotlinx.coroutines.flow.merge
|
|
||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import tachiyomi.core.i18n.stringResource
|
import tachiyomi.core.i18n.stringResource
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@ -43,72 +41,59 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun getPreferences(): List<Preference> {
|
override fun getPreferences(): List<Preference> {
|
||||||
val context = LocalContext.current
|
|
||||||
val uiPreferences = remember { Injekt.get<UiPreferences>() }
|
val uiPreferences = remember { Injekt.get<UiPreferences>() }
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
getThemeGroup(context = context, uiPreferences = uiPreferences),
|
getThemeGroup(uiPreferences = uiPreferences),
|
||||||
getDisplayGroup(context = context, uiPreferences = uiPreferences),
|
getDisplayGroup(uiPreferences = uiPreferences),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getThemeGroup(
|
private fun getThemeGroup(
|
||||||
context: Context,
|
|
||||||
uiPreferences: UiPreferences,
|
uiPreferences: UiPreferences,
|
||||||
): Preference.PreferenceGroup {
|
): Preference.PreferenceGroup {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
val themeModePref = uiPreferences.themeMode()
|
val themeModePref = uiPreferences.themeMode()
|
||||||
val themeMode by themeModePref.collectAsState()
|
val themeMode by themeModePref.collectAsState()
|
||||||
|
|
||||||
val appThemePref = uiPreferences.appTheme()
|
val appThemePref = uiPreferences.appTheme()
|
||||||
|
val appTheme by appThemePref.collectAsState()
|
||||||
|
|
||||||
val amoledPref = uiPreferences.themeDarkAmoled()
|
val amoledPref = uiPreferences.themeDarkAmoled()
|
||||||
val amoled by amoledPref.collectAsState()
|
val amoled by amoledPref.collectAsState()
|
||||||
|
|
||||||
LaunchedEffect(themeMode) {
|
|
||||||
setAppCompatDelegateThemeMode(themeMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
merge(appThemePref.changes(), amoledPref.changes())
|
|
||||||
.drop(2)
|
|
||||||
.collectLatest { (context as? Activity)?.let { ActivityCompat.recreate(it) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(MR.strings.pref_category_theme),
|
title = stringResource(MR.strings.pref_category_theme),
|
||||||
preferenceItems = listOf(
|
preferenceItems = listOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
|
||||||
pref = themeModePref,
|
|
||||||
title = stringResource(MR.strings.pref_theme_mode),
|
|
||||||
entries = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
mapOf(
|
|
||||||
ThemeMode.SYSTEM to stringResource(MR.strings.theme_system),
|
|
||||||
ThemeMode.LIGHT to stringResource(MR.strings.theme_light),
|
|
||||||
ThemeMode.DARK to stringResource(MR.strings.theme_dark),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
mapOf(
|
|
||||||
ThemeMode.LIGHT to stringResource(MR.strings.theme_light),
|
|
||||||
ThemeMode.DARK to stringResource(MR.strings.theme_dark),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.CustomPreference(
|
Preference.PreferenceItem.CustomPreference(
|
||||||
title = stringResource(MR.strings.pref_app_theme),
|
title = stringResource(MR.strings.pref_app_theme),
|
||||||
) { item ->
|
) {
|
||||||
val value by appThemePref.collectAsState()
|
Column {
|
||||||
AppThemePreferenceWidget(
|
AppThemeModePreferenceWidget(
|
||||||
title = item.title,
|
value = themeMode,
|
||||||
value = value,
|
onItemClick = {
|
||||||
amoled = amoled,
|
themeModePref.set(it)
|
||||||
onItemClick = { appThemePref.set(it) },
|
setAppCompatDelegateThemeMode(it)
|
||||||
)
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
AppThemePreferenceWidget(
|
||||||
|
value = appTheme,
|
||||||
|
amoled = amoled,
|
||||||
|
onItemClick = { appThemePref.set(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = amoledPref,
|
pref = amoledPref,
|
||||||
title = stringResource(MR.strings.pref_dark_theme_pure_black),
|
title = stringResource(MR.strings.pref_dark_theme_pure_black),
|
||||||
enabled = themeMode != ThemeMode.LIGHT,
|
enabled = themeMode != ThemeMode.LIGHT,
|
||||||
|
onValueChanged = {
|
||||||
|
(context as? Activity)?.let { ActivityCompat.recreate(it) }
|
||||||
|
true
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -116,9 +101,10 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getDisplayGroup(
|
private fun getDisplayGroup(
|
||||||
context: Context,
|
|
||||||
uiPreferences: UiPreferences,
|
uiPreferences: UiPreferences,
|
||||||
): Preference.PreferenceGroup {
|
): Preference.PreferenceGroup {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
val langs = remember { getLangs(context) }
|
val langs = remember { getLangs(context) }
|
||||||
var currentLanguage by remember {
|
var currentLanguage by remember {
|
||||||
mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "")
|
mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "")
|
||||||
|
@ -7,6 +7,7 @@ import android.net.Uri
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@ -80,13 +81,12 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getStorageLocationPref(
|
fun storageLocationPicker(
|
||||||
storagePreferences: StoragePreferences,
|
storageDirPref: tachiyomi.core.preference.Preference<String>,
|
||||||
): Preference.PreferenceItem.TextPreference {
|
): ManagedActivityResultLauncher<Uri?, Uri?> {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val storageDirPref = storagePreferences.baseStorageDirectory()
|
|
||||||
val storageDir by storageDirPref.collectAsState()
|
return rememberLauncherForActivityResult(
|
||||||
val pickStorageLocation = rememberLauncherForActivityResult(
|
|
||||||
contract = ActivityResultContracts.OpenDocumentTree(),
|
contract = ActivityResultContracts.OpenDocumentTree(),
|
||||||
) { uri ->
|
) { uri ->
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
@ -101,13 +101,31 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
Injekt.get<DownloadCache>().invalidateCache()
|
Injekt.get<DownloadCache>().invalidateCache()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun storageLocationText(
|
||||||
|
storageDirPref: tachiyomi.core.preference.Preference<String>,
|
||||||
|
): String {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val storageDir by storageDirPref.collectAsState()
|
||||||
|
|
||||||
|
return remember(storageDir) {
|
||||||
|
val file = UniFile.fromUri(context, storageDir.toUri())
|
||||||
|
file?.filePath ?: file?.uri?.toString()
|
||||||
|
} ?: stringResource(MR.strings.invalid_location, storageDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getStorageLocationPref(
|
||||||
|
storagePreferences: StoragePreferences,
|
||||||
|
): Preference.PreferenceItem.TextPreference {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val pickStorageLocation = storageLocationPicker(storagePreferences.baseStorageDirectory())
|
||||||
|
|
||||||
return Preference.PreferenceItem.TextPreference(
|
return Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(MR.strings.pref_storage_location),
|
title = stringResource(MR.strings.pref_storage_location),
|
||||||
subtitle = remember(storageDir) {
|
subtitle = storageLocationText(storagePreferences.baseStorageDirectory()),
|
||||||
val file = UniFile.fromUri(context, storageDir.toUri())
|
|
||||||
file?.filePath ?: file?.uri?.toString()
|
|
||||||
} ?: stringResource(MR.strings.invalid_location, storageDir),
|
|
||||||
onClick = {
|
onClick = {
|
||||||
try {
|
try {
|
||||||
pickStorageLocation.launch(null)
|
pickStorageLocation.launch(null)
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.widget
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
|
||||||
|
import androidx.compose.material3.SegmentedButton
|
||||||
|
import androidx.compose.material3.SegmentedButtonDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import eu.kanade.domain.ui.model.ThemeMode
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
|
private val options = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
mapOf(
|
||||||
|
ThemeMode.SYSTEM to MR.strings.theme_system,
|
||||||
|
ThemeMode.LIGHT to MR.strings.theme_light,
|
||||||
|
ThemeMode.DARK to MR.strings.theme_dark,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
mapOf(
|
||||||
|
ThemeMode.LIGHT to MR.strings.theme_light,
|
||||||
|
ThemeMode.DARK to MR.strings.theme_dark,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun AppThemeModePreferenceWidget(
|
||||||
|
value: ThemeMode,
|
||||||
|
onItemClick: (ThemeMode) -> Unit,
|
||||||
|
) {
|
||||||
|
BasePreferenceWidget(
|
||||||
|
subcomponent = {
|
||||||
|
MultiChoiceSegmentedButtonRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = PrefsHorizontalPadding),
|
||||||
|
) {
|
||||||
|
options.onEachIndexed { index, (mode, labelRes) ->
|
||||||
|
SegmentedButton(
|
||||||
|
checked = mode == value,
|
||||||
|
onCheckedChange = { onItemClick(mode) },
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(
|
||||||
|
index,
|
||||||
|
options.size,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(stringResource(labelRes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.presentation.more.settings.widget
|
package eu.kanade.presentation.more.settings.widget
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@ -36,9 +37,11 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import eu.kanade.domain.ui.model.AppTheme
|
import eu.kanade.domain.ui.model.AppTheme
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
import eu.kanade.presentation.manga.components.MangaCover
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
@ -51,13 +54,11 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AppThemePreferenceWidget(
|
internal fun AppThemePreferenceWidget(
|
||||||
title: String,
|
|
||||||
value: AppTheme,
|
value: AppTheme,
|
||||||
amoled: Boolean,
|
amoled: Boolean,
|
||||||
onItemClick: (AppTheme) -> Unit,
|
onItemClick: (AppTheme) -> Unit,
|
||||||
) {
|
) {
|
||||||
BasePreferenceWidget(
|
BasePreferenceWidget(
|
||||||
title = title,
|
|
||||||
subcomponent = {
|
subcomponent = {
|
||||||
AppThemesList(
|
AppThemesList(
|
||||||
currentTheme = value,
|
currentTheme = value,
|
||||||
@ -74,6 +75,7 @@ private fun AppThemesList(
|
|||||||
amoled: Boolean,
|
amoled: Boolean,
|
||||||
onItemClick: (AppTheme) -> Unit,
|
onItemClick: (AppTheme) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
val appThemes = remember {
|
val appThemes = remember {
|
||||||
AppTheme.entries
|
AppTheme.entries
|
||||||
.filterNot { it.titleRes == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) }
|
.filterNot { it.titleRes == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) }
|
||||||
@ -97,7 +99,10 @@ private fun AppThemesList(
|
|||||||
) {
|
) {
|
||||||
AppThemePreviewItem(
|
AppThemePreviewItem(
|
||||||
selected = currentTheme == appTheme,
|
selected = currentTheme == appTheme,
|
||||||
onClick = { onItemClick(appTheme) },
|
onClick = {
|
||||||
|
onItemClick(appTheme)
|
||||||
|
(context as? Activity)?.let { ActivityCompat.recreate(it) }
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,12 +126,12 @@ object HomeScreen : Screen() {
|
|||||||
materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) togetherWith
|
materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) togetherWith
|
||||||
materialFadeThroughOut(durationMillis = TabFadeDuration)
|
materialFadeThroughOut(durationMillis = TabFadeDuration)
|
||||||
},
|
},
|
||||||
content = {
|
label = "tabContent",
|
||||||
tabNavigator.saveableState(key = "currentTab", it) {
|
) {
|
||||||
it.Content()
|
tabNavigator.saveableState(key = "currentTab", it) {
|
||||||
}
|
it.Content()
|
||||||
},
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,7 @@ import eu.kanade.tachiyomi.ui.deeplink.DeepLinkScreen
|
|||||||
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
||||||
|
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim
|
import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
@ -251,6 +252,7 @@ class MainActivity : BaseActivity() {
|
|||||||
HandleOnNewIntent(context = context, navigator = navigator)
|
HandleOnNewIntent(context = context, navigator = navigator)
|
||||||
|
|
||||||
CheckForUpdates()
|
CheckForUpdates()
|
||||||
|
ShowOnboarding()
|
||||||
}
|
}
|
||||||
|
|
||||||
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
|
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
|
||||||
@ -342,6 +344,17 @@ class MainActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ShowOnboarding() {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
if (!preferences.shownOnboardingFlow().get()) {
|
||||||
|
navigator.push(OnboardingScreen())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets custom splash screen exit animation on devices prior to Android 12.
|
* Sets custom splash screen exit animation on devices prior to Android 12.
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.more
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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 tachiyomi.domain.storage.service.StoragePreferences
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class OnboardingScreen : Screen() {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
|
val basePreferences = remember { Injekt.get<BasePreferences>() }
|
||||||
|
val storagePreferences = remember { Injekt.get<StoragePreferences>() }
|
||||||
|
val uiPreferences = remember { Injekt.get<UiPreferences>() }
|
||||||
|
|
||||||
|
OnboardingScreen(
|
||||||
|
storagePreferences = storagePreferences,
|
||||||
|
uiPreferences = uiPreferences,
|
||||||
|
onComplete = {
|
||||||
|
basePreferences.shownOnboardingFlow().set(true)
|
||||||
|
navigator.pop()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.stateIn
|
|||||||
* Local-copy implementation of PreferenceStore mostly for test and preview purposes
|
* Local-copy implementation of PreferenceStore mostly for test and preview purposes
|
||||||
*/
|
*/
|
||||||
class InMemoryPreferenceStore(
|
class InMemoryPreferenceStore(
|
||||||
private val initialPreferences: Sequence<InMemoryPreference<*>> = sequenceOf(),
|
initialPreferences: Sequence<InMemoryPreference<*>> = sequenceOf(),
|
||||||
) : PreferenceStore {
|
) : PreferenceStore {
|
||||||
|
|
||||||
private val preferences: Map<String, Preference<*>> =
|
private val preferences: Map<String, Preference<*>> =
|
||||||
|
@ -173,6 +173,15 @@
|
|||||||
<!-- Shortcuts-->
|
<!-- Shortcuts-->
|
||||||
<string name="app_not_available">App not available</string>
|
<string name="app_not_available">App not available</string>
|
||||||
|
|
||||||
|
<!-- Onboarding -->
|
||||||
|
<string name="pref_onboarding_guide">Onboarding guide</string>
|
||||||
|
<string name="onboarding_heading">Welcome!</string>
|
||||||
|
<string name="onboarding_description">Let\'s set some things up first. You can always change these in the settings later too.</string>
|
||||||
|
<string name="onboarding_action_next">Next</string>
|
||||||
|
<string name="onboarding_action_finish">Get started</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>
|
||||||
|
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<!-- Subsections -->
|
<!-- Subsections -->
|
||||||
<string name="pref_category_general">General</string>
|
<string name="pref_category_general">General</string>
|
||||||
@ -196,11 +205,10 @@
|
|||||||
|
|
||||||
<!-- General section -->
|
<!-- General section -->
|
||||||
<string name="pref_category_theme">Theme</string>
|
<string name="pref_category_theme">Theme</string>
|
||||||
<string name="pref_theme_mode">Dark mode</string>
|
|
||||||
<string name="theme_system">Follow system</string>
|
|
||||||
<string name="theme_light">Off</string>
|
|
||||||
<string name="theme_dark">On</string>
|
|
||||||
<string name="pref_app_theme">App theme</string>
|
<string name="pref_app_theme">App theme</string>
|
||||||
|
<string name="theme_system">System</string>
|
||||||
|
<string name="theme_light">Light</string>
|
||||||
|
<string name="theme_dark">Dark</string>
|
||||||
<string name="theme_monet">Dynamic</string>
|
<string name="theme_monet">Dynamic</string>
|
||||||
<string name="theme_greenapple">Green Apple</string>
|
<string name="theme_greenapple">Green Apple</string>
|
||||||
<string name="theme_lavender">Lavender</string>
|
<string name="theme_lavender">Lavender</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user