chore: merge changes from upstream.

Signed-off-by: KaiserBh <kaiserbh@proton.me>
This commit is contained in:
KaiserBh
2024-01-14 06:02:16 +11:00
83 changed files with 1226 additions and 653 deletions

View File

@@ -40,11 +40,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.icerock.moko.resources.StringResource
import eu.kanade.presentation.browse.components.BaseBrowseItem
import eu.kanade.presentation.browse.components.ExtensionIcon
import eu.kanade.presentation.components.WarningBanner
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep
@@ -52,6 +55,7 @@ import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.material.PullRefresh
@@ -59,6 +63,7 @@ import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.EmptyScreenAction
import tachiyomi.presentation.core.screens.LoadingScreen
import tachiyomi.presentation.core.theme.header
import tachiyomi.presentation.core.util.plus
@@ -80,6 +85,8 @@ fun ExtensionScreen(
onClickUpdateAll: () -> Unit,
onRefresh: () -> Unit,
) {
val navigator = LocalNavigator.currentOrThrow
PullRefresh(
refreshing = state.isRefreshing,
onRefresh = onRefresh,
@@ -96,6 +103,13 @@ fun ExtensionScreen(
EmptyScreen(
stringRes = msg,
modifier = Modifier.padding(contentPadding),
actions = persistentListOf(
EmptyScreenAction(
stringRes = MR.strings.label_extension_repos,
icon = Icons.Outlined.Settings,
onClick = { navigator.push(ExtensionReposScreen()) },
),
),
)
}
else -> {

View File

@@ -6,7 +6,7 @@ import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.PreviewLightDark
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.screens.EmptyScreen
@@ -15,7 +15,7 @@ import tachiyomi.presentation.core.screens.EmptyScreenAction
@PreviewLightDark
@Composable
private fun NoActionPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
EmptyScreen(
stringRes = MR.strings.empty_screen,
@@ -27,7 +27,7 @@ private fun NoActionPreview() {
@PreviewLightDark
@Composable
private fun WithActionPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
EmptyScreen(
stringRes = MR.strings.empty_screen,

View File

@@ -101,6 +101,6 @@ data class TabContent(
val titleRes: StringResource,
val badgeNumber: Int? = null,
val searchEnabled: Boolean = false,
val actions: ImmutableList<AppBar.Action> = persistentListOf(),
val actions: ImmutableList<AppBar.AppBarAction> = persistentListOf(),
val content: @Composable (contentPadding: PaddingValues, snackbarHostState: SnackbarHostState) -> Unit,
)

View File

@@ -14,7 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.PreviewLightDark
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.tachiyomi.util.CrashLogUtil
import kotlinx.coroutines.launch
import tachiyomi.i18n.MR
@@ -63,7 +63,7 @@ fun CrashScreen(
@PreviewLightDark
@Composable
private fun CrashScreenPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
CrashScreen(exception = RuntimeException("Dummy")) {}
}
}

View File

@@ -17,7 +17,7 @@ import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.components.relativeDateText
import eu.kanade.presentation.history.components.HistoryItem
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.domain.history.model.HistoryWithRelations
@@ -143,7 +143,7 @@ internal fun HistoryScreenPreviews(
@PreviewParameter(HistoryScreenModelStateProvider::class)
historyState: HistoryScreenModel.State,
) {
TachiyomiTheme {
TachiyomiPreviewTheme {
HistoryScreen(
state = historyState,
snackbarHostState = SnackbarHostState(),

View File

@@ -12,7 +12,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.PreviewLightDark
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.material.padding
@@ -91,7 +91,7 @@ fun HistoryDeleteAllDialog(
@PreviewLightDark
@Composable
private fun HistoryDeleteDialogPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
HistoryDeleteDialog(
onDismissRequest = {},
onDelete = {},

View File

@@ -23,7 +23,7 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.presentation.util.formatChapterNumber
import eu.kanade.tachiyomi.util.lang.toTimestampString
import tachiyomi.domain.history.model.HistoryWithRelations
@@ -98,7 +98,7 @@ private fun HistoryItemPreviews(
@PreviewParameter(HistoryWithRelationsProvider::class)
historyWithRelations: HistoryWithRelations,
) {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
HistoryItem(
history = historyWithRelations,

View File

@@ -6,7 +6,7 @@ import androidx.compose.material.icons.outlined.Folder
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.PreviewLightDark
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import tachiyomi.presentation.core.components.Badge
@Composable
@@ -50,7 +50,7 @@ internal fun LanguageBadge(
@PreviewLightDark
@Composable
private fun BadgePreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Column {
DownloadsBadge(count = 10)
UnreadBadge(count = 10)

View File

@@ -37,6 +37,7 @@ fun LibraryToolbar(
onClickRefresh: () -> Unit,
onClickGlobalUpdate: () -> Unit,
onClickOpenRandomManga: () -> Unit,
onClickSyncNow: () -> Unit,
searchQuery: String?,
onSearchQueryChange: (String?) -> Unit,
scrollBehavior: TopAppBarScrollBehavior?,
@@ -56,6 +57,7 @@ fun LibraryToolbar(
onClickRefresh = onClickRefresh,
onClickGlobalUpdate = onClickGlobalUpdate,
onClickOpenRandomManga = onClickOpenRandomManga,
onClickSyncNow = onClickSyncNow,
scrollBehavior = scrollBehavior,
)
}
@@ -70,6 +72,7 @@ private fun LibraryRegularToolbar(
onClickRefresh: () -> Unit,
onClickGlobalUpdate: () -> Unit,
onClickOpenRandomManga: () -> Unit,
onClickSyncNow: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior?,
) {
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f
@@ -115,6 +118,10 @@ private fun LibraryRegularToolbar(
title = stringResource(MR.strings.action_open_random_manga),
onClick = onClickOpenRandomManga,
),
AppBar.OverflowAction(
title = stringResource(MR.strings.sync_library),
onClick = onClickSyncNow,
),
),
)
},

View File

@@ -11,7 +11,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.PreviewLightDark
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.pluralStringResource
@@ -44,7 +44,7 @@ fun MissingChapterCountListItem(
@PreviewLightDark
@Composable
private fun Preview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
MissingChapterCountListItem(count = 42)
}

View File

@@ -19,7 +19,7 @@ import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.ui.RichTextStyle
import com.halilibo.richtext.ui.material3.RichText
import com.halilibo.richtext.ui.string.RichTextStringStyle
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
@@ -69,7 +69,7 @@ fun NewUpdateScreen(
@PreviewLightDark
@Composable
private fun NewUpdateScreenPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
NewUpdateScreen(
versionName = "v0.99.9",
changelogInfo = """

View File

@@ -13,7 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
@@ -61,7 +61,7 @@ const val GETTING_STARTED_URL = "https://tachiyomi.org/docs/guides/getting-start
@PreviewLightDark
@Composable
private fun GuidesStepPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
GuidesStep(
onRestoreBackup = {},
).Content()

View File

@@ -40,6 +40,7 @@ import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen
import eu.kanade.presentation.more.settings.screen.data.StorageInfo
import eu.kanade.presentation.more.settings.screen.data.SyncOptionsScreen
import eu.kanade.presentation.more.settings.screen.data.SyncSettingsSelector
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
import eu.kanade.presentation.util.relativeTimeSpanString
@@ -463,24 +464,7 @@ object SettingsDataScreen : SearchableSettings {
@Composable
private fun getSyncNowPref(): Preference.PreferenceGroup {
val scope = rememberCoroutineScope()
var showDialog by remember { mutableStateOf(false) }
val context = LocalContext.current
if (showDialog) {
SyncConfirmationDialog(
onConfirm = {
showDialog = false
scope.launch {
if (!SyncDataJob.isAnyJobRunning(context)) {
SyncDataJob.startNow(context)
} else {
context.toast(MR.strings.sync_in_progress)
}
}
},
onDismissRequest = { showDialog = false },
)
}
val navigator = LocalNavigator.currentOrThrow
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_sync_now_group_title),
preferenceItems = persistentListOf(
@@ -489,7 +473,7 @@ object SettingsDataScreen : SearchableSettings {
title = stringResource(MR.strings.pref_sync_now),
subtitle = stringResource(MR.strings.pref_sync_now_subtitle),
onClick = {
showDialog = true
navigator.push(SyncSettingsSelector())
},
),
),
@@ -540,26 +524,4 @@ object SettingsDataScreen : SearchableSettings {
),
)
}
@Composable
private fun SyncConfirmationDialog(
onConfirm: () -> Unit,
onDismissRequest: () -> Unit,
) {
AlertDialog(
onDismissRequest = onDismissRequest,
title = { Text(text = stringResource(MR.strings.pref_sync_confirmation_title)) },
text = { Text(text = stringResource(MR.strings.pref_sync_confirmation_message)) },
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_cancel))
}
},
confirmButton = {
TextButton(onClick = onConfirm) {
Text(text = stringResource(MR.strings.action_ok))
}
},
)
}
}

View File

@@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.outlined.ContentCopy
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
@@ -18,8 +19,12 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import eu.kanade.tachiyomi.util.system.copyToClipboard
import kotlinx.collections.immutable.ImmutableSet
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
@Composable
fun ExtensionReposContent(
@@ -53,6 +58,8 @@ private fun ExtensionRepoListItem(
onDelete: () -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
ElevatedCard(
modifier = modifier,
) {
@@ -74,8 +81,23 @@ private fun ExtensionRepoListItem(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
IconButton(
onClick = {
val url = "$repo/index.min.json"
context.copyToClipboard(url, url)
},
) {
Icon(
imageVector = Icons.Outlined.ContentCopy,
contentDescription = stringResource(MR.strings.action_copy_to_clipboard),
)
}
IconButton(onClick = onDelete) {
Icon(imageVector = Icons.Outlined.Delete, contentDescription = null)
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = stringResource(MR.strings.action_delete),
)
}
}
}

View File

@@ -0,0 +1,142 @@
package eu.kanade.presentation.more.settings.screen.data
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.data.backup.create.BackupOptions
import eu.kanade.tachiyomi.data.sync.SyncDataJob
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.update
import tachiyomi.domain.sync.SyncOptions
import tachiyomi.domain.sync.SyncPreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.LazyColumnWithAction
import tachiyomi.presentation.core.components.SectionCard
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class SyncSettingsSelector : Screen() {
@Composable
override fun Content() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val model = rememberScreenModel { SyncSettingsSelectorModel() }
val state by model.state.collectAsState()
Scaffold(
topBar = {
AppBar(
title = stringResource(MR.strings.pref_choose_what_to_sync),
navigateUp = navigator::pop,
scrollBehavior = it,
)
},
) { contentPadding ->
LazyColumnWithAction(
contentPadding = contentPadding,
actionLabel = stringResource(MR.strings.label_sync),
actionEnabled = state.options.anyEnabled(),
onClickAction = {
if (!SyncDataJob.isAnyJobRunning(context)) {
model.syncNow(context)
navigator.pop()
} else {
context.toast(MR.strings.sync_in_progress)
}
},
) {
item {
SectionCard(MR.strings.label_library) {
Options(BackupOptions.libraryOptions, state, model)
}
}
item {
SectionCard(MR.strings.label_settings) {
Options(BackupOptions.settingsOptions, state, model)
}
}
}
}
}
@Composable
private fun Options(
options: ImmutableList<BackupOptions.Entry>,
state: SyncSettingsSelectorModel.State,
model: SyncSettingsSelectorModel,
) {
options.forEach { option ->
LabeledCheckbox(
label = stringResource(option.label),
checked = option.getter(state.options),
onCheckedChange = {
model.toggle(option.setter, it)
},
enabled = option.enabled(state.options),
)
}
}
}
private class SyncSettingsSelectorModel(
val syncPreferences: SyncPreferences = Injekt.get(),
) : StateScreenModel<SyncSettingsSelectorModel.State>(
State(syncOptionsToBackupOptions(syncPreferences.getSyncOptions())),
) {
fun toggle(setter: (BackupOptions, Boolean) -> BackupOptions, enabled: Boolean) {
mutableState.update {
val updatedOptions = setter(it.options, enabled)
syncPreferences.setSyncOptions(backupOptionsToSyncOptions(updatedOptions))
it.copy(options = updatedOptions)
}
}
fun syncNow(context: Context) {
SyncDataJob.startNow(context)
}
@Immutable
data class State(
val options: BackupOptions = BackupOptions(),
) companion object {
private fun syncOptionsToBackupOptions(syncOptions: SyncOptions): BackupOptions {
return BackupOptions(
libraryEntries = syncOptions.libraryEntries,
categories = syncOptions.categories,
chapters = syncOptions.chapters,
tracking = syncOptions.tracking,
history = syncOptions.history,
appSettings = syncOptions.appSettings,
sourceSettings = syncOptions.sourceSettings,
privateSettings = syncOptions.privateSettings,
)
}
private fun backupOptionsToSyncOptions(backupOptions: BackupOptions): SyncOptions {
return SyncOptions(
libraryEntries = backupOptions.libraryEntries,
categories = backupOptions.categories,
chapters = backupOptions.chapters,
tracking = backupOptions.tracking,
history = backupOptions.history,
appSettings = backupOptions.appSettings,
sourceSettings = backupOptions.sourceSettings,
privateSettings = backupOptions.privateSettings,
)
}
}
}

View File

@@ -42,15 +42,19 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.AppTheme
import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
import tachiyomi.core.preference.InMemoryPreferenceStore
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.secondaryItemAlpha
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.fullType
@Composable
internal fun AppThemePreferenceWidget(
@@ -258,7 +262,8 @@ fun AppThemePreviewItem(
@Composable
private fun AppThemesListPreview() {
var appTheme by remember { mutableStateOf(AppTheme.DEFAULT) }
TachiyomiTheme {
Injekt.addSingleton(fullType<UiPreferences>(), UiPreferences(InMemoryPreferenceStore()))
TachiyomiTheme(appTheme = appTheme) {
Surface {
AppThemesList(
currentTheme = appTheme,

View File

@@ -12,7 +12,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.PreviewLightDark
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
@@ -43,7 +43,7 @@ internal fun InfoWidget(text: String) {
@PreviewLightDark
@Composable
private fun InfoWidgetPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
InfoWidget(text = stringResource(MR.strings.download_ahead_info))
}

View File

@@ -10,7 +10,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.PreviewLightDark
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
@Composable
fun SwitchPreferenceWidget(
@@ -40,7 +40,7 @@ fun SwitchPreferenceWidget(
@PreviewLightDark
@Composable
private fun SwitchPreferenceWidgetPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
Column {
SwitchPreferenceWidget(

View File

@@ -13,7 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.PreviewLightDark
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import tachiyomi.presentation.core.util.secondaryItemAlpha
@Composable
@@ -62,7 +62,7 @@ fun TextPreferenceWidget(
@PreviewLightDark
@Composable
private fun TextPreferenceWidgetPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
Column {
TextPreferenceWidget(

View File

@@ -33,7 +33,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.tachiyomi.data.database.models.toDomainChapter
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
@@ -306,7 +306,7 @@ private val FakeChapterLongTitle = previewChapter(
@PreviewLightDark
@Composable
private fun TransitionTextPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface(modifier = Modifier.padding(48.dp)) {
ChapterTransition(
transition = ChapterTransition.Next(ReaderChapter(FakeChapter), ReaderChapter(FakeChapter)),
@@ -320,7 +320,7 @@ private fun TransitionTextPreview() {
@PreviewLightDark
@Composable
private fun TransitionTextLongTitlePreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface(modifier = Modifier.padding(48.dp)) {
ChapterTransition(
transition = ChapterTransition.Next(ReaderChapter(FakeChapterLongTitle), ReaderChapter(FakeChapter)),
@@ -334,7 +334,7 @@ private fun TransitionTextLongTitlePreview() {
@PreviewLightDark
@Composable
private fun TransitionTextWithGapPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface(modifier = Modifier.padding(48.dp)) {
ChapterTransition(
transition = ChapterTransition.Next(ReaderChapter(FakeChapter), ReaderChapter(FakeGapChapter)),
@@ -348,7 +348,7 @@ private fun TransitionTextWithGapPreview() {
@PreviewLightDark
@Composable
private fun TransitionTextNoNextPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface(modifier = Modifier.padding(48.dp)) {
ChapterTransition(
transition = ChapterTransition.Next(ReaderChapter(FakeChapter), null),
@@ -362,7 +362,7 @@ private fun TransitionTextNoNextPreview() {
@PreviewLightDark
@Composable
private fun TransitionTextNoPreviousPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface(modifier = Modifier.padding(48.dp)) {
ChapterTransition(
transition = ChapterTransition.Prev(ReaderChapter(FakeChapter), null),

View File

@@ -16,7 +16,7 @@ import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.manga.model.readerOrientation
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.presentation.reader.components.ModeSelectionDialog
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import tachiyomi.i18n.MR
@@ -81,7 +81,7 @@ private fun DialogContent(
@PreviewLightDark
@Composable
private fun DialogContentPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
Column {
DialogContent(

View File

@@ -12,7 +12,7 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.sp
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
@Composable
fun PageIndicatorText(
@@ -51,7 +51,7 @@ fun PageIndicatorText(
@PreviewLightDark
@Composable
private fun PageIndicatorTextPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
PageIndicatorText(currentPage = 10, totalPages = 69)
}

View File

@@ -18,7 +18,7 @@ import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.manga.model.readingMode
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.presentation.reader.components.ModeSelectionDialog
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import tachiyomi.i18n.MR
@@ -79,7 +79,7 @@ private fun DialogContent(
@PreviewLightDark
@Composable
private fun DialogContentPreview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
Column {
DialogContent(

View File

@@ -19,7 +19,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.SettingsItemsPaddings
import tachiyomi.presentation.core.components.material.padding
@@ -70,7 +70,7 @@ fun ModeSelectionDialog(
@PreviewLightDark
@Composable
private fun Preview() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
Column {
ModeSelectionDialog(

View File

@@ -28,9 +28,30 @@ fun TachiyomiTheme(
appTheme: AppTheme? = null,
amoled: Boolean? = null,
content: @Composable () -> Unit,
) {
val uiPreferences = Injekt.get<UiPreferences>()
BaseTachiyomiTheme(
appTheme = appTheme ?: uiPreferences.appTheme().get(),
isAmoled = amoled ?: uiPreferences.themeDarkAmoled().get(),
content = content,
)
}
@Composable
fun TachiyomiPreviewTheme(
appTheme: AppTheme = AppTheme.DEFAULT,
isAmoled: Boolean = false,
content: @Composable () -> Unit,
) = BaseTachiyomiTheme(appTheme, isAmoled, content)
@Composable
private fun BaseTachiyomiTheme(
appTheme: AppTheme,
isAmoled: Boolean,
content: @Composable () -> Unit,
) {
MaterialTheme(
colorScheme = getThemeColorScheme(appTheme, amoled),
colorScheme = getThemeColorScheme(appTheme, isAmoled),
content = content,
)
}
@@ -38,11 +59,10 @@ fun TachiyomiTheme(
@Composable
@ReadOnlyComposable
private fun getThemeColorScheme(
appTheme: AppTheme?,
amoled: Boolean?,
appTheme: AppTheme,
isAmoled: Boolean,
): ColorScheme {
val uiPreferences = Injekt.get<UiPreferences>()
val colorScheme = when (appTheme ?: uiPreferences.appTheme().get()) {
val colorScheme = when (appTheme) {
AppTheme.DEFAULT -> TachiyomiColorScheme
AppTheme.MONET -> MonetColorScheme(LocalContext.current)
AppTheme.GREEN_APPLE -> GreenAppleColorScheme
@@ -59,6 +79,6 @@ private fun getThemeColorScheme(
}
return colorScheme.getColorScheme(
isSystemInDarkTheme(),
amoled ?: uiPreferences.themeDarkAmoled().get(),
isAmoled,
)
}

View File

@@ -48,7 +48,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import dev.icerock.moko.resources.StringResource
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.presentation.track.components.TrackLogoIcon
import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
@@ -323,7 +323,7 @@ private fun TrackInfoDialogHomePreviews(
@PreviewParameter(TrackInfoDialogHomePreviewProvider::class)
content: @Composable () -> Unit,
) {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
content()
}

View File

@@ -32,7 +32,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import dev.icerock.moko.resources.StringResource
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableList
@@ -229,7 +229,7 @@ private fun BaseSelector(
@PreviewLightDark
@Composable
private fun TrackStatusSelectorPreviews() {
TachiyomiTheme {
TachiyomiPreviewTheme {
Surface {
TrackStatusSelector(
selection = 1,

View File

@@ -68,7 +68,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.system.openInBrowser
import tachiyomi.i18n.MR
@@ -380,5 +380,5 @@ private fun TrackerSearchPreviews(
@PreviewParameter(TrackerSearchPreviewProvider::class)
content: @Composable () -> Unit,
) {
TachiyomiTheme { content() }
TachiyomiPreviewTheme { content() }
}

View File

@@ -14,7 +14,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.tachiyomi.data.track.Tracker
import tachiyomi.presentation.core.util.clickableNoIndication
@@ -49,7 +49,7 @@ private fun TrackLogoIconPreviews(
@PreviewParameter(TrackLogoIconPreviewProvider::class)
tracker: Tracker,
) {
TachiyomiTheme {
TachiyomiPreviewTheme {
TrackLogoIcon(
tracker = tracker,
onClick = null,

View File

@@ -58,22 +58,24 @@ class MangaRestorer(
backupManga: BackupManga,
backupCategories: List<BackupCategory>,
) {
val dbManga = findExistingManga(backupManga)
val manga = backupManga.getMangaImpl()
val restoredManga = if (dbManga == null) {
restoreNewManga(manga)
} else {
restoreExistingManga(manga, dbManga)
}
handler.await(inTransaction = true) {
val dbManga = findExistingManga(backupManga)
val manga = backupManga.getMangaImpl()
val restoredManga = if (dbManga == null) {
restoreNewManga(manga)
} else {
restoreExistingManga(manga, dbManga)
}
restoreMangaDetails(
manga = restoredManga,
chapters = backupManga.chapters,
categories = backupManga.categories,
backupCategories = backupCategories,
history = backupManga.history + backupManga.brokenHistory.map { it.toBackupHistory() },
tracks = backupManga.tracking,
)
restoreMangaDetails(
manga = restoredManga,
chapters = backupManga.chapters,
categories = backupManga.categories,
backupCategories = backupCategories,
history = backupManga.history + backupManga.brokenHistory.map { it.toBackupHistory() },
tracks = backupManga.tracking,
)
}
}
private suspend fun findExistingManga(backupManga: BackupManga): Manga? {

View File

@@ -103,12 +103,15 @@ class DownloadCache(
scope.launch {
rootDownloadsDirLock.withLock {
try {
val diskCache = diskCacheFile.inputStream().use {
ProtoBuf.decodeFromByteArray<RootDirectory>(it.readBytes())
if (diskCacheFile.exists()) {
val diskCache = diskCacheFile.inputStream().use {
ProtoBuf.decodeFromByteArray<RootDirectory>(it.readBytes())
}
rootDownloadsDir = diskCache
lastRenew = System.currentTimeMillis()
}
rootDownloadsDir = diskCache
lastRenew = System.currentTimeMillis()
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Failed to initialize disk cache" }
diskCacheFile.delete()
}
}

View File

@@ -80,7 +80,6 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters)
TimeUnit.MINUTES,
)
.addTag(TAG_AUTO)
// No initial delay is needed, remove the randomDelayMillis
.build()
context.workManager.enqueueUniquePeriodicWork(TAG_AUTO, ExistingPeriodicWorkPolicy.UPDATE, request)

View File

@@ -72,16 +72,17 @@ class SyncManager(
* from the database using the BackupManager, then synchronizes the data with a sync service.
*/
suspend fun syncData() {
val syncOptions = syncPreferences.getSyncOptions()
val databaseManga = getAllMangaFromDB()
val backupOptions = BackupOptions(
libraryEntries = true,
categories = true,
chapters = true,
tracking = true,
history = true,
appSettings = true,
sourceSettings = true,
privateSettings = true,
libraryEntries = syncOptions.libraryEntries,
categories = syncOptions.categories,
chapters = syncOptions.chapters,
tracking = syncOptions.tracking,
history = syncOptions.history,
appSettings = syncOptions.appSettings,
sourceSettings = syncOptions.sourceSettings,
privateSettings = syncOptions.privateSettings,
)
val backup = Backup(
backupManga = backupCreator.backupMangas(databaseManga, backupOptions),
@@ -119,10 +120,17 @@ class SyncManager(
val remoteBackup = syncService?.doSync(syncData)
// Stop the sync early if the remote backup is null or empty
if (remoteBackup?.backupManga?.size == 0) {
notifier.showSyncError("No data found on remote server.")
return
}
// Check if it's first sync based on lastSyncTimestamp
if (syncPreferences.lastSyncTimestamp().get() == 0L && databaseManga.isNotEmpty()) {
// It's first sync no need to restore data. (just update remote data)
syncPreferences.lastSyncTimestamp().set(Date().time)
notifier.showSyncSuccess("Updated remote data successfully")
return
}
@@ -130,10 +138,8 @@ class SyncManager(
val (filteredFavorites, nonFavorites) = filterFavoritesAndNonFavorites(remoteBackup)
updateNonFavorites(nonFavorites)
val mangas = processFavoriteManga(filteredFavorites)
val newSyncData = backup.copy(
backupManga = mangas,
backupManga = filteredFavorites,
backupCategories = remoteBackup.backupCategories,
backupSources = remoteBackup.backupSources,
backupPreferences = remoteBackup.backupPreferences,
@@ -141,9 +147,10 @@ class SyncManager(
)
// It's local sync no need to restore data. (just update remote data)
if (mangas.isEmpty()) {
if (filteredFavorites.isEmpty()) {
// update the sync timestamp
syncPreferences.lastSyncTimestamp().set(Date().time)
notifier.showSyncSuccess("Sync completed successfully")
return
}
@@ -328,6 +335,7 @@ class SyncManager(
val favorites = mutableListOf<BackupManga>()
val nonFavorites = mutableListOf<BackupManga>()
val logTag = "filterFavoritesAndNonFavorites"
val elapsedTimeMillis = measureTimeMillis {
val databaseMangaFavorites = getFavorites.await()
val localMangaMap = databaseMangaFavorites.associateBy {
@@ -368,51 +376,6 @@ class SyncManager(
return Pair(favorites, nonFavorites)
}
private fun processFavoriteManga(backupManga: List<BackupManga>): List<BackupManga> {
val mangas = mutableListOf<BackupManga>()
val lastSyncTimeStamp = syncPreferences.lastSyncTimestamp().get()
val elapsedTimeMillis = measureTimeMillis {
logcat(LogPriority.DEBUG) { "Starting to process BackupMangas." }
backupManga.forEach { manga ->
val mangaLastUpdatedStatus = manga.lastModifiedAt * 1000L > lastSyncTimeStamp
val chaptersUpdatedStatus = chaptersUpdatedAfterSync(manga, lastSyncTimeStamp)
if (mangaLastUpdatedStatus || chaptersUpdatedStatus) {
mangas.add(manga)
logcat(LogPriority.DEBUG) {
"Added ${manga.title} to the process list. Manga Last Updated: $mangaLastUpdatedStatus, " +
"Chapters Updated: $chaptersUpdatedStatus."
}
} else {
logcat(LogPriority.DEBUG) {
"Skipped ${manga.title} as it has not been updated since the last sync " +
"(Last Modified: ${manga.lastModifiedAt * 1000L}, Last Sync: $lastSyncTimeStamp)."
}
}
}
}
val minutes = elapsedTimeMillis / 60000
val seconds = (elapsedTimeMillis % 60000) / 1000
logcat(LogPriority.DEBUG) { "Processing completed in ${minutes}m ${seconds}s. Total Processed: ${mangas.size}" }
return mangas
}
private fun chaptersUpdatedAfterSync(manga: BackupManga, lastSyncTimeStamp: Long): Boolean {
return manga.chapters.any { chapter ->
val updated = chapter.lastModifiedAt * 1000L > lastSyncTimeStamp
if (updated) {
logcat(LogPriority.DEBUG) {
"Chapter ${chapter.name} of ${manga.title} updated after last sync " +
"(Chapter Last Modified: ${chapter.lastModifiedAt * 1000L}, Last Sync: $lastSyncTimeStamp)."
}
}
updated
}
}
/**
* Updates the non-favorite manga in the local database with their favorite status from the backup.
* @param nonFavorites the list of non-favorite BackupManga objects from the backup.

View File

@@ -72,4 +72,15 @@ class SyncNotifier(private val context: Context) {
show(Notifications.ID_RESTORE_COMPLETE)
}
}
fun showSyncSuccess(message: String?) {
context.cancelNotification(Notifications.ID_RESTORE_PROGRESS)
with(completeNotificationBuilder) {
setContentTitle(context.getString(R.string.sync_complete))
setContentText(message)
show(Notifications.ID_RESTORE_COMPLETE)
}
}
}

View File

@@ -35,15 +35,7 @@ internal class ExtensionApi {
suspend fun findExtensions(): List<Extension.Available> {
return withIOContext {
val extensions = sourcePreferences.extensionRepos().get().flatMap { getExtensions(it) }
// Sanity check - a small number of extensions probably means something broke
// with the repo generator
if (extensions.isEmpty()) {
throw Exception()
}
extensions
sourcePreferences.extensionRepos().get().flatMap { getExtensions(it) }
}
}

View File

@@ -1,7 +1,5 @@
package eu.kanade.tachiyomi.ui.browse.extension
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Translate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -10,6 +8,7 @@ import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.ExtensionScreen
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.TabContent
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreen
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
@@ -29,11 +28,14 @@ fun extensionsTab(
badgeNumber = state.updates.takeIf { it > 0 },
searchEnabled = true,
actions = persistentListOf(
AppBar.Action(
AppBar.OverflowAction(
title = stringResource(MR.strings.action_filter),
icon = Icons.Outlined.Translate,
onClick = { navigator.push(ExtensionFilterScreen()) },
),
AppBar.OverflowAction(
title = stringResource(MR.strings.label_extension_repos),
onClick = { navigator.push(ExtensionReposScreen()) },
),
),
content = { contentPadding, _ ->
ExtensionScreen(

View File

@@ -37,12 +37,14 @@ import eu.kanade.presentation.more.onboarding.GETTING_STARTED_URL
import eu.kanade.presentation.util.Tab
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.sync.SyncDataJob
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
import eu.kanade.tachiyomi.ui.category.CategoryScreen
import eu.kanade.tachiyomi.ui.home.HomeScreen
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest
@@ -135,6 +137,13 @@ object LibraryTab : Tab {
}
}
},
onClickSyncNow = {
if (!SyncDataJob.isAnyJobRunning(context)) {
SyncDataJob.startNow(context)
} else {
context.toast(MR.strings.sync_in_progress)
}
},
searchQuery = state.searchQuery,
onSearchQueryChange = screenModel::search,
scrollBehavior = scrollBehavior.takeIf { !tabVisible }, // For scroll overlay when no tab

View File

@@ -102,7 +102,14 @@ abstract class PagerViewer(val activity: ReaderActivity) : Viewer {
},
)
pager.tapListener = { event ->
val pos = PointF(event.x / pager.width, event.y / pager.height)
val viewPosition = IntArray(2)
pager.getLocationOnScreen(viewPosition)
val viewPositionRelativeToWindow = IntArray(2)
pager.getLocationInWindow(viewPositionRelativeToWindow)
val pos = PointF(
(event.rawX - viewPosition[0] + viewPositionRelativeToWindow[0]) / pager.width,
(event.rawY - viewPosition[1] + viewPositionRelativeToWindow[1]) / pager.height,
)
when (config.navigator.getAction(pos)) {
NavigationRegion.MENU -> activity.toggleMenu()
NavigationRegion.NEXT -> moveToNext()

View File

@@ -111,7 +111,14 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
},
)
recycler.tapListener = { event ->
val pos = PointF(event.x / recycler.width, event.y / recycler.height)
val viewPosition = IntArray(2)
recycler.getLocationOnScreen(viewPosition)
val viewPositionRelativeToWindow = IntArray(2)
recycler.getLocationInWindow(viewPositionRelativeToWindow)
val pos = PointF(
(event.rawX - viewPosition[0] + viewPositionRelativeToWindow[0]) / recycler.width,
(event.rawY - viewPosition[1] + viewPositionRelativeToWindow[1]) / recycler.height,
)
when (config.navigator.getAction(pos)) {
NavigationRegion.MENU -> activity.toggleMenu()
NavigationRegion.NEXT, NavigationRegion.RIGHT -> scrollDown()