mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 14:27:57 +01:00 
			
		
		
		
	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:
		| @@ -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) }, | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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", | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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, | ||||
|             ), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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() | ||||
| } | ||||
| @@ -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)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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)) | ||||
|         }, | ||||
|     ) | ||||
| } | ||||
| @@ -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() }, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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, | ||||
|     ) | ||||
| } | ||||
| @@ -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 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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, | ||||
|                 ), | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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) } } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -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 = { | ||||
|   | ||||
| @@ -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 | ||||
|                     }, | ||||
|                 ), | ||||
|   | ||||
| @@ -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(), | ||||
|     ), | ||||
| ) | ||||
|   | ||||
| @@ -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 = { | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
|         ) | ||||
|     } | ||||
|   | ||||
| @@ -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, | ||||
|                 ) | ||||
|             } | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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) } | ||||
|   | ||||
| @@ -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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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()) }, | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user