More settings stuff (#8226)

* title size

* move about screen to settings

keeping shortcut inside more screen

* more

* shrink texts

* scrollable create backup dialog choices

* search back button

* cleanups

* delay changes that require activity recreate

* lessen horizontal padding
This commit is contained in:
Ivan Iskandar 2022-10-18 20:35:10 +07:00 committed by GitHub
parent ea092fa175
commit f5bde3726a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 552 additions and 649 deletions

View File

@ -13,16 +13,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.PreferenceRow
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.components.SwitchPreference
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
import eu.kanade.tachiyomi.ui.more.MoreController
@ -57,26 +57,28 @@ fun MoreScreen(
}
item {
SwitchPreference(
preference = presenter.downloadedOnly,
SwitchPreferenceWidget(
title = stringResource(R.string.label_downloaded_only),
subtitle = stringResource(R.string.downloaded_only_summary),
painter = rememberVectorPainter(Icons.Outlined.CloudOff),
icon = Icons.Outlined.CloudOff,
checked = presenter.downloadedOnly.value,
onCheckedChanged = { presenter.downloadedOnly.value = it },
)
}
item {
SwitchPreference(
preference = presenter.incognitoMode,
SwitchPreferenceWidget(
title = stringResource(R.string.pref_incognito_mode),
subtitle = stringResource(R.string.pref_incognito_mode_summary),
painter = painterResource(R.drawable.ic_glasses_24dp),
icon = ImageVector.vectorResource(R.drawable.ic_glasses_24dp),
checked = presenter.incognitoMode.value,
onCheckedChanged = { presenter.incognitoMode.value = it },
)
}
item { Divider() }
item {
PreferenceRow(
TextPreferenceWidget(
title = stringResource(R.string.label_download_queue),
subtitle = when (downloadQueueState) {
DownloadQueueState.Stopped -> null
@ -99,46 +101,46 @@ fun MoreScreen(
pluralStringResource(id = R.plurals.download_queue_summary, count = pending, pending)
}
},
painter = rememberVectorPainter(Icons.Outlined.GetApp),
onClick = onClickDownloadQueue,
icon = Icons.Outlined.GetApp,
onPreferenceClick = onClickDownloadQueue,
)
}
item {
PreferenceRow(
TextPreferenceWidget(
title = stringResource(R.string.categories),
painter = rememberVectorPainter(Icons.Outlined.Label),
onClick = onClickCategories,
icon = Icons.Outlined.Label,
onPreferenceClick = onClickCategories,
)
}
item {
PreferenceRow(
TextPreferenceWidget(
title = stringResource(R.string.label_backup),
painter = rememberVectorPainter(Icons.Outlined.SettingsBackupRestore),
onClick = onClickBackupAndRestore,
icon = Icons.Outlined.SettingsBackupRestore,
onPreferenceClick = onClickBackupAndRestore,
)
}
item { Divider() }
item {
PreferenceRow(
TextPreferenceWidget(
title = stringResource(R.string.label_settings),
painter = rememberVectorPainter(Icons.Outlined.Settings),
onClick = onClickSettings,
icon = Icons.Outlined.Settings,
onPreferenceClick = onClickSettings,
)
}
item {
PreferenceRow(
TextPreferenceWidget(
title = stringResource(R.string.pref_category_about),
painter = rememberVectorPainter(Icons.Outlined.Info),
onClick = onClickAbout,
icon = Icons.Outlined.Info,
onPreferenceClick = onClickAbout,
)
}
item {
PreferenceRow(
TextPreferenceWidget(
title = stringResource(R.string.label_help),
painter = rememberVectorPainter(Icons.Outlined.HelpOutline),
onClick = { uriHandler.openUri(MoreController.URL_HELP) },
icon = Icons.Outlined.HelpOutline,
onPreferenceClick = { uriHandler.openUri(MoreController.URL_HELP) },
)
}
}

View File

@ -1,152 +0,0 @@
package eu.kanade.presentation.more.about
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Public
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.LinkIcon
import eu.kanade.presentation.components.PreferenceRow
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.more.LogoHeader
import eu.kanade.presentation.util.plus
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.system.copyToClipboard
@Composable
fun AboutScreen(
navigateUp: () -> Unit,
checkVersion: () -> Unit,
getFormattedBuildTime: () -> String,
onClickLicenses: () -> Unit,
) {
val context = LocalContext.current
val uriHandler = LocalUriHandler.current
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = stringResource(R.string.pref_category_about),
navigateUp = navigateUp,
scrollBehavior = scrollBehavior,
)
},
) { contentPadding ->
ScrollbarLazyColumn(
contentPadding = contentPadding,
) {
item {
LogoHeader()
}
item {
PreferenceRow(
title = stringResource(R.string.version),
subtitle = when {
BuildConfig.DEBUG -> {
"Debug ${BuildConfig.COMMIT_SHA} (${getFormattedBuildTime()})"
}
BuildConfig.PREVIEW -> {
"Preview r${BuildConfig.COMMIT_COUNT} (${BuildConfig.COMMIT_SHA}, ${getFormattedBuildTime()})"
}
else -> {
"Stable ${BuildConfig.VERSION_NAME} (${getFormattedBuildTime()})"
}
},
onClick = {
val deviceInfo = CrashLogUtil(context).getDebugInfo()
context.copyToClipboard("Debug information", deviceInfo)
},
)
}
if (BuildConfig.INCLUDE_UPDATER) {
item {
PreferenceRow(
title = stringResource(R.string.check_for_updates),
onClick = checkVersion,
)
}
}
if (!BuildConfig.DEBUG) {
item {
PreferenceRow(
title = stringResource(R.string.whats_new),
onClick = { uriHandler.openUri(RELEASE_URL) },
)
}
}
item {
PreferenceRow(
title = stringResource(R.string.help_translate),
onClick = { uriHandler.openUri("https://tachiyomi.org/help/contribution/#translation") },
)
}
item {
PreferenceRow(
title = stringResource(R.string.licenses),
onClick = onClickLicenses,
)
}
item {
PreferenceRow(
title = stringResource(R.string.privacy_policy),
onClick = { uriHandler.openUri("https://tachiyomi.org/privacy") },
)
}
item {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
LinkIcon(
label = stringResource(R.string.website),
painter = rememberVectorPainter(Icons.Outlined.Public),
url = "https://tachiyomi.org",
)
LinkIcon(
label = "Discord",
painter = painterResource(R.drawable.ic_discord_24dp),
url = "https://discord.gg/tachiyomi",
)
LinkIcon(
label = "Twitter",
painter = painterResource(R.drawable.ic_twitter_24dp),
url = "https://twitter.com/tachiyomiorg",
)
LinkIcon(
label = "Facebook",
painter = painterResource(R.drawable.ic_facebook_24dp),
url = "https://facebook.com/tachiyomiorg",
)
LinkIcon(
label = "Reddit",
painter = painterResource(R.drawable.ic_reddit_24dp),
url = "https://www.reddit.com/r/Tachiyomi",
)
LinkIcon(
label = "GitHub",
painter = painterResource(R.drawable.ic_github_24dp),
url = "https://github.com/tachiyomiorg",
)
}
}
}
}
}

View File

@ -1,39 +0,0 @@
package eu.kanade.presentation.more.about
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R
@Composable
fun LicensesScreen(
navigateUp: () -> Unit,
) {
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = stringResource(R.string.licenses),
navigateUp = navigateUp,
scrollBehavior = scrollBehavior,
)
},
) { contentPadding ->
LibrariesContainer(
modifier = Modifier
.fillMaxSize(),
contentPadding = contentPadding,
colors = LibraryDefaults.libraryColors(
backgroundColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
badgeBackgroundColor = MaterialTheme.colorScheme.primary,
badgeContentColor = MaterialTheme.colorScheme.onPrimary,
),
)
}
}

View File

@ -2,7 +2,6 @@ package eu.kanade.presentation.more.settings
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Icon
@ -10,9 +9,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R
@ -26,12 +23,7 @@ fun PreferenceScaffold(
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = stringResource(id = titleRes),
modifier = Modifier.padding(start = 8.dp),
)
},
title = { Text(text = stringResource(id = titleRes)) },
navigationIcon = {
if (onBackPressed != null) {
IconButton(onClick = onBackPressed) {

View File

@ -1,31 +0,0 @@
package eu.kanade.presentation.more.settings.database
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import eu.kanade.domain.source.model.SourceWithCount
@Stable
interface ClearDatabaseState {
val items: List<SourceWithCount>
val selection: List<Long>
val isEmpty: Boolean
var dialog: Dialog?
}
fun ClearDatabaseState(): ClearDatabaseState {
return ClearDatabaseStateImpl()
}
class ClearDatabaseStateImpl : ClearDatabaseState {
override var items: List<SourceWithCount> by mutableStateOf(emptyList())
override var selection: List<Long> by mutableStateOf(emptyList())
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
override var dialog: Dialog? by mutableStateOf(null)
}
sealed class Dialog {
data class Delete(val sourceIds: List<Long>) : Dialog()
}

View File

@ -1,73 +0,0 @@
package eu.kanade.presentation.more.settings.database.components
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.more.settings.database.ClearDatabaseState
import eu.kanade.tachiyomi.R
@Composable
fun ClearDatabaseContent(
state: ClearDatabaseState,
contentPadding: PaddingValues,
onClickSelection: (Source) -> Unit,
onClickDelete: () -> Unit,
) {
Crossfade(targetState = state.isEmpty.not()) { _state ->
when (_state) {
true -> {
Column(
modifier = Modifier
.padding(contentPadding)
.fillMaxSize(),
) {
FastScrollLazyColumn(
modifier = Modifier.weight(1f),
) {
items(state.items) { sourceWithCount ->
ClearDatabaseItem(
source = sourceWithCount.source,
count = sourceWithCount.count,
isSelected = state.selection.contains(sourceWithCount.id),
onClickSelect = { onClickSelection(sourceWithCount.source) },
)
}
}
Divider()
Button(
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.fillMaxWidth(),
onClick = onClickDelete,
enabled = state.selection.isNotEmpty(),
) {
Text(
text = stringResource(R.string.action_delete),
color = MaterialTheme.colorScheme.onPrimary,
)
}
}
}
false -> {
EmptyScreen(message = stringResource(R.string.database_clean))
}
}
}
}

View File

@ -1,31 +0,0 @@
package eu.kanade.presentation.more.settings.database.components
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import eu.kanade.tachiyomi.R
@Composable
fun ClearDatabaseDeleteDialog(
onDismissRequest: () -> Unit,
onDelete: () -> Unit,
) {
AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDelete) {
Text(text = stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(android.R.string.cancel))
}
},
text = {
Text(text = stringResource(R.string.clear_database_confirmation))
},
)
}

View File

@ -1,53 +0,0 @@
package eu.kanade.presentation.more.settings.database.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.browse.components.SourceIcon
import eu.kanade.presentation.util.selectedBackground
import eu.kanade.tachiyomi.R
@Composable
fun ClearDatabaseItem(
source: Source,
count: Long,
isSelected: Boolean,
onClickSelect: () -> Unit,
) {
Row(
modifier = Modifier
.selectedBackground(isSelected)
.clickable(onClick = onClickSelect)
.padding(horizontal = 8.dp)
.height(56.dp),
verticalAlignment = Alignment.CenterVertically,
) {
SourceIcon(source = source)
Column(
modifier = Modifier
.padding(start = 8.dp)
.weight(1f),
) {
Text(
text = source.visualName,
style = MaterialTheme.typography.bodyMedium,
)
Text(text = stringResource(R.string.clear_database_source_item_count, count))
}
Checkbox(
checked = isSelected,
onCheckedChange = { onClickSelect() },
)
}
}

View File

@ -1,45 +0,0 @@
package eu.kanade.presentation.more.settings.database.components
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.more.settings.database.ClearDatabaseState
import eu.kanade.tachiyomi.R
@Composable
fun ClearDatabaseToolbar(
state: ClearDatabaseState,
navigateUp: () -> Unit,
onClickSelectAll: () -> Unit,
onClickInvertSelection: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior,
) {
AppBar(
title = stringResource(R.string.pref_clear_database),
navigateUp = navigateUp,
actions = {
if (state.isEmpty.not()) {
AppBarActions(
actions = listOf(
AppBar.Action(
title = stringResource(R.string.action_select_all),
icon = Icons.Outlined.SelectAll,
onClick = onClickSelectAll,
),
AppBar.Action(
title = stringResource(R.string.action_select_all),
icon = Icons.Outlined.FlipToBack,
onClick = onClickInvertSelection,
),
),
)
}
},
scrollBehavior = scrollBehavior,
)
}

View File

@ -0,0 +1,254 @@
package eu.kanade.presentation.more.settings.screen
import android.content.Context
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Public
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import com.bluelinelabs.conductor.Router
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.LinkIcon
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.more.LogoHeader
import eu.kanade.presentation.more.about.LicensesScreen
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.util.LocalBackPress
import eu.kanade.presentation.util.LocalRouter
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
import eu.kanade.tachiyomi.ui.more.NewUpdateDialogController
import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.launch
import logcat.LogPriority
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
class AboutScreen : Screen {
@Composable
override fun Content() {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val uriHandler = LocalUriHandler.current
val handleBack = LocalBackPress.current
val navigator = LocalNavigator.currentOrThrow
val router = LocalRouter.currentOrThrow
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = stringResource(R.string.pref_category_about),
navigateUp = if (handleBack != null) handleBack::invoke else null,
scrollBehavior = scrollBehavior,
)
},
) { contentPadding ->
ScrollbarLazyColumn(
contentPadding = contentPadding,
) {
item {
LogoHeader()
}
item {
TextPreferenceWidget(
title = stringResource(R.string.version),
subtitle = getVersionName(withBuildDate = true),
onPreferenceClick = {
val deviceInfo = CrashLogUtil(context).getDebugInfo()
context.copyToClipboard("Debug information", deviceInfo)
},
)
}
if (BuildConfig.INCLUDE_UPDATER) {
item {
TextPreferenceWidget(
title = stringResource(R.string.check_for_updates),
onPreferenceClick = {
scope.launch {
checkVersion(context, router)
}
},
)
}
}
if (!BuildConfig.DEBUG) {
item {
TextPreferenceWidget(
title = stringResource(R.string.whats_new),
onPreferenceClick = { uriHandler.openUri(RELEASE_URL) },
)
}
}
item {
TextPreferenceWidget(
title = stringResource(R.string.help_translate),
onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/help/contribution/#translation") },
)
}
item {
TextPreferenceWidget(
title = stringResource(R.string.licenses),
onPreferenceClick = { navigator.push(LicensesScreen()) },
)
}
item {
TextPreferenceWidget(
title = stringResource(R.string.privacy_policy),
onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/privacy") },
)
}
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.Center,
) {
LinkIcon(
label = stringResource(R.string.website),
painter = rememberVectorPainter(Icons.Outlined.Public),
url = "https://tachiyomi.org",
)
LinkIcon(
label = "Discord",
painter = painterResource(R.drawable.ic_discord_24dp),
url = "https://discord.gg/tachiyomi",
)
LinkIcon(
label = "Twitter",
painter = painterResource(R.drawable.ic_twitter_24dp),
url = "https://twitter.com/tachiyomiorg",
)
LinkIcon(
label = "Facebook",
painter = painterResource(R.drawable.ic_facebook_24dp),
url = "https://facebook.com/tachiyomiorg",
)
LinkIcon(
label = "Reddit",
painter = painterResource(R.drawable.ic_reddit_24dp),
url = "https://www.reddit.com/r/Tachiyomi",
)
LinkIcon(
label = "GitHub",
painter = painterResource(R.drawable.ic_github_24dp),
url = "https://github.com/tachiyomiorg",
)
}
}
}
}
}
/**
* Checks version and shows a user prompt if an update is available.
*/
private suspend fun checkVersion(context: Context, router: Router) {
val updateChecker = AppUpdateChecker()
withUIContext {
context.toast(R.string.update_check_look_for_updates)
try {
when (val result = withIOContext { updateChecker.checkForUpdate(context, isUserPrompt = true) }) {
is AppUpdateResult.NewUpdate -> {
NewUpdateDialogController(result).showDialog(router)
}
is AppUpdateResult.NoNewUpdate -> {
context.toast(R.string.update_check_no_new_updates)
}
else -> {}
}
} catch (e: Exception) {
context.toast(e.message)
logcat(LogPriority.ERROR, e)
}
}
}
companion object {
fun getVersionName(withBuildDate: Boolean): String {
return when {
BuildConfig.DEBUG -> {
"Debug ${BuildConfig.COMMIT_SHA}".let {
if (withBuildDate) {
"$it (${getFormattedBuildTime()}"
} else {
it
}
}
}
BuildConfig.PREVIEW -> {
"Preview r${BuildConfig.COMMIT_COUNT}".let {
if (withBuildDate) {
"$it (${BuildConfig.COMMIT_SHA}, ${getFormattedBuildTime()})"
} else {
"$it (${BuildConfig.COMMIT_SHA})"
}
}
}
else -> {
"Stable ${BuildConfig.VERSION_NAME}".let {
if (withBuildDate) {
"$it (${getFormattedBuildTime()})"
} else {
it
}
}
}
}
}
private fun getFormattedBuildTime(): String {
return try {
val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US)
inputDf.timeZone = TimeZone.getTimeZone("UTC")
val buildTime = inputDf.parse(BuildConfig.BUILD_TIME)
val outputDf = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM,
DateFormat.SHORT,
Locale.getDefault(),
)
outputDf.timeZone = TimeZone.getDefault()
buildTime!!.toDateTimestampString(UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat().get()))
} catch (e: Exception) {
BuildConfig.BUILD_TIME
}
}
}
}

View File

@ -1,19 +1,26 @@
package eu.kanade.presentation.more.settings.screen
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@ -27,6 +34,7 @@ import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga
import eu.kanade.domain.source.model.Source
import eu.kanade.domain.source.model.SourceWithCount
import eu.kanade.presentation.browse.components.SourceIcon
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.Divider
@ -34,8 +42,7 @@ import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.more.settings.database.components.ClearDatabaseDeleteDialog
import eu.kanade.presentation.more.settings.database.components.ClearDatabaseItem
import eu.kanade.presentation.util.selectedBackground
import eu.kanade.tachiyomi.Database
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.lang.launchIO
@ -58,13 +65,27 @@ class ClearDatabaseScreen : Screen {
is ClearDatabaseScreenModel.State.Loading -> LoadingScreen()
is ClearDatabaseScreenModel.State.Ready -> {
if (s.showConfirmation) {
ClearDatabaseDeleteDialog(
AlertDialog(
onDismissRequest = model::hideConfirmation,
onDelete = {
model.removeMangaBySourceId()
model.clearSelection()
model.hideConfirmation()
context.toast(R.string.clear_database_completed)
confirmButton = {
TextButton(
onClick = {
model.removeMangaBySourceId()
model.clearSelection()
model.hideConfirmation()
context.toast(R.string.clear_database_completed)
},
) {
Text(text = stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = model::hideConfirmation) {
Text(text = stringResource(android.R.string.cancel))
}
},
text = {
Text(text = stringResource(R.string.clear_database_confirmation))
},
)
}
@ -140,6 +161,40 @@ class ClearDatabaseScreen : Screen {
}
}
}
@Composable
private fun ClearDatabaseItem(
source: Source,
count: Long,
isSelected: Boolean,
onClickSelect: () -> Unit,
) {
Row(
modifier = Modifier
.selectedBackground(isSelected)
.clickable(onClick = onClickSelect)
.padding(horizontal = 8.dp)
.height(56.dp),
verticalAlignment = Alignment.CenterVertically,
) {
SourceIcon(source = source)
Column(
modifier = Modifier
.padding(start = 8.dp)
.weight(1f),
) {
Text(
text = source.visualName,
style = MaterialTheme.typography.bodyMedium,
)
Text(text = stringResource(R.string.clear_database_source_item_count, count))
}
Checkbox(
checked = isSelected,
onCheckedChange = { onClickSelect() },
)
}
}
}
private class ClearDatabaseScreenModel : StateScreenModel<ClearDatabaseScreenModel.State>(State.Loading) {

View File

@ -0,0 +1,43 @@
package eu.kanade.presentation.more.about
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R
class LicensesScreen : Screen {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = stringResource(R.string.licenses),
navigateUp = navigator::pop,
scrollBehavior = scrollBehavior,
)
},
) { contentPadding ->
LibrariesContainer(
modifier = Modifier
.fillMaxSize(),
contentPadding = contentPadding,
colors = LibraryDefaults.libraryColors(
backgroundColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
badgeBackgroundColor = MaterialTheme.colorScheme.primary,
badgeContentColor = MaterialTheme.colorScheme.onPrimary,
),
)
}
}
}

View File

@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Context
import android.os.Build
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable
@ -19,6 +20,7 @@ import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.isTablet
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.merge
import uy.kohesive.injekt.Injekt
@ -54,9 +56,25 @@ class SettingsAppearanceScreen : SearchableSettings {
val appThemePref = uiPreferences.appTheme()
val amoledPref = uiPreferences.themeDarkAmoled()
LaunchedEffect(Unit) {
themeModePref.changes()
.drop(1)
.debounce(1000)
.collectLatest {
AppCompatDelegate.setDefaultNightMode(
when (it) {
ThemeMode.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
ThemeMode.DARK -> AppCompatDelegate.MODE_NIGHT_YES
ThemeMode.SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
},
)
}
}
LaunchedEffect(Unit) {
merge(appThemePref.changes(), amoledPref.changes())
.drop(2)
.debounce(1000)
.collectLatest { (context as? Activity)?.let { ActivityCompat.recreate(it) } }
}

View File

@ -8,11 +8,12 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
@ -39,8 +40,12 @@ import androidx.core.net.toUri
import com.google.accompanist.permissions.rememberPermissionState
import com.hippo.unifile.UniFile
import eu.kanade.domain.backup.service.BackupPreferences
import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.util.collectAsState
import eu.kanade.presentation.util.isScrolledToEnd
import eu.kanade.presentation.util.isScrolledToStart
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupConst
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
@ -148,25 +153,34 @@ class SettingsBackupScreen : SearchableSettings {
onDismissRequest = onDismissRequest,
title = { Text(text = stringResource(R.string.backup_choice)) },
text = {
Column {
CreateBackupDialogItem(
isSelected = true,
title = stringResource(R.string.manga),
)
choices.forEach { (k, v) ->
val isSelected = flags.contains(k)
CreateBackupDialogItem(
isSelected = isSelected,
title = stringResource(v),
modifier = Modifier.clickable {
if (isSelected) {
flags.remove(k)
} else {
flags.add(k)
}
},
)
Box {
val state = rememberLazyListState()
ScrollbarLazyColumn(state = state) {
item {
CreateBackupDialogItem(
isSelected = true,
title = stringResource(R.string.manga),
)
}
choices.forEach { (k, v) ->
item {
val isSelected = flags.contains(k)
CreateBackupDialogItem(
isSelected = isSelected,
title = stringResource(v),
modifier = Modifier.clickable {
if (isSelected) {
flags.remove(k)
} else {
flags.add(k)
}
},
)
}
}
}
if (!state.isScrolledToStart()) Divider(modifier = Modifier.align(Alignment.TopCenter))
if (!state.isScrolledToEnd()) Divider(modifier = Modifier.align(Alignment.BottomCenter))
}
},
dismissButton = {

View File

@ -9,6 +9,7 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.core.os.LocaleListCompat
@ -17,6 +18,8 @@ import eu.kanade.domain.library.service.LibraryPreferences
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.LocaleHelper
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.xmlpull.v1.XmlPullParser
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -30,6 +33,7 @@ class SettingsGeneralScreen : SearchableSettings {
@Composable
override fun getPreferences(): List<Preference> {
val scope = rememberCoroutineScope()
val prefs = remember { Injekt.get<BasePreferences>() }
val libraryPrefs = remember { Injekt.get<LibraryPreferences>() }
return mutableListOf<Preference>().apply {
@ -71,12 +75,15 @@ class SettingsGeneralScreen : SearchableSettings {
subtitle = "%s",
entries = langs,
onValueChanged = { newValue ->
val locale = if (newValue.isEmpty()) {
LocaleListCompat.getEmptyLocaleList()
} else {
LocaleListCompat.forLanguageTags(newValue)
scope.launch {
delay(1000)
val locale = if (newValue.isEmpty()) {
LocaleListCompat.getEmptyLocaleList()
} else {
LocaleListCompat.forLanguageTags(newValue)
}
AppCompatDelegate.setApplicationLocales(locale)
}
AppCompatDelegate.setApplicationLocales(locale)
true
},
),

View File

@ -4,7 +4,8 @@ import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
@ -13,6 +14,7 @@ import androidx.compose.material.icons.outlined.Code
import androidx.compose.material.icons.outlined.CollectionsBookmark
import androidx.compose.material.icons.outlined.Explore
import androidx.compose.material.icons.outlined.GetApp
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Palette
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Security
@ -25,8 +27,11 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -35,7 +40,6 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastFirstOrNull
import androidx.core.graphics.ColorUtils
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
@ -76,7 +80,9 @@ object SettingsMainScreen : Screen {
val navigator = LocalNavigator.currentOrThrow
val backPress = LocalBackPress.currentOrThrow
val containerColor = if (twoPane) getPalerSurface() else MaterialTheme.colorScheme.surface
val topBarState = rememberTopAppBarState()
Scaffold(
topBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topBarState),
topBar = { scrollBehavior ->
// https://issuetracker.google.com/issues/249688556
MaterialTheme(
@ -114,15 +120,34 @@ object SettingsMainScreen : Screen {
},
containerColor = containerColor,
content = { contentPadding ->
LazyColumn(contentPadding = contentPadding) {
items(
val state = rememberLazyListState()
val indexSelected = if (twoPane) {
items.indexOfFirst { it.screen::class == navigator.items.first()::class }
.also {
LaunchedEffect(Unit) {
state.animateScrollToItem(it)
if (it > 0) {
// Lift scroll
topBarState.contentOffset = topBarState.heightOffsetLimit
}
}
}
} else {
null
}
LazyColumn(
state = state,
contentPadding = contentPadding,
) {
itemsIndexed(
items = items,
key = { it.hashCode() },
) { item ->
key = { _, item -> item.hashCode() },
) { index, item ->
val selected = indexSelected == index
var modifier: Modifier = Modifier
var contentColor = LocalContentColor.current
if (twoPane) {
val selected = navigator.items.fastFirstOrNull { it::class == item.screen::class } != null
modifier = Modifier
.padding(horizontal = 8.dp)
.clip(RoundedCornerShape(24.dp))
@ -141,7 +166,7 @@ object SettingsMainScreen : Screen {
TextPreferenceWidget(
modifier = modifier,
title = stringResource(item.titleRes),
subtitle = stringResource(item.subtitleRes),
subtitle = item.formatSubtitle(),
icon = item.icon,
onPreferenceClick = { navigator.navigate(item.screen, twoPane) },
)
@ -160,6 +185,7 @@ object SettingsMainScreen : Screen {
private data class Item(
@StringRes val titleRes: Int,
@StringRes val subtitleRes: Int,
val formatSubtitle: @Composable () -> String = { stringResource(subtitleRes) },
val icon: ImageVector,
val screen: Screen,
)
@ -225,4 +251,13 @@ private val items = listOf(
icon = Icons.Outlined.Code,
screen = SettingsAdvancedScreen(),
),
Item(
titleRes = R.string.pref_category_about,
subtitleRes = 0,
formatSubtitle = {
"${stringResource(R.string.app_name)} ${AboutScreen.getVersionName(withBuildDate = false)}"
},
icon = Icons.Outlined.Info,
screen = AboutScreen(),
),
)

View File

@ -91,12 +91,15 @@ class SettingsSearchScreen : Screen {
Column {
TopAppBar(
navigationIcon = {
IconButton(onClick = navigator::pop) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
val canPop = remember { navigator.canPop }
if (canPop) {
IconButton(onClick = navigator::pop) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
},
title = {

View File

@ -78,7 +78,7 @@ private fun AppThemesList(
modifier = Modifier
.animateContentSize()
.padding(vertical = 8.dp),
contentPadding = PaddingValues(horizontal = HorizontalPadding),
contentPadding = PaddingValues(horizontal = PrefsHorizontalPadding),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(

View File

@ -53,30 +53,30 @@ internal fun BasePreferenceWidget(
) {
if (icon != null) {
Box(
modifier = Modifier.padding(start = HorizontalPadding),
modifier = Modifier.padding(start = PrefsHorizontalPadding, end = 8.dp),
content = { icon() },
)
}
Column(
modifier = Modifier
.weight(1f)
.padding(vertical = 16.dp),
.padding(vertical = PrefsVerticalPadding),
) {
if (!title.isNullOrBlank()) {
Text(
modifier = Modifier.padding(horizontal = HorizontalPadding),
modifier = Modifier.padding(horizontal = PrefsHorizontalPadding),
text = title,
overflow = TextOverflow.Ellipsis,
maxLines = 2,
style = MaterialTheme.typography.titleLarge,
fontSize = 20.sp,
fontSize = TitleFontSize,
)
}
subcomponent?.invoke(this)
}
if (widget != null) {
Box(
modifier = Modifier.padding(end = HorizontalPadding),
modifier = Modifier.padding(end = PrefsHorizontalPadding),
content = { widget() },
)
}
@ -117,4 +117,6 @@ internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier = comp
}
internal val TrailingWidgetBuffer = 16.dp
internal val HorizontalPadding = 24.dp
internal val PrefsHorizontalPadding = 16.dp
internal val PrefsVerticalPadding = 16.dp
internal val TitleFontSize = 16.sp

View File

@ -23,7 +23,7 @@ import eu.kanade.tachiyomi.R
internal fun InfoWidget(text: String) {
Column(
modifier = Modifier
.padding(horizontal = HorizontalPadding, vertical = 16.dp)
.padding(horizontal = PrefsHorizontalPadding, vertical = 16.dp)
.secondaryItemAlpha(),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
@ -33,7 +33,7 @@ internal fun InfoWidget(text: String) {
)
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
style = MaterialTheme.typography.bodySmall,
)
}
}

View File

@ -21,7 +21,7 @@ fun PreferenceGroupHeader(title: String) {
Text(
text = title,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.padding(horizontal = 24.dp),
modifier = Modifier.padding(horizontal = PrefsHorizontalPadding),
style = MaterialTheme.typography.bodyMedium,
)
}

View File

@ -33,9 +33,9 @@ fun TextPreferenceWidget(
Text(
text = subtitle,
modifier = Modifier
.padding(horizontal = HorizontalPadding)
.padding(horizontal = PrefsHorizontalPadding)
.secondaryItemAlpha(),
style = MaterialTheme.typography.bodyMedium,
style = MaterialTheme.typography.bodySmall,
maxLines = 10,
)
}

View File

@ -22,7 +22,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
@Composable
@ -40,7 +39,7 @@ fun TrackingPreferenceWidget(
modifier = modifier
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
.fillMaxWidth()
.padding(horizontal = HorizontalPadding, vertical = 8.dp),
.padding(horizontal = PrefsHorizontalPadding, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
@ -62,7 +61,7 @@ fun TrackingPreferenceWidget(
.padding(horizontal = 16.dp),
maxLines = 1,
style = MaterialTheme.typography.titleLarge,
fontSize = 20.sp,
fontSize = TitleFontSize,
)
if (checked) {
Icon(

View File

@ -11,7 +11,6 @@ import android.content.IntentFilter
import android.os.Build
import android.os.Looper
import android.webkit.WebView
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import androidx.glance.appwidget.GlanceAppWidgetManager
@ -28,8 +27,6 @@ import coil.util.DebugLogger
import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.DomainModule
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.ThemeMode
import eu.kanade.tachiyomi.crash.CrashActivity
import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
import eu.kanade.tachiyomi.data.coil.DomainMangaKeyer
@ -42,7 +39,6 @@ import eu.kanade.tachiyomi.glance.UpdatesGridGlanceWidget
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
import eu.kanade.tachiyomi.util.preference.asHotFlow
import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.system.isDevFlavor
@ -67,7 +63,6 @@ import java.security.Security
class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
private val basePreferences: BasePreferences by injectLazy()
private val uiPreferences: UiPreferences by injectLazy()
private val networkPreferences: NetworkPreferences by injectLazy()
private val disableIncognitoReceiver = DisableIncognitoReceiver()
@ -126,17 +121,6 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
}
.launchIn(ProcessLifecycleOwner.get().lifecycleScope)
uiPreferences.themeMode()
.asHotFlow {
AppCompatDelegate.setDefaultNightMode(
when (it) {
ThemeMode.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
ThemeMode.DARK -> AppCompatDelegate.MODE_NIGHT_YES
ThemeMode.SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
},
)
}.launchIn(ProcessLifecycleOwner.get().lifecycleScope)
// Updates widget update
Injekt.get<DatabaseHandler>()
.subscribeToList { updatesViewQueries.updates(after = UpdatesGridGlanceWidget.DateLimit.timeInMillis) }

View File

@ -1,86 +0,0 @@
package eu.kanade.tachiyomi.ui.more
import androidx.compose.runtime.Composable
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.more.about.AboutScreen
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast
import logcat.LogPriority
import uy.kohesive.injekt.injectLazy
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
class AboutController : BasicFullComposeController() {
private val preferences: UiPreferences by injectLazy()
private val updateChecker by lazy { AppUpdateChecker() }
@Composable
override fun ComposeContent() {
AboutScreen(
navigateUp = router::popCurrentController,
checkVersion = this::checkVersion,
getFormattedBuildTime = this::getFormattedBuildTime,
onClickLicenses = { router.pushController(LicensesController()) },
)
}
/**
* Checks version and shows a user prompt if an update is available.
*/
private fun checkVersion() {
if (activity == null) return
activity!!.toast(R.string.update_check_look_for_updates)
viewScope.launchIO {
try {
val result = updateChecker.checkForUpdate(activity!!, isUserPrompt = true)
withUIContext {
when (result) {
is AppUpdateResult.NewUpdate -> {
NewUpdateDialogController(result).showDialog(router)
}
is AppUpdateResult.NoNewUpdate -> {
activity?.toast(R.string.update_check_no_new_updates)
}
else -> {}
}
}
} catch (e: Exception) {
withUIContext { activity?.toast(e.message) }
logcat(LogPriority.ERROR, e)
}
}
}
private fun getFormattedBuildTime(): String {
return try {
val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US)
inputDf.timeZone = TimeZone.getTimeZone("UTC")
val buildTime = inputDf.parse(BuildConfig.BUILD_TIME)
val outputDf = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM,
DateFormat.SHORT,
Locale.getDefault(),
)
outputDf.timeZone = TimeZone.getDefault()
buildTime!!.toDateTimestampString(UiPreferences.dateFormat(preferences.dateFormat().get()))
} catch (e: Exception) {
BuildConfig.BUILD_TIME
}
}
}

View File

@ -1,15 +0,0 @@
package eu.kanade.tachiyomi.ui.more
import androidx.compose.runtime.Composable
import eu.kanade.presentation.more.about.LicensesScreen
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
class LicensesController : BasicFullComposeController() {
@Composable
override fun ComposeContent() {
LicensesScreen(
navigateUp = router::popCurrentController,
)
}
}

View File

@ -21,9 +21,9 @@ class MoreController :
presenter = presenter,
onClickDownloadQueue = { router.pushController(DownloadController()) },
onClickCategories = { router.pushController(CategoryController()) },
onClickBackupAndRestore = { router.pushController(SettingsMainController(toBackupScreen = true)) },
onClickBackupAndRestore = { router.pushController(SettingsMainController.toBackupScreen()) },
onClickSettings = { router.pushController(SettingsMainController()) },
onClickAbout = { router.pushController(AboutController()) },
onClickAbout = { router.pushController(SettingsMainController.toAboutScreen()) },
)
}

View File

@ -9,6 +9,7 @@ import cafe.adriel.voyager.core.stack.StackEvent
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.transitions.ScreenTransition
import eu.kanade.presentation.components.TwoPanelBox
import eu.kanade.presentation.more.settings.screen.AboutScreen
import eu.kanade.presentation.more.settings.screen.SettingsBackupScreen
import eu.kanade.presentation.more.settings.screen.SettingsGeneralScreen
import eu.kanade.presentation.more.settings.screen.SettingsMainScreen
@ -19,14 +20,10 @@ import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
import soup.compose.material.motion.animation.materialSharedAxisX
import soup.compose.material.motion.animation.rememberSlideDistance
class SettingsMainController : BasicFullComposeController {
@Suppress("unused")
constructor(bundle: Bundle) : this(bundle.getBoolean(TO_BACKUP_SCREEN))
constructor(toBackupScreen: Boolean = false) : super(bundleOf(TO_BACKUP_SCREEN to toBackupScreen))
class SettingsMainController(bundle: Bundle = bundleOf()) : BasicFullComposeController(bundle) {
private val toBackupScreen = args.getBoolean(TO_BACKUP_SCREEN)
private val toAboutScreen = args.getBoolean(TO_ABOUT_SCREEN)
@Composable
override fun ComposeContent() {
@ -34,7 +31,13 @@ class SettingsMainController : BasicFullComposeController {
val widthSizeClass = calculateWindowWidthSizeClass()
if (widthSizeClass == WindowWidthSizeClass.Compact) {
Navigator(
screen = if (toBackupScreen) SettingsBackupScreen() else SettingsMainScreen,
screen = if (toBackupScreen) {
SettingsBackupScreen()
} else if (toAboutScreen) {
AboutScreen()
} else {
SettingsMainScreen
},
content = {
CompositionLocalProvider(LocalBackPress provides this::back) {
val slideDistance = rememberSlideDistance()
@ -52,7 +55,13 @@ class SettingsMainController : BasicFullComposeController {
)
} else {
Navigator(
screen = if (toBackupScreen) SettingsBackupScreen() else SettingsGeneralScreen(),
screen = if (toBackupScreen) {
SettingsBackupScreen()
} else if (toAboutScreen) {
AboutScreen()
} else {
SettingsGeneralScreen()
},
) {
TwoPanelBox(
startContent = {
@ -81,6 +90,17 @@ class SettingsMainController : BasicFullComposeController {
private fun back() {
activity?.onBackPressed()
}
companion object {
fun toBackupScreen(): SettingsMainController {
return SettingsMainController(bundleOf(TO_BACKUP_SCREEN to true))
}
fun toAboutScreen(): SettingsMainController {
return SettingsMainController(bundleOf(TO_ABOUT_SCREEN to true))
}
}
}
private const val TO_BACKUP_SCREEN = "to_backup_screen"
private const val TO_ABOUT_SCREEN = "to_about_screen"