diff --git a/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt b/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt index b2ba82016..42aaf5980 100644 --- a/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt @@ -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) }, ) } } diff --git a/app/src/main/java/eu/kanade/presentation/more/about/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/about/AboutScreen.kt deleted file mode 100644 index e14a75cab..000000000 --- a/app/src/main/java/eu/kanade/presentation/more/about/AboutScreen.kt +++ /dev/null @@ -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", - ) - } - } - } - } -} diff --git a/app/src/main/java/eu/kanade/presentation/more/about/LicensesScreen.kt b/app/src/main/java/eu/kanade/presentation/more/about/LicensesScreen.kt deleted file mode 100644 index 14bfb2222..000000000 --- a/app/src/main/java/eu/kanade/presentation/more/about/LicensesScreen.kt +++ /dev/null @@ -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, - ), - ) - } -} diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceScaffold.kt b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceScaffold.kt index 10fa1984f..913b781cf 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceScaffold.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceScaffold.kt @@ -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) { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/database/ClearDatabaseState.kt b/app/src/main/java/eu/kanade/presentation/more/settings/database/ClearDatabaseState.kt deleted file mode 100644 index f0b791e74..000000000 --- a/app/src/main/java/eu/kanade/presentation/more/settings/database/ClearDatabaseState.kt +++ /dev/null @@ -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 - val selection: List - val isEmpty: Boolean - var dialog: Dialog? -} - -fun ClearDatabaseState(): ClearDatabaseState { - return ClearDatabaseStateImpl() -} - -class ClearDatabaseStateImpl : ClearDatabaseState { - override var items: List by mutableStateOf(emptyList()) - override var selection: List 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) : Dialog() -} diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/database/components/ClearDatabaseContent.kt b/app/src/main/java/eu/kanade/presentation/more/settings/database/components/ClearDatabaseContent.kt deleted file mode 100644 index 5ea34717d..000000000 --- a/app/src/main/java/eu/kanade/presentation/more/settings/database/components/ClearDatabaseContent.kt +++ /dev/null @@ -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)) - } - } - } -} diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/database/components/ClearDatabaseDialogs.kt b/app/src/main/java/eu/kanade/presentation/more/settings/database/components/ClearDatabaseDialogs.kt deleted file mode 100644 index 60416a447..000000000 --- a/app/src/main/java/eu/kanade/presentation/more/settings/database/components/ClearDatabaseDialogs.kt +++ /dev/null @@ -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)) - }, - ) -} diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/database/components/ClearDatabaseItem.kt b/app/src/main/java/eu/kanade/presentation/more/settings/database/components/ClearDatabaseItem.kt deleted file mode 100644 index 81ff9895c..000000000 --- a/app/src/main/java/eu/kanade/presentation/more/settings/database/components/ClearDatabaseItem.kt +++ /dev/null @@ -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() }, - ) - } -} diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/database/components/ClearDatabaseToolbar.kt b/app/src/main/java/eu/kanade/presentation/more/settings/database/components/ClearDatabaseToolbar.kt deleted file mode 100644 index fce2c7b64..000000000 --- a/app/src/main/java/eu/kanade/presentation/more/settings/database/components/ClearDatabaseToolbar.kt +++ /dev/null @@ -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, - ) -} diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt new file mode 100644 index 000000000..5ec60104e --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt @@ -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().dateFormat().get())) + } catch (e: Exception) { + BuildConfig.BUILD_TIME + } + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearDatabaseScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearDatabaseScreen.kt index 5db9f824f..d388d9888 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearDatabaseScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearDatabaseScreen.kt @@ -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(State.Loading) { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/LicensesScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/LicensesScreen.kt new file mode 100644 index 000000000..823b28125 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/LicensesScreen.kt @@ -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, + ), + ) + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt index 3b59df0ee..cc909933e 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt @@ -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) } } } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt index ef1389aab..73372ff6d 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt @@ -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 = { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsGeneralScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsGeneralScreen.kt index b18a131e3..06939f9d1 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsGeneralScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsGeneralScreen.kt @@ -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 { + val scope = rememberCoroutineScope() val prefs = remember { Injekt.get() } val libraryPrefs = remember { Injekt.get() } return mutableListOf().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 }, ), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsMainScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsMainScreen.kt index 2817fdbdf..599c8cc1b 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsMainScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsMainScreen.kt @@ -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(), + ), ) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSearchScreen.kt index 8782dcba7..3b3fbe33d 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSearchScreen.kt @@ -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 = { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt index 9c20d670b..78f2d1da4 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt @@ -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( diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/BasePreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/BasePreferenceWidget.kt index ef720e77a..37b6d8d9c 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/BasePreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/BasePreferenceWidget.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/InfoWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/InfoWidget.kt index 243caaf4c..c38c16345 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/InfoWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/InfoWidget.kt @@ -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, ) } } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/PreferenceGroupHeader.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/PreferenceGroupHeader.kt index faa57ca36..d41b9a277 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/PreferenceGroupHeader.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/PreferenceGroupHeader.kt @@ -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, ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/TextPreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/TextPreferenceWidget.kt index ef5e1e526..27e2a9851 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/TextPreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/TextPreferenceWidget.kt @@ -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, ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/TrackingPreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/TrackingPreferenceWidget.kt index 582453e1a..69f7e4c5a 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/TrackingPreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/TrackingPreferenceWidget.kt @@ -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( diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index ce24e9c2b..f7d44ce91 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -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() .subscribeToList { updatesViewQueries.updates(after = UpdatesGridGlanceWidget.DateLimit.timeInMillis) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt deleted file mode 100644 index e2108310d..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt +++ /dev/null @@ -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 - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/LicensesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/LicensesController.kt deleted file mode 100644 index 9ae086919..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/LicensesController.kt +++ /dev/null @@ -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, - ) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt index 5aebd53a5..534d6c46a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt @@ -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()) }, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt index 53580fc1e..5476a2cbe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt @@ -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"