Implement Mihon's spotless PR (#1257)

* Remove detekt (mihonapp/mihon#1130)

Annoying. More annoying in this project.

(cherry picked from commit 777ae2461e1eb277a3aa0c998ff69e4f100387a1)

* Add spotless (with ktlint) (mihonapp/mihon#1136)

(cherry picked from commit 5ae8095ef1ed2ae9f98486f9148e933c77a28692)

* Address spotless lint errors (mihonapp/mihon#1138)

* Add spotless (with ktlint)

* Run spotlessApply

* screaming case screaming case screaming case

* Update PagerViewerAdapter.kt

* Update ReaderTransitionView.kt

(cherry picked from commit d6252ab7703d52ecf9f43de3ee36fd63e665a31f)

* Generate locales_config.xml in build dir

(cherry picked from commit ac41bffdc97b4cfed923de6b9e8e01cccf3eb6eb)

* Address more spotless lint errors in SY

* some more missed

* more missed

* still missing, not sure while it won't report error when running locally

* one more

* more

* more

* correct comment

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
Tran M. Cuong 2024-08-23 08:24:50 +07:00 committed by GitHub
parent 759fd4d4e3
commit 3705880a77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
363 changed files with 1223 additions and 3138 deletions

View File

@ -36,7 +36,7 @@ jobs:
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
- name: Build app - name: Build app
run: ./gradlew detekt assembleDevDebug run: ./gradlew spotlessCheck assembleDevDebug
- name: Upload APK - name: Upload APK
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

View File

@ -50,7 +50,7 @@ jobs:
# SY --> # SY -->
- name: Build app and run unit tests - name: Build app and run unit tests
run: ./gradlew detekt assembleStandardRelease testStandardReleaseUnitTest --stacktrace run: ./gradlew spotlessCheck assembleStandardRelease testStandardReleaseUnitTest --stacktrace
- name: Sign APK - name: Sign APK
uses: r0adkll/sign-android-release@v1 uses: r0adkll/sign-android-release@v1

View File

@ -24,10 +24,6 @@ Before you start, please note that the ability to use following technologies is
- [Android Studio](https://developer.android.com/studio) - [Android Studio](https://developer.android.com/studio)
- Emulator or phone with developer options enabled to test changes. - Emulator or phone with developer options enabled to test changes.
## Linting
Run the `detekt` gradle task. If the build fails, a report of issues can be found in `app/build/reports/detekt/`. The report is availble in several formats and details each issue that needs attention.
## Getting help ## Getting help
- Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing. - Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing.

View File

@ -21,7 +21,7 @@ if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
// shortcutHelper.setFilePath("./shortcuts.xml") // shortcutHelper.setFilePath("./shortcuts.xml")
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
android { android {
namespace = "eu.kanade.tachiyomi" namespace = "eu.kanade.tachiyomi"
@ -38,7 +38,7 @@ android {
buildConfigField("boolean", "INCLUDE_UPDATER", "false") buildConfigField("boolean", "INCLUDE_UPDATER", "false")
ndk { ndk {
abiFilters += SUPPORTED_ABIS abiFilters += supportedAbis
} }
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@ -47,7 +47,7 @@ android {
abi { abi {
isEnable = true isEnable = true
reset() reset()
include(*SUPPORTED_ABIS.toTypedArray()) include(*supportedAbis.toTypedArray())
isUniversalApk = true isUniversalApk = true
} }
} }
@ -243,7 +243,6 @@ dependencies {
implementation(libs.compose.webview) implementation(libs.compose.webview)
implementation(libs.compose.grid) implementation(libs.compose.grid)
// Logging // Logging
implementation(libs.logcat) implementation(libs.logcat)

View File

@ -32,7 +32,8 @@ class GetEnabledSources(
) { a, b, c -> Triple(a, b, c) }, ) { a, b, c -> Triple(a, b, c) },
// SY <-- // SY <--
repository.getSources(), repository.getSources(),
) { pinnedSourceIds, ) {
pinnedSourceIds,
(enabledLanguages, disabledSources, lastUsedSource), (enabledLanguages, disabledSources, lastUsedSource),
(excludedFromDataSaver, sourcesInCategories, sourceCategoriesFilter), (excludedFromDataSaver, sourcesInCategories, sourceCategoriesFilter),
sources, sources,

View File

@ -18,12 +18,20 @@ class UiPreferences(
fun themeMode() = preferenceStore.getEnum( fun themeMode() = preferenceStore.getEnum(
"pref_theme_mode_key", "pref_theme_mode_key",
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ThemeMode.SYSTEM } else { ThemeMode.LIGHT }, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ThemeMode.SYSTEM
} else {
ThemeMode.LIGHT
},
) )
fun appTheme() = preferenceStore.getEnum( fun appTheme() = preferenceStore.getEnum(
"pref_app_theme", "pref_app_theme",
if (DeviceUtil.isDynamicColorAvailable) { AppTheme.MONET } else { AppTheme.DEFAULT }, if (DeviceUtil.isDynamicColorAvailable) {
AppTheme.MONET
} else {
AppTheme.DEFAULT
},
) )
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false) fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)

View File

@ -240,7 +240,7 @@ private fun DetailsHeader(
Extension name: ${extension.name} (lang: ${extension.lang}; package: ${extension.pkgName}) Extension name: ${extension.name} (lang: ${extension.lang}; package: ${extension.pkgName})
Extension version: ${extension.versionName} (lib: ${extension.libVersion}; version code: ${extension.versionCode}) Extension version: ${extension.versionName} (lib: ${extension.libVersion}; version code: ${extension.versionCode})
NSFW: ${extension.isNsfw} NSFW: ${extension.isNsfw}
""".trimIndent() """.trimIndent(),
) )
if (extension is Extension.Installed) { if (extension is Extension.Installed) {
@ -251,7 +251,7 @@ private fun DetailsHeader(
Obsolete: ${extension.isObsolete} Obsolete: ${extension.isObsolete}
Shared: ${extension.isShared} Shared: ${extension.isShared}
Repository: ${extension.repoUrl} Repository: ${extension.repoUrl}
""".trimIndent() """.trimIndent(),
) )
} }
} }

View File

@ -219,7 +219,9 @@ private fun ExtensionContent(
when (it) { when (it) {
is Extension.Available -> onInstallExtension(it) is Extension.Available -> onInstallExtension(it)
is Extension.Installed -> onOpenExtension(it) is Extension.Installed -> onOpenExtension(it)
is Extension.Untrusted -> { trustState = it } is Extension.Untrusted -> {
trustState = it
}
} }
}, },
onLongClickItem = onLongClickItem, onLongClickItem = onLongClickItem,
@ -241,7 +243,9 @@ private fun ExtensionContent(
onOpenExtension(it) onOpenExtension(it)
} }
} }
is Extension.Untrusted -> { trustState = it } is Extension.Untrusted -> {
trustState = it
}
} }
}, },
) )

View File

@ -35,7 +35,7 @@ import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.LabeledCheckbox import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.components.material.topSmallPaddingValues import tachiyomi.presentation.core.components.material.topSmallPaddingValues
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@ -179,7 +179,7 @@ private fun SourcePinButton(
MaterialTheme.colorScheme.primary MaterialTheme.colorScheme.primary
} else { } else {
MaterialTheme.colorScheme.onBackground.copy( MaterialTheme.colorScheme.onBackground.copy(
alpha = SecondaryItemAlpha, alpha = SECONDARY_ALPHA,
) )
} }
val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin

View File

@ -79,7 +79,7 @@ fun TabbedDialog(
modifier = Modifier.animateContentSize(), modifier = Modifier.animateContentSize(),
state = pagerState, state = pagerState,
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
pageContent = { page -> content(page) } pageContent = { page -> content(page) },
) )
} }
} }

View File

@ -207,7 +207,6 @@ private fun ColumnScope.SortPage(
}.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary().get().isNotEmpty()) }.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary().get().isNotEmpty())
// SY <-- // SY <--
val trackerSortOption = if (trackers.isEmpty()) { val trackerSortOption = if (trackers.isEmpty()) {
emptyList() emptyList()
} else { } else {

View File

@ -62,7 +62,7 @@ private val ContinueReadingButtonIconSizeLarge = 20.dp
private val ContinueReadingButtonGridPadding = 6.dp private val ContinueReadingButtonGridPadding = 6.dp
private val ContinueReadingButtonListSpacing = 8.dp private val ContinueReadingButtonListSpacing = 8.dp
private const val GridSelectedCoverAlpha = 0.76f private const val GRID_SELECTED_COVER_ALPHA = 0.76f
/** /**
* Layout of grid list item with title overlaying the cover. * Layout of grid list item with title overlaying the cover.
@ -90,7 +90,7 @@ fun MangaCompactGridItem(
MangaCover.Book( MangaCover.Book(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha), .alpha(if (isSelected) GRID_SELECTED_COVER_ALPHA else coverAlpha),
data = coverData, data = coverData,
) )
}, },
@ -197,7 +197,7 @@ fun MangaComfortableGridItem(
MangaCover.Book( MangaCover.Book(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha), .alpha(if (isSelected) GRID_SELECTED_COVER_ALPHA else coverAlpha),
data = coverData, data = coverData,
) )
}, },
@ -371,7 +371,7 @@ fun MangaListItem(
size = ContinueReadingButtonSizeSmall, size = ContinueReadingButtonSizeSmall,
iconSize = ContinueReadingButtonIconSizeSmall, iconSize = ContinueReadingButtonIconSizeSmall,
onClick = onClickContinueReading, onClick = onClickContinueReading,
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing) modifier = Modifier.padding(start = ContinueReadingButtonListSpacing),
) )
} }
} }
@ -392,7 +392,7 @@ private fun ContinueReadingButton(
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f), containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f),
contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer), contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer),
), ),
modifier = Modifier.size(size) modifier = Modifier.size(size),
) { ) {
Icon( Icon(
imageVector = Icons.Filled.PlayArrow, imageVector = Icons.Filled.PlayArrow,

View File

@ -995,7 +995,9 @@ private fun LazyListScope.sharedChapterItems(
// SY <-- // SY <--
}, },
readProgress = item.chapter.lastPageRead readProgress = item.chapter.lastPageRead
.takeIf { /* SY --> */(!item.chapter.read || alwaysShowReadingProgress)/* SY <-- */ && it > 0L } .takeIf {
/* SY --> */(!item.chapter.read || alwaysShowReadingProgress)/* SY <-- */ && it > 0L
}
?.let { ?.let {
stringResource( stringResource(
MR.strings.chapter_progress, MR.strings.chapter_progress,

View File

@ -12,7 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.pluralStringResource import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@ -60,6 +60,6 @@ private fun MissingChaptersWarning(count: Int) {
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error.copy(alpha = SecondaryItemAlpha), color = MaterialTheme.colorScheme.error.copy(alpha = SECONDARY_ALPHA),
) )
} }

View File

@ -40,8 +40,8 @@ import eu.kanade.tachiyomi.data.download.model.Download
import me.saket.swipe.SwipeableActionsBox import me.saket.swipe.SwipeableActionsBox
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.ReadItemAlpha import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.selectedBackground import tachiyomi.presentation.core.util.selectedBackground
@ -135,7 +135,7 @@ fun MangaChapterListItem(
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
onTextLayout = { textHeight = it.size.height }, onTextLayout = { textHeight = it.size.height },
color = LocalContentColor.current.copy(alpha = if (read) ReadItemAlpha else 1f), color = LocalContentColor.current.copy(alpha = if (read) DISABLED_ALPHA else 1f),
) )
} }
@ -143,7 +143,7 @@ fun MangaChapterListItem(
val subtitleStyle = MaterialTheme.typography.bodySmall val subtitleStyle = MaterialTheme.typography.bodySmall
.merge( .merge(
color = LocalContentColor.current color = LocalContentColor.current
.copy(alpha = if (read) ReadItemAlpha else SecondaryItemAlpha) .copy(alpha = if (read) DISABLED_ALPHA else SECONDARY_ALPHA),
) )
ProvideTextStyle(value = subtitleStyle) { ProvideTextStyle(value = subtitleStyle) {
if (date != null) { if (date != null) {
@ -152,14 +152,19 @@ fun MangaChapterListItem(
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
if (readProgress != null || scanlator != null/* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText() if (readProgress != null ||
scanlator != null/* SY --> */ ||
sourceName != null/* SY <-- */
) {
DotSeparatorText()
}
} }
if (readProgress != null) { if (readProgress != null) {
Text( Text(
text = readProgress, text = readProgress,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
color = LocalContentColor.current.copy(alpha = ReadItemAlpha), color = LocalContentColor.current.copy(alpha = DISABLED_ALPHA),
) )
if (scanlator != null/* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText() if (scanlator != null/* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText()
} }

View File

@ -82,6 +82,7 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
import tachiyomi.presentation.core.components.material.TextButton import tachiyomi.presentation.core.components.material.TextButton
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.pluralStringResource import tachiyomi.presentation.core.i18n.pluralStringResource
@ -181,7 +182,7 @@ fun MangaActionRow(
// SY <-- // SY <--
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f) val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = DISABLED_ALPHA)
// TODO: show something better when using custom interval // TODO: show something better when using custom interval
val nextUpdateDays = remember(nextUpdate) { val nextUpdateDays = remember(nextUpdate) {

View File

@ -44,7 +44,7 @@ import tachiyomi.presentation.core.i18n.stringResource
@Composable @Composable
private fun PagePreviewLoading( private fun PagePreviewLoading(
setMaxWidth: (Dp) -> Unit setMaxWidth: (Dp) -> Unit,
) { ) {
val density = LocalDensity.current val density = LocalDensity.current
Box( Box(
@ -63,7 +63,7 @@ private fun PagePreviewLoading(
@Composable @Composable
private fun PagePreviewRow( private fun PagePreviewRow(
onOpenPage: (Int) -> Unit, onOpenPage: (Int) -> Unit,
items: ImmutableList<PagePreview> items: ImmutableList<PagePreview>,
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@ -88,7 +88,7 @@ private fun PagePreviewMore(
) { ) {
Box( Box(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center,
) { ) {
TextButton(onClick = onMorePreviewsClicked) { TextButton(onClick = onMorePreviewsClicked) {
Text(stringResource(SYMR.strings.more_previews)) Text(stringResource(SYMR.strings.more_previews))
@ -116,7 +116,7 @@ fun PagePreviews(
pagePreviewState.pagePreviews.take(rowCount * itemPerRowCount).chunked(itemPerRowCount).forEach { pagePreviewState.pagePreviews.take(rowCount * itemPerRowCount).chunked(itemPerRowCount).forEach {
PagePreviewRow( PagePreviewRow(
onOpenPage = onOpenPage, onOpenPage = onOpenPage,
items = remember(it) { it.toImmutableList() } items = remember(it) { it.toImmutableList() },
) )
} }
@ -153,7 +153,7 @@ fun LazyListScope.PagePreviewItems(
) { ) {
PagePreviewRow( PagePreviewRow(
onOpenPage = onOpenPage, onOpenPage = onOpenPage,
items = remember(it) { it.toImmutableList() } items = remember(it) { it.toImmutableList() },
) )
} }
item( item(

View File

@ -3,7 +3,6 @@ package eu.kanade.presentation.more.settings.screen
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.os.Build
import android.provider.Settings import android.provider.Settings
import android.webkit.WebStorage import android.webkit.WebStorage
import android.webkit.WebView import android.webkit.WebView
@ -376,7 +375,7 @@ object SettingsAdvancedScreen : SearchableSettings {
chooseColorProfile.launch(arrayOf("*/*")) chooseColorProfile.launch(arrayOf("*/*"))
}, },
), ),
) ),
) )
} }

View File

@ -180,11 +180,15 @@ object SettingsAppearanceScreen : SearchableSettings {
Preference.PreferenceItem.SliderPreference( Preference.PreferenceItem.SliderPreference(
value = previewsRowCount, value = previewsRowCount,
title = stringResource(SYMR.strings.pref_previews_row_count), title = stringResource(SYMR.strings.pref_previews_row_count),
subtitle = if (previewsRowCount > 0) pluralStringResource( subtitle = if (previewsRowCount > 0) {
pluralStringResource(
SYMR.plurals.row_count, SYMR.plurals.row_count,
previewsRowCount, previewsRowCount,
previewsRowCount, previewsRowCount,
) else stringResource(MR.strings.disabled), )
} else {
stringResource(MR.strings.disabled)
},
min = 0, min = 0,
max = 10, max = 10,
onValueChanged = { onValueChanged = {

View File

@ -94,7 +94,7 @@ object SettingsBrowseScreen : SearchableSettings {
pref = uiPreferences.feedTabInFront(), pref = uiPreferences.feedTabInFront(),
title = stringResource(SYMR.strings.pref_feed_position), title = stringResource(SYMR.strings.pref_feed_position),
subtitle = stringResource(SYMR.strings.pref_feed_position_summery), subtitle = stringResource(SYMR.strings.pref_feed_position_summery),
enabled = hideFeedTab.not() enabled = hideFeedTab.not(),
), ),
), ),
), ),

View File

@ -37,7 +37,7 @@ class OpenSourceLicensesScreen : Screen() {
name = it.name, name = it.name,
website = it.website, website = it.website,
license = it.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(), license = it.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(),
) ),
) )
}, },
) )

View File

@ -26,7 +26,6 @@ import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.LabeledCheckbox import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.LazyColumnWithAction import tachiyomi.presentation.core.components.LazyColumnWithAction
import tachiyomi.presentation.core.components.SectionCard import tachiyomi.presentation.core.components.SectionCard

View File

@ -28,7 +28,7 @@ import tachiyomi.presentation.core.i18n.stringResource
class BackupSchemaScreen : Screen() { class BackupSchemaScreen : Screen() {
companion object { companion object {
const val title = "Backup file schema" const val TITLE = "Backup file schema"
} }
@Composable @Composable
@ -41,7 +41,7 @@ class BackupSchemaScreen : Screen() {
Scaffold( Scaffold(
topBar = { topBar = {
AppBar( AppBar(
title = title, title = TITLE,
navigateUp = navigator::pop, navigateUp = navigator::pop,
actions = { actions = {
AppBarActions( AppBarActions(
@ -50,7 +50,7 @@ class BackupSchemaScreen : Screen() {
title = stringResource(MR.strings.action_copy_to_clipboard), title = stringResource(MR.strings.action_copy_to_clipboard),
icon = Icons.Default.ContentCopy, icon = Icons.Default.ContentCopy,
onClick = { onClick = {
context.copyToClipboard(title, schema) context.copyToClipboard(TITLE, schema)
}, },
), ),
), ),

View File

@ -31,11 +31,11 @@ class DebugInfoScreen : Screen() {
itemsProvider = { itemsProvider = {
listOf( listOf(
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = WorkerInfoScreen.title, title = WorkerInfoScreen.TITLE,
onClick = { navigator.push(WorkerInfoScreen()) }, onClick = { navigator.push(WorkerInfoScreen()) },
), ),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = BackupSchemaScreen.title, title = BackupSchemaScreen.TITLE,
onClick = { navigator.push(BackupSchemaScreen()) }, onClick = { navigator.push(BackupSchemaScreen()) },
), ),
getAppInfoGroup(), getAppInfoGroup(),

View File

@ -49,7 +49,7 @@ import java.time.ZoneId
class WorkerInfoScreen : Screen() { class WorkerInfoScreen : Screen() {
companion object { companion object {
const val title = "Worker info" const val TITLE = "Worker info"
} }
@Composable @Composable
@ -65,7 +65,7 @@ class WorkerInfoScreen : Screen() {
Scaffold( Scaffold(
topBar = { topBar = {
AppBar( AppBar(
title = title, title = TITLE,
navigateUp = navigator::pop, navigateUp = navigator::pop,
actions = { actions = {
AppBarActions( AppBarActions(
@ -74,7 +74,7 @@ class WorkerInfoScreen : Screen() {
title = stringResource(MR.strings.action_copy_to_clipboard), title = stringResource(MR.strings.action_copy_to_clipboard),
icon = Icons.Default.ContentCopy, icon = Icons.Default.ContentCopy,
onClick = { onClick = {
context.copyToClipboard(title, enqueued + finished + running) context.copyToClipboard(TITLE, enqueued + finished + running)
}, },
), ),
), ),
@ -159,7 +159,7 @@ class WorkerInfoScreen : Screen() {
Injekt.get<UiPreferences>().dateFormat().get(), Injekt.get<UiPreferences>().dateFormat().get(),
), ),
) )
appendLine("Next scheduled run: $timestamp",) appendLine("Next scheduled run: $timestamp")
appendLine("Attempt #${workInfo.runAttemptCount + 1}") appendLine("Attempt #${workInfo.runAttemptCount + 1}")
} }
appendLine() appendLine()

View File

@ -34,7 +34,9 @@ import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrolledToStart import tachiyomi.presentation.core.util.isScrolledToStart
private enum class State { private enum class State {
CHECKED, INVERSED, UNCHECKED CHECKED,
INVERSED,
UNCHECKED,
} }
@Composable @Composable

View File

@ -15,7 +15,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
@Composable @Composable
@ -73,7 +73,7 @@ private fun RowScope.BaseStatsItem(
style = subtitleStyle style = subtitleStyle
.copy( .copy(
color = MaterialTheme.colorScheme.onSurface color = MaterialTheme.colorScheme.onSurface
.copy(alpha = SecondaryItemAlpha), .copy(alpha = SECONDARY_ALPHA),
), ),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
) )

View File

@ -226,7 +226,7 @@ private fun ChapterText(
Text( Text(
text = buildAnnotatedString { text = buildAnnotatedString {
if (downloaded) { if (downloaded) {
appendInlineContent(DownloadedIconContentId) appendInlineContent(DOWNLOADED_ICON_ID)
append(' ') append(' ')
} }
append(name) append(name)
@ -236,7 +236,7 @@ private fun ChapterText(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
inlineContent = persistentMapOf( inlineContent = persistentMapOf(
DownloadedIconContentId to InlineTextContent( DOWNLOADED_ICON_ID to InlineTextContent(
Placeholder( Placeholder(
width = 22.sp, width = 22.sp,
height = 22.sp, height = 22.sp,
@ -273,7 +273,7 @@ private val CardColor: CardColors
) )
private val VerticalSpacerSize = 24.dp private val VerticalSpacerSize = 24.dp
private const val DownloadedIconContentId = "downloaded" private const val DOWNLOADED_ICON_ID = "downloaded"
private fun previewChapter(name: String, scanlator: String, chapterNumber: Double) = Chapter.create().copy( private fun previewChapter(name: String, scanlator: String, chapterNumber: Double) = Chapter.create().copy(
id = 0L, id = 0L,

View File

@ -63,7 +63,7 @@ fun ExhUtils(
modifier modifier
.fillMaxWidth() .fillMaxWidth()
.background(backgroundColor), .background(backgroundColor),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
AnimatedVisibility(visible = isVisible) { AnimatedVisibility(visible = isVisible) {
Column { Column {
@ -84,7 +84,7 @@ fun ExhUtils(
) { ) {
Column( Column(
Modifier.weight(3f), Modifier.weight(3f),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Text( Text(
text = stringResource(SYMR.strings.eh_autoscroll), text = stringResource(SYMR.strings.eh_autoscroll),
@ -93,17 +93,17 @@ fun ExhUtils(
fontFamily = FontFamily.SansSerif, fontFamily = FontFamily.SansSerif,
style = MaterialTheme.typography.labelLarge, style = MaterialTheme.typography.labelLarge,
modifier = Modifier.fillMaxWidth(0.75f), modifier = Modifier.fillMaxWidth(0.75f),
textAlign = TextAlign.Center textAlign = TextAlign.Center,
) )
} }
Column( Column(
Modifier.weight(1f), Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Switch( Switch(
checked = isAutoScroll, checked = isAutoScroll,
onCheckedChange = null, onCheckedChange = null,
enabled = isAutoScrollEnabled enabled = isAutoScrollEnabled,
) )
} }
} }
@ -114,7 +114,7 @@ fun ExhUtils(
) { ) {
Column( Column(
Modifier.weight(3f), Modifier.weight(3f),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
var autoScrollFrequencyState by remember { var autoScrollFrequencyState by remember {
mutableStateOf(autoScrollFrequency) mutableStateOf(autoScrollFrequency)

View File

@ -38,7 +38,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -58,8 +57,6 @@ import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
private const val UnsetStatusTextAlpha = 0.5F
@Composable @Composable
fun TrackInfoDialogHome( fun TrackInfoDialogHome(
trackItems: List<TrackItem>, trackItems: List<TrackItem>,
@ -211,10 +208,9 @@ private fun TrackInfoItem(
if (onScoreClick != null) { if (onScoreClick != null) {
VerticalDivider() VerticalDivider()
TrackDetailsItem( TrackDetailsItem(
modifier = Modifier modifier = Modifier.weight(1f),
.weight(1f) text = score,
.alpha(if (score == null) UnsetStatusTextAlpha else 1f), placeholder = stringResource(MR.strings.score),
text = score ?: stringResource(MR.strings.score),
onClick = onScoreClick, onClick = onScoreClick,
) )
} }
@ -243,6 +239,8 @@ private fun TrackInfoItem(
} }
} }
private const val UNSET_TEXT_ALPHA = 0.5F
@Composable @Composable
private fun TrackDetailsItem( private fun TrackDetailsItem(
text: String?, text: String?,
@ -263,7 +261,7 @@ private fun TrackDetailsItem(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = if (text == null) UnsetStatusTextAlpha else 1f), color = MaterialTheme.colorScheme.onSurface.copy(alpha = if (text == null) UNSET_TEXT_ALPHA else 1f),
) )
} }
} }

View File

@ -43,7 +43,7 @@ import eu.kanade.tachiyomi.ui.updates.UpdatesItem
import tachiyomi.domain.updates.model.UpdatesWithRelations import tachiyomi.domain.updates.model.UpdatesWithRelations
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.ListGroupHeader import tachiyomi.presentation.core.components.ListGroupHeader
import tachiyomi.presentation.core.components.material.ReadItemAlpha import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.selectedBackground import tachiyomi.presentation.core.util.selectedBackground
@ -107,8 +107,10 @@ internal fun LazyListScope.updatesUiItems(
readProgress = updatesItem.update.lastPageRead readProgress = updatesItem.update.lastPageRead
.takeIf { .takeIf {
/* SY --> */( /* SY --> */(
!updatesItem.update.read || (preserveReadingPosition && updatesItem.isEhBasedUpdate()) !updatesItem.update.read ||
)/* SY <-- */ && it > 0L (preserveReadingPosition && updatesItem.isEhBasedUpdate())
)/* SY <-- */ &&
it > 0L
} }
?.let { ?.let {
stringResource( stringResource(
@ -152,7 +154,7 @@ private fun UpdatesUiItem(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val textAlpha = if (update.read) ReadItemAlpha else 1f val textAlpha = if (update.read) DISABLED_ALPHA else 1f
Row( Row(
modifier = modifier modifier = modifier
@ -226,7 +228,7 @@ private fun UpdatesUiItem(
Text( Text(
text = readProgress, text = readProgress,
maxLines = 1, maxLines = 1,
color = LocalContentColor.current.copy(alpha = ReadItemAlpha), color = LocalContentColor.current.copy(alpha = DISABLED_ALPHA),
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
} }

View File

@ -1,6 +1,5 @@
package eu.kanade.presentation.util package eu.kanade.presentation.util
import android.annotation.SuppressLint
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform import androidx.compose.animation.ContentTransform
@ -28,7 +27,6 @@ import soup.compose.material.motion.animation.rememberSlideDistance
/** /**
* For invoking back press to the parent activity * For invoking back press to the parent activity
*/ */
@SuppressLint("ComposeCompositionLocalUsage")
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null } val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
interface Tab : cafe.adriel.voyager.navigator.tab.Tab { interface Tab : cafe.adriel.voyager.navigator.tab.Tab {

View File

@ -174,8 +174,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
val syncPreferences: SyncPreferences = Injekt.get() val syncPreferences: SyncPreferences = Injekt.get()
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions() val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart) {
) {
SyncDataJob.startNow(this@App) SyncDataJob.startNow(this@App)
} }
@ -199,7 +198,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
) )
} }
@Suppress("MagicNumber")
override fun newImageLoader(context: Context): ImageLoader { override fun newImageLoader(context: Context): ImageLoader {
return ImageLoader.Builder(this).apply { return ImageLoader.Builder(this).apply {
val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client } val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client }
@ -239,8 +237,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
val syncPreferences: SyncPreferences = Injekt.get() val syncPreferences: SyncPreferences = Injekt.get()
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions() val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppResume if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppResume) {
) {
SyncDataJob.startNow(this@App) SyncDataJob.startNow(this@App)
} }
} }

View File

@ -57,7 +57,7 @@ class BackupCreator(
// SY --> // SY -->
private val savedSearchBackupCreator: SavedSearchBackupCreator = SavedSearchBackupCreator(), private val savedSearchBackupCreator: SavedSearchBackupCreator = SavedSearchBackupCreator(),
private val getMergedManga: GetMergedManga = Injekt.get(), private val getMergedManga: GetMergedManga = Injekt.get(),
private val handler: DatabaseHandler = Injekt.get() private val handler: DatabaseHandler = Injekt.get(),
// SY <-- // SY <--
) { ) {
@ -92,7 +92,7 @@ class BackupCreator(
} else { } else {
emptyList() emptyList()
} + getMergedManga.await(), // SY <-- } + getMergedManga.await(), // SY <--
options options,
) )
val backup = Backup( val backup = Backup(
backupManga = backupManga, backupManga = backupManga,

View File

@ -39,7 +39,8 @@ data class BackupOptions(
// SY <-- // SY <--
) )
fun canCreate() = libraryEntries || categories || appSettings || extensionRepoSettings || sourceSettings || savedSearches fun canCreate() =
libraryEntries || categories || appSettings || extensionRepoSettings || sourceSettings || savedSearches
companion object { companion object {
val libraryOptions = persistentListOf( val libraryOptions = persistentListOf(

View File

@ -7,7 +7,7 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class SavedSearchBackupCreator( class SavedSearchBackupCreator(
private val handler: DatabaseHandler = Injekt.get() private val handler: DatabaseHandler = Injekt.get(),
) { ) {
suspend operator fun invoke(): List<BackupSavedSearch> { suspend operator fun invoke(): List<BackupSavedSearch> {

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
@Suppress("MagicNumber")
@Serializable @Serializable
data class Backup( data class Backup(
@ProtoNumber(1) val backupManga: List<BackupManga>, @ProtoNumber(1) val backupManga: List<BackupManga>,

View File

@ -4,7 +4,6 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
@Suppress("MagicNumber")
@Serializable @Serializable
data class BackupChapter( data class BackupChapter(
// in 1.x some of these values have different names // in 1.x some of these values have different names

View File

@ -4,7 +4,6 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import mihon.domain.extensionrepo.model.ExtensionRepo import mihon.domain.extensionrepo.model.ExtensionRepo
@Suppress("MagicNumber")
@Serializable @Serializable
class BackupExtensionRepos( class BackupExtensionRepos(
@ProtoNumber(1) var baseUrl: String, @ProtoNumber(1) var baseUrl: String,

View File

@ -3,13 +3,9 @@ package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.model.UpdateStrategy
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import tachiyomi.domain.manga.model.CustomMangaInfo
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
@Suppress( @Suppress("DEPRECATION")
"DEPRECATION",
"MagicNumber",
)
@Serializable @Serializable
data class BackupManga( data class BackupManga(
// in 1.x some of these values have different names // in 1.x some of these values have different names

View File

@ -36,7 +36,8 @@ data class BackupMergedMangaReference(
} }
val backupMergedMangaReferenceMapper = val backupMergedMangaReferenceMapper =
{ _: Long, {
_: Long,
isInfoManga: Boolean, isInfoManga: Boolean,
getChapterUpdates: Boolean, getChapterUpdates: Boolean,
chapterSortMode: Long, chapterSortMode: Long,

View File

@ -53,7 +53,20 @@ data class BackupTracking(
} }
val backupTrackMapper = { val backupTrackMapper = {
_: Long, _: Long, syncId: Long, mediaId: Long, libraryId: Long?, title: String, lastChapterRead: Double, totalChapters: Long, status: Long, score: Double, remoteUrl: String, startDate: Long, finishDate: Long -> _: Long,
_: Long,
syncId: Long,
mediaId: Long,
libraryId: Long?,
title: String,
lastChapterRead: Double,
totalChapters: Long,
status: Long,
score: Double,
remoteUrl: String,
startDate: Long,
finishDate: Long,
->
BackupTracking( BackupTracking(
syncId = syncId.toInt(), syncId = syncId.toInt(),
mediaId = mediaId, mediaId = mediaId,

View File

@ -200,7 +200,7 @@ class BackupRestorer(
} }
private fun CoroutineScope.restoreExtensionRepos( private fun CoroutineScope.restoreExtensionRepos(
backupExtensionRepo: List<BackupExtensionRepos> backupExtensionRepo: List<BackupExtensionRepos>,
) = launch { ) = launch {
backupExtensionRepo backupExtensionRepo
.forEach { .forEach {

View File

@ -27,7 +27,13 @@ data class RestoreOptions(
// SY <-- // SY <--
) )
fun canRestore() = libraryEntries || categories || appSettings || extensionRepoSettings || sourceSettings /* SY --> */ || savedSearches /* SY <-- */ fun canRestore() =
libraryEntries ||
categories ||
appSettings ||
extensionRepoSettings ||
sourceSettings /* SY --> */ ||
savedSearches /* SY <-- */
companion object { companion object {
val options = persistentListOf( val options = persistentListOf(
@ -72,7 +78,7 @@ data class RestoreOptions(
extensionRepoSettings = array[3], extensionRepoSettings = array[3],
sourceSettings = array[4], sourceSettings = array[4],
// SY --> // SY -->
savedSearches = array[5] savedSearches = array[5],
// SY <-- // SY <--
) )
} }

View File

@ -8,7 +8,7 @@ import uy.kohesive.injekt.api.get
class ExtensionRepoRestorer( class ExtensionRepoRestorer(
private val handler: DatabaseHandler = Injekt.get(), private val handler: DatabaseHandler = Injekt.get(),
private val getExtensionRepos: GetExtensionRepo = Injekt.get() private val getExtensionRepos: GetExtensionRepo = Injekt.get(),
) { ) {
suspend operator fun invoke( suspend operator fun invoke(
@ -32,7 +32,7 @@ class ExtensionRepoRestorer(
backupRepo.name, backupRepo.name,
backupRepo.shortName, backupRepo.shortName,
backupRepo.website, backupRepo.website,
backupRepo.signingKeyFingerprint backupRepo.signingKeyFingerprint,
) )
} }
} }

View File

@ -36,8 +36,8 @@ class ChapterCache(
private val context: Context, private val context: Context,
private val json: Json, private val json: Json,
// SY --> // SY -->
readerPreferences: ReaderPreferences readerPreferences: ReaderPreferences,
//S Y <-- // SY <--
) { ) {
// --> EH // --> EH

View File

@ -45,7 +45,6 @@ import java.io.IOException
* Available request parameter: * Available request parameter:
* - [USE_CUSTOM_COVER_KEY]: Use custom cover if set by user, default is true * - [USE_CUSTOM_COVER_KEY]: Use custom cover if set by user, default is true
*/ */
@Suppress("LongParameterList")
class MangaCoverFetcher( class MangaCoverFetcher(
private val url: String?, private val url: String?,
private val isLibraryManga: Boolean, private val isLibraryManga: Boolean,
@ -86,7 +85,7 @@ class MangaCoverFetcher(
source = ImageSource( source = ImageSource(
file = file.toOkioPath(), file = file.toOkioPath(),
fileSystem = FileSystem.SYSTEM, fileSystem = FileSystem.SYSTEM,
diskCacheKey = diskCacheKey diskCacheKey = diskCacheKey,
), ),
mimeType = "image/*", mimeType = "image/*",
dataSource = DataSource.DISK, dataSource = DataSource.DISK,

View File

@ -58,7 +58,7 @@ class PagePreviewFetcher(
source = ImageSource( source = ImageSource(
file = file.toOkioPath(), file = file.toOkioPath(),
fileSystem = FileSystem.SYSTEM, fileSystem = FileSystem.SYSTEM,
diskCacheKey = diskCacheKey diskCacheKey = diskCacheKey,
), ),
mimeType = "image/*", mimeType = "image/*",
dataSource = DataSource.DISK, dataSource = DataSource.DISK,
@ -230,7 +230,7 @@ class PagePreviewFetcher(
file = data, file = data,
fileSystem = FileSystem.SYSTEM, fileSystem = FileSystem.SYSTEM,
diskCacheKey = diskCacheKey, diskCacheKey = diskCacheKey,
closeable = this closeable = this,
) )
} }

View File

@ -1,3 +1,5 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter

View File

@ -1,3 +1,5 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
class ChapterImpl : Chapter { class ChapterImpl : Chapter {

View File

@ -1,3 +1,5 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
import java.io.Serializable import java.io.Serializable

View File

@ -1,3 +1,5 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
class TrackImpl : Track { class TrackImpl : Track {

View File

@ -121,7 +121,8 @@ class DownloadProvider(
getValidChapterDirNames(chp.name, chp.scanlator).any { dir -> getValidChapterDirNames(chp.name, chp.scanlator).any { dir ->
mangaDir.findFile(dir) != null mangaDir.findFile(dir) != null
} }
} == null || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true } == null ||
it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
} }
} }
// SY <-- // SY <--

View File

@ -190,7 +190,7 @@ class Downloader(
fun clearQueue() { fun clearQueue() {
cancelDownloaderJob() cancelDownloaderJob()
_clearQueue() internalClearQueue()
notifier.dismissProgress() notifier.dismissProgress()
} }
@ -204,9 +204,12 @@ class Downloader(
val activeDownloadsFlow = queueState.transformLatest { queue -> val activeDownloadsFlow = queueState.transformLatest { queue ->
while (true) { while (true) {
val activeDownloads = queue.asSequence() val activeDownloads = queue.asSequence()
.filter { it.status.value <= Download.State.DOWNLOADING.value } // Ignore completed downloads, leave them in the queue // Ignore completed downloads, leave them in the queue
.filter { it.status.value <= Download.State.DOWNLOADING.value }
.groupBy { it.source } .groupBy { it.source }
.toList().take(5) // Concurrently download from 5 different sources .toList()
// Concurrently download from 5 different sources
.take(5)
.map { (_, downloads) -> downloads.first() } .map { (_, downloads) -> downloads.first() }
emit(activeDownloads) emit(activeDownloads)
@ -650,7 +653,7 @@ class Downloader(
chapter, chapter,
urls, urls,
categories, categories,
source.name source.name,
) )
// Remove the old file // Remove the old file
@ -710,7 +713,7 @@ class Downloader(
removeFromQueueIf { it.manga.id == manga.id } removeFromQueueIf { it.manga.id == manga.id }
} }
private fun _clearQueue() { private fun internalClearQueue() {
_queueState.update { _queueState.update {
it.forEach { download -> it.forEach { download ->
if (download.status == Download.State.DOWNLOADING || download.status == Download.State.QUEUE) { if (download.status == Download.State.DOWNLOADING || download.status == Download.State.QUEUE) {
@ -732,7 +735,7 @@ class Downloader(
} }
pause() pause()
_clearQueue() internalClearQueue()
addAllToQueue(downloads) addAllToQueue(downloads)
if (wasRunning) { if (wasRunning) {

View File

@ -363,14 +363,17 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
async { async {
semaphore.withPermit { semaphore.withPermit {
if ( if (
mdlistLogged && mangaInSource.firstOrNull() mdlistLogged &&
mangaInSource.firstOrNull()
?.let { it.manga.source in mangaDexSourceIds } == true ?.let { it.manga.source in mangaDexSourceIds } == true
) { ) {
launch { launch {
mangaInSource.forEach { (manga) -> mangaInSource.forEach { (manga) ->
try { try {
val tracks = getTracks.await(manga.id) val tracks = getTracks.await(manga.id)
if (tracks.isEmpty() || tracks.none { it.trackerId == TrackerManager.MDLIST }) { if (tracks.isEmpty() ||
tracks.none { it.trackerId == TrackerManager.MDLIST }
) {
val track = mdList.createInitialTracker(manga) val track = mdList.createInitialTracker(manga)
insertTrack.await(mdList.refresh(track).toDomainTrack(false)!!) insertTrack.await(mdList.refresh(track).toDomainTrack(false)!!)
} }
@ -400,10 +403,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
// SY --> // SY -->
.sortedByDescending { it.sourceOrder }.run { .sortedByDescending { it.sourceOrder }.run {
if (libraryPreferences.libraryReadDuplicateChapters().get()) { if (libraryPreferences.libraryReadDuplicateChapters().get()) {
val readChapters = getChaptersByMangaId.await(manga.id).filter { it.read } val readChapters = getChaptersByMangaId.await(manga.id).filter {
it.read
}
val newReadChapters = this.filter { chapter -> val newReadChapters = this.filter { chapter ->
chapter.chapterNumber > 0 && chapter.chapterNumber > 0 &&
readChapters.any { it.chapterNumber == chapter.chapterNumber } readChapters.any {
it.chapterNumber == chapter.chapterNumber
}
} }
if (newReadChapters.isNotEmpty()) { if (newReadChapters.isNotEmpty()) {
@ -415,7 +422,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
this this
} }
} }
//SY <-- // SY <--
if (newChapters.isNotEmpty()) { if (newChapters.isNotEmpty()) {
val categoryIds = getCategories.await(manga.id).map { it.id } val categoryIds = getCategories.await(manga.id).map { it.id }
@ -766,7 +773,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val constraints = Constraints( val constraints = Constraints(
requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
NetworkType.UNMETERED NetworkType.UNMETERED
} else { NetworkType.CONNECTED }, } else {
NetworkType.CONNECTED
},
requiresCharging = DEVICE_CHARGING in restrictions, requiresCharging = DEVICE_CHARGING in restrictions,
requiresBatteryNotLow = true, requiresBatteryNotLow = true,
) )

View File

@ -147,7 +147,7 @@ class SyncManager(
return return
} }
if (remoteBackup === syncData.backup){ if (remoteBackup === syncData.backup) {
// nothing changed // nothing changed
logcat(LogPriority.DEBUG) { "Skip restore due to remote was overwrite from local" } logcat(LogPriority.DEBUG) { "Skip restore due to remote was overwrite from local" }
syncPreferences.lastSyncTimestamp().set(Date().time) syncPreferences.lastSyncTimestamp().set(Date().time)

View File

@ -72,7 +72,7 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
try { try {
val remoteSData = pullSyncData() val remoteSData = pullSyncData()
if (remoteSData != null ){ if (remoteSData != null) {
// Get local unique device ID // Get local unique device ID
val localDeviceId = syncPreferences.uniqueDeviceID() val localDeviceId = syncPreferences.uniqueDeviceID()
val lastSyncDeviceId = remoteSData.deviceId val lastSyncDeviceId = remoteSData.deviceId
@ -86,7 +86,7 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
return if (lastSyncDeviceId == localDeviceId) { return if (lastSyncDeviceId == localDeviceId) {
pushSyncData(syncData) pushSyncData(syncData)
syncData.backup syncData.backup
}else{ } else {
// Merge the local and remote sync data // Merge the local and remote sync data
val mergedSyncData = mergeSyncData(syncData, remoteSData) val mergedSyncData = mergeSyncData(syncData, remoteSData)
pushSyncData(mergedSyncData) pushSyncData(mergedSyncData)
@ -165,7 +165,9 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
appProperties = mapOf("deviceId" to syncData.deviceId) appProperties = mapOf("deviceId" to syncData.deviceId)
} }
drive.files().update(fileId, fileMetadata, mediaContent).execute() drive.files().update(fileId, fileMetadata, mediaContent).execute()
logcat(LogPriority.DEBUG) { "Updated existing sync data file in Google Drive with file ID: $fileId" } logcat(LogPriority.DEBUG) {
"Updated existing sync data file in Google Drive with file ID: $fileId"
}
} else { } else {
val fileMetadata = File().apply { val fileMetadata = File().apply {
name = remoteFileName name = remoteFileName
@ -176,7 +178,9 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
val uploadedFile = drive.files().create(fileMetadata, mediaContent) val uploadedFile = drive.files().create(fileMetadata, mediaContent)
.setFields("id") .setFields("id")
.execute() .execute()
logcat(LogPriority.DEBUG) { "Created new sync data file in Google Drive with file ID: ${uploadedFile.id}" } logcat(LogPriority.DEBUG) {
"Created new sync data file in Google Drive with file ID: ${uploadedFile.id}"
}
} }
} }
} }
@ -203,7 +207,6 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
} }
} }
suspend fun deleteSyncDataFromGoogleDrive(): DeleteSyncDataStatus { suspend fun deleteSyncDataFromGoogleDrive(): DeleteSyncDataStatus {
val drive = googleDriveService.driveService val drive = googleDriveService.driveService

View File

@ -26,7 +26,7 @@ abstract class SyncService(
val json: Json, val json: Json,
val syncPreferences: SyncPreferences, val syncPreferences: SyncPreferences,
) { ) {
abstract suspend fun doSync(syncData: SyncData): Backup?; abstract suspend fun doSync(syncData: SyncData): Backup?
/** /**
* Merges the local and remote sync data into a single JSON string. * Merges the local and remote sync data into a single JSON string.
@ -44,7 +44,8 @@ abstract class SyncService(
remoteSyncData.backup?.backupManga, remoteSyncData.backup?.backupManga,
localSyncData.backup?.backupCategories ?: emptyList(), localSyncData.backup?.backupCategories ?: emptyList(),
remoteSyncData.backup?.backupCategories ?: emptyList(), remoteSyncData.backup?.backupCategories ?: emptyList(),
mergedCategoriesList) mergedCategoriesList,
)
val mergedSourcesList = val mergedSourcesList =
mergeSourcesLists(localSyncData.backup?.backupSources, remoteSyncData.backup?.backupSources) mergeSourcesLists(localSyncData.backup?.backupSources, remoteSyncData.backup?.backupSources)
@ -120,11 +121,13 @@ abstract class SyncService(
val mergedCategoriesMapByName = mergedCategories.associateBy { it.name } val mergedCategoriesMapByName = mergedCategories.associateBy { it.name }
fun updateCategories(theManga: BackupManga, theMap: Map<Long, BackupCategory>): BackupManga { fun updateCategories(theManga: BackupManga, theMap: Map<Long, BackupCategory>): BackupManga {
return theManga.copy(categories = theManga.categories.mapNotNull { return theManga.copy(
categories = theManga.categories.mapNotNull {
theMap[it]?.let { category -> theMap[it]?.let { category ->
mergedCategoriesMapByName[category.name]?.order mergedCategoriesMapByName[category.name]?.order
} }
}) },
)
} }
logcat(LogPriority.DEBUG, logTag) { logcat(LogPriority.DEBUG, logTag) {
@ -147,7 +150,7 @@ abstract class SyncService(
} }
updateCategories( updateCategories(
local.copy(chapters = mergeChapters(local.chapters, remote.chapters)), local.copy(chapters = mergeChapters(local.chapters, remote.chapters)),
localCategoriesMapByOrder localCategoriesMapByOrder,
) )
} else { } else {
logcat(LogPriority.DEBUG, logTag) { logcat(LogPriority.DEBUG, logTag) {
@ -155,7 +158,7 @@ abstract class SyncService(
} }
updateCategories( updateCategories(
remote.copy(chapters = mergeChapters(local.chapters, remote.chapters)), remote.copy(chapters = mergeChapters(local.chapters, remote.chapters)),
remoteCategoriesMapByOrder remoteCategoriesMapByOrder,
) )
} }
} }
@ -301,7 +304,7 @@ abstract class SyncService(
private fun mergeSourcesLists( private fun mergeSourcesLists(
localSources: List<BackupSource>?, localSources: List<BackupSource>?,
remoteSources: List<BackupSource>? remoteSources: List<BackupSource>?,
): List<BackupSource> { ): List<BackupSource> {
val logTag = "MergeSources" val logTag = "MergeSources"
@ -346,7 +349,7 @@ abstract class SyncService(
private fun mergePreferencesLists( private fun mergePreferencesLists(
localPreferences: List<BackupPreference>?, localPreferences: List<BackupPreference>?,
remotePreferences: List<BackupPreference>? remotePreferences: List<BackupPreference>?,
): List<BackupPreference> { ): List<BackupPreference> {
val logTag = "MergePreferences" val logTag = "MergePreferences"
@ -394,7 +397,7 @@ abstract class SyncService(
private fun mergeSourcePreferencesLists( private fun mergeSourcePreferencesLists(
localPreferences: List<BackupSourcePreferences>?, localPreferences: List<BackupSourcePreferences>?,
remotePreferences: List<BackupSourcePreferences>? remotePreferences: List<BackupSourcePreferences>?,
): List<BackupSourcePreferences> { ): List<BackupSourcePreferences> {
val logTag = "MergeSourcePreferences" val logTag = "MergeSourcePreferences"
@ -408,7 +411,8 @@ abstract class SyncService(
} }
// Merge both source preferences maps // Merge both source preferences maps
val mergedSourcePreferences = (localPreferencesMap.keys + remotePreferencesMap.keys).distinct().mapNotNull { sourceKey -> val mergedSourcePreferences = (localPreferencesMap.keys + remotePreferencesMap.keys).distinct()
.mapNotNull { sourceKey ->
val localSourcePreference = localPreferencesMap[sourceKey] val localSourcePreference = localPreferencesMap[sourceKey]
val remoteSourcePreference = remotePreferencesMap[sourceKey] val remoteSourcePreference = remotePreferencesMap[sourceKey]
@ -450,7 +454,7 @@ abstract class SyncService(
private fun mergeIndividualPreferences( private fun mergeIndividualPreferences(
localPrefs: List<BackupPreference>, localPrefs: List<BackupPreference>,
remotePrefs: List<BackupPreference> remotePrefs: List<BackupPreference>,
): List<BackupPreference> { ): List<BackupPreference> {
val mergedPrefsMap = (localPrefs + remotePrefs).associateBy { it.key } val mergedPrefsMap = (localPrefs + remotePrefs).associateBy { it.key }
return mergedPrefsMap.values.toList() return mergedPrefsMap.values.toList()
@ -459,7 +463,7 @@ abstract class SyncService(
// SY --> // SY -->
private fun mergeSavedSearchesLists( private fun mergeSavedSearchesLists(
localSearches: List<BackupSavedSearch>?, localSearches: List<BackupSavedSearch>?,
remoteSearches: List<BackupSavedSearch>? remoteSearches: List<BackupSavedSearch>?,
): List<BackupSavedSearch> { ): List<BackupSavedSearch> {
val logTag = "MergeSavedSearches" val logTag = "MergeSavedSearches"

View File

@ -38,7 +38,7 @@ class SyncYomiSyncService(
try { try {
val (remoteData, etag) = pullSyncData() val (remoteData, etag) = pullSyncData()
val finalSyncData = if (remoteData != null){ val finalSyncData = if (remoteData != null) {
assert(etag.isNotEmpty()) { "ETag should never be empty if remote data is not null" } assert(etag.isNotEmpty()) { "ETag should never be empty if remote data is not null" }
logcat(LogPriority.DEBUG, "SyncService") { logcat(LogPriority.DEBUG, "SyncService") {
"Try update remote data with ETag($etag)" "Try update remote data with ETag($etag)"
@ -54,7 +54,6 @@ class SyncYomiSyncService(
pushSyncData(finalSyncData, etag) pushSyncData(finalSyncData, etag)
return finalSyncData.backup return finalSyncData.backup
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR) { "Error syncing: ${e.message}" } logcat(LogPriority.ERROR) { "Error syncing: ${e.message}" }
notifier.showSyncError(e.message) notifier.showSyncError(e.message)
@ -113,7 +112,6 @@ class SyncYomiSyncService(
// return default value so we can overwrite it // return default value so we can overwrite it
Pair(null, "") Pair(null, "")
} }
} else { } else {
val responseBody = response.body.string() val responseBody = response.body.string()
notifier.showSyncError("Failed to download sync data: $responseBody") notifier.showSyncError("Failed to download sync data: $responseBody")
@ -165,11 +163,9 @@ class SyncYomiSyncService(
.takeIf { it?.isNotEmpty() == true } ?: throw SyncYomiException("Missing ETag") .takeIf { it?.isNotEmpty() == true } ?: throw SyncYomiException("Missing ETag")
syncPreferences.lastSyncEtag().set(newETag) syncPreferences.lastSyncEtag().set(newETag)
logcat(LogPriority.DEBUG) { "SyncYomi sync completed" } logcat(LogPriority.DEBUG) { "SyncYomi sync completed" }
} else if (response.code == HttpStatus.SC_PRECONDITION_FAILED) { } else if (response.code == HttpStatus.SC_PRECONDITION_FAILED) {
// other clients updated remote data, will try next time // other clients updated remote data, will try next time
logcat(LogPriority.DEBUG) { "SyncYomi sync failed with 412" } logcat(LogPriority.DEBUG) { "SyncYomi sync failed with 412" }
} else { } else {
val responseBody = response.body.string() val responseBody = response.body.string()
notifier.showSyncError("Failed to upload sync data: $responseBody") notifier.showSyncError("Failed to upload sync data: $responseBody")

View File

@ -65,7 +65,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
with(json) { with(json) {
authClient.newCall( authClient.newCall(
POST( POST(
apiUrl, API_URL,
body = payload.toString().toRequestBody(jsonMime), body = payload.toString().toRequestBody(jsonMime),
), ),
) )
@ -109,7 +109,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("completedAt", createDate(track.finished_reading_date)) put("completedAt", createDate(track.finished_reading_date))
} }
} }
authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))) authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
.awaitSuccess() .awaitSuccess()
track track
} }
@ -131,7 +131,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("listId", track.libraryId) put("listId", track.libraryId)
} }
} }
authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))) authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
.awaitSuccess() .awaitSuccess()
} }
} }
@ -172,7 +172,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
with(json) { with(json) {
authClient.newCall( authClient.newCall(
POST( POST(
apiUrl, API_URL,
body = payload.toString().toRequestBody(jsonMime), body = payload.toString().toRequestBody(jsonMime),
), ),
) )
@ -242,7 +242,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
with(json) { with(json) {
authClient.newCall( authClient.newCall(
POST( POST(
apiUrl, API_URL,
body = payload.toString().toRequestBody(jsonMime), body = payload.toString().toRequestBody(jsonMime),
), ),
) )
@ -286,7 +286,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
with(json) { with(json) {
authClient.newCall( authClient.newCall(
POST( POST(
apiUrl, API_URL,
body = payload.toString().toRequestBody(jsonMime), body = payload.toString().toRequestBody(jsonMime),
), ),
) )
@ -364,17 +364,17 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
} }
companion object { companion object {
private const val clientId = "16329" private const val CLIENT_ID = "16329"
private const val apiUrl = "https://graphql.anilist.co/" private const val API_URL = "https://graphql.anilist.co/"
private const val baseUrl = "https://anilist.co/api/v2/" private const val BASE_URL = "https://anilist.co/api/v2/"
private const val baseMangaUrl = "https://anilist.co/manga/" private const val BASE_MANGA_URL = "https://anilist.co/manga/"
fun mangaUrl(mediaId: Long): String { fun mangaUrl(mediaId: Long): String {
return baseMangaUrl + mediaId return BASE_MANGA_URL + mediaId
} }
fun authUrl(): Uri = "${baseUrl}oauth/authorize".toUri().buildUpon() fun authUrl(): Uri = "${BASE_URL}oauth/authorize".toUri().buildUpon()
.appendQueryParameter("client_id", clientId) .appendQueryParameter("client_id", CLIENT_ID)
.appendQueryParameter("response_type", "token") .appendQueryParameter("response_type", "token")
.build() .build()
} }

View File

@ -42,7 +42,7 @@ class BangumiApi(
.add("rating", track.score.toInt().toString()) .add("rating", track.score.toInt().toString())
.add("status", track.toBangumiStatus()) .add("status", track.toBangumiStatus())
.build() .build()
authClient.newCall(POST("$apiUrl/collection/${track.remote_id}/update", body = body)) authClient.newCall(POST("$API_URL/collection/${track.remote_id}/update", body = body))
.awaitSuccess() .awaitSuccess()
track track
} }
@ -55,7 +55,7 @@ class BangumiApi(
.add("rating", track.score.toInt().toString()) .add("rating", track.score.toInt().toString())
.add("status", track.toBangumiStatus()) .add("status", track.toBangumiStatus())
.build() .build()
authClient.newCall(POST("$apiUrl/collection/${track.remote_id}/update", body = sbody)) authClient.newCall(POST("$API_URL/collection/${track.remote_id}/update", body = sbody))
.awaitSuccess() .awaitSuccess()
// chapter update // chapter update
@ -64,7 +64,7 @@ class BangumiApi(
.build() .build()
authClient.newCall( authClient.newCall(
POST( POST(
"$apiUrl/subject/${track.remote_id}/update/watched_eps", "$API_URL/subject/${track.remote_id}/update/watched_eps",
body = body, body = body,
), ),
).awaitSuccess() ).awaitSuccess()
@ -75,7 +75,7 @@ class BangumiApi(
suspend fun search(search: String): List<TrackSearch> { suspend fun search(search: String): List<TrackSearch> {
return withIOContext { return withIOContext {
val url = "$apiUrl/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}" val url = "$API_URL/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}"
.toUri() .toUri()
.buildUpon() .buildUpon()
.appendQueryParameter("max_results", "20") .appendQueryParameter("max_results", "20")
@ -124,7 +124,7 @@ class BangumiApi(
suspend fun findLibManga(track: Track): Track? { suspend fun findLibManga(track: Track): Track? {
return withIOContext { return withIOContext {
with(json) { with(json) {
authClient.newCall(GET("$apiUrl/subject/${track.remote_id}")) authClient.newCall(GET("$API_URL/subject/${track.remote_id}"))
.awaitSuccess() .awaitSuccess()
.parseAs<JsonObject>() .parseAs<JsonObject>()
.let { jsonToSearch(it) } .let { jsonToSearch(it) }
@ -134,7 +134,7 @@ class BangumiApi(
suspend fun statusLibManga(track: Track): Track? { suspend fun statusLibManga(track: Track): Track? {
return withIOContext { return withIOContext {
val urlUserRead = "$apiUrl/collection/${track.remote_id}" val urlUserRead = "$API_URL/collection/${track.remote_id}"
val requestUserRead = Request.Builder() val requestUserRead = Request.Builder()
.url(urlUserRead) .url(urlUserRead)
.cacheControl(CacheControl.FORCE_NETWORK) .cacheControl(CacheControl.FORCE_NETWORK)
@ -171,41 +171,41 @@ class BangumiApi(
} }
private fun accessTokenRequest(code: String) = POST( private fun accessTokenRequest(code: String) = POST(
oauthUrl, OAUTH_URL,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "authorization_code") .add("grant_type", "authorization_code")
.add("client_id", clientId) .add("client_id", CLIENT_ID)
.add("client_secret", clientSecret) .add("client_secret", CLIENT_SECRET)
.add("code", code) .add("code", code)
.add("redirect_uri", redirectUrl) .add("redirect_uri", REDIRECT_URL)
.build(), .build(),
) )
companion object { companion object {
private const val clientId = "bgm291665acbd06a4c28" private const val CLIENT_ID = "bgm291665acbd06a4c28"
private const val clientSecret = "43e5ce36b207de16e5d3cfd3e79118db" private const val CLIENT_SECRET = "43e5ce36b207de16e5d3cfd3e79118db"
private const val apiUrl = "https://api.bgm.tv" private const val API_URL = "https://api.bgm.tv"
private const val oauthUrl = "https://bgm.tv/oauth/access_token" private const val OAUTH_URL = "https://bgm.tv/oauth/access_token"
private const val loginUrl = "https://bgm.tv/oauth/authorize" private const val LOGIN_URL = "https://bgm.tv/oauth/authorize"
private const val redirectUrl = "mihon://bangumi-auth" private const val REDIRECT_URL = "mihon://bangumi-auth"
fun authUrl(): Uri = fun authUrl(): Uri =
loginUrl.toUri().buildUpon() LOGIN_URL.toUri().buildUpon()
.appendQueryParameter("client_id", clientId) .appendQueryParameter("client_id", CLIENT_ID)
.appendQueryParameter("response_type", "code") .appendQueryParameter("response_type", "code")
.appendQueryParameter("redirect_uri", redirectUrl) .appendQueryParameter("redirect_uri", REDIRECT_URL)
.build() .build()
fun refreshTokenRequest(token: String) = POST( fun refreshTokenRequest(token: String) = POST(
oauthUrl, OAUTH_URL,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "refresh_token") .add("grant_type", "refresh_token")
.add("client_id", clientId) .add("client_id", CLIENT_ID)
.add("client_secret", clientSecret) .add("client_secret", CLIENT_SECRET)
.add("refresh_token", token) .add("refresh_token", token)
.add("redirect_uri", redirectUrl) .add("redirect_uri", REDIRECT_URL)
.build(), .build(),
) )
} }

View File

@ -66,7 +66,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
with(json) { with(json) {
authClient.newCall( authClient.newCall(
POST( POST(
"${baseUrl}library-entries", "${BASE_URL}library-entries",
headers = headersOf( headers = headersOf(
"Content-Type", "Content-Type",
"application/vnd.api+json", "application/vnd.api+json",
@ -104,7 +104,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
with(json) { with(json) {
authClient.newCall( authClient.newCall(
Request.Builder() Request.Builder()
.url("${baseUrl}library-entries/${track.remote_id}") .url("${BASE_URL}library-entries/${track.remote_id}")
.headers( .headers(
headersOf( headersOf(
"Content-Type", "Content-Type",
@ -130,7 +130,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
authClient authClient
.newCall( .newCall(
DELETE( DELETE(
"${baseUrl}library-entries/${track.remoteId}", "${BASE_URL}library-entries/${track.remoteId}",
headers = headersOf( headers = headersOf(
"Content-Type", "Content-Type",
"application/vnd.api+json", "application/vnd.api+json",
@ -143,7 +143,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
suspend fun search(query: String): List<TrackSearch> { suspend fun search(query: String): List<TrackSearch> {
return withIOContext { return withIOContext {
with(json) { with(json) {
authClient.newCall(GET(algoliaKeyUrl)) authClient.newCall(GET(ALGOLIA_KEY_URL))
.awaitSuccess() .awaitSuccess()
.parseAs<JsonObject>() .parseAs<JsonObject>()
.let { .let {
@ -157,16 +157,16 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
private suspend fun algoliaSearch(key: String, query: String): List<TrackSearch> { private suspend fun algoliaSearch(key: String, query: String): List<TrackSearch> {
return withIOContext { return withIOContext {
val jsonObject = buildJsonObject { val jsonObject = buildJsonObject {
put("params", "query=${URLEncoder.encode(query, StandardCharsets.UTF_8.name())}$algoliaFilter") put("params", "query=${URLEncoder.encode(query, StandardCharsets.UTF_8.name())}$ALGOLIA_FILTER")
} }
with(json) { with(json) {
client.newCall( client.newCall(
POST( POST(
algoliaUrl, ALGOLIA_URL,
headers = headersOf( headers = headersOf(
"X-Algolia-Application-Id", "X-Algolia-Application-Id",
algoliaAppId, ALGOLIA_APP_ID,
"X-Algolia-API-Key", "X-Algolia-API-Key",
key, key,
), ),
@ -187,7 +187,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
suspend fun findLibManga(track: Track, userId: String): Track? { suspend fun findLibManga(track: Track, userId: String): Track? {
return withIOContext { return withIOContext {
val url = "${baseUrl}library-entries".toUri().buildUpon() val url = "${BASE_URL}library-entries".toUri().buildUpon()
.encodedQuery("filter[manga_id]=${track.remote_id}&filter[user_id]=$userId") .encodedQuery("filter[manga_id]=${track.remote_id}&filter[user_id]=$userId")
.appendQueryParameter("include", "manga") .appendQueryParameter("include", "manga")
.build() .build()
@ -210,7 +210,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
suspend fun getLibManga(track: Track): Track { suspend fun getLibManga(track: Track): Track {
return withIOContext { return withIOContext {
val url = "${baseUrl}library-entries".toUri().buildUpon() val url = "${BASE_URL}library-entries".toUri().buildUpon()
.encodedQuery("filter[id]=${track.remote_id}") .encodedQuery("filter[id]=${track.remote_id}")
.appendQueryParameter("include", "manga") .appendQueryParameter("include", "manga")
.build() .build()
@ -237,11 +237,11 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.add("username", username) .add("username", username)
.add("password", password) .add("password", password)
.add("grant_type", "password") .add("grant_type", "password")
.add("client_id", clientId) .add("client_id", CLIENT_ID)
.add("client_secret", clientSecret) .add("client_secret", CLIENT_SECRET)
.build() .build()
with(json) { with(json) {
client.newCall(POST(loginUrl, body = formBody)) client.newCall(POST(LOGIN_URL, body = formBody))
.awaitSuccess() .awaitSuccess()
.parseAs() .parseAs()
} }
@ -250,7 +250,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
suspend fun getCurrentUser(): String { suspend fun getCurrentUser(): String {
return withIOContext { return withIOContext {
val url = "${baseUrl}users".toUri().buildUpon() val url = "${BASE_URL}users".toUri().buildUpon()
.encodedQuery("filter[self]=true") .encodedQuery("filter[self]=true")
.build() .build()
with(json) { with(json) {
@ -265,35 +265,31 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
} }
companion object { companion object {
private const val clientId = private const val CLIENT_ID = "dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd"
"dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd" private const val CLIENT_SECRET = "54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151"
private const val clientSecret =
"54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151"
private const val baseUrl = "https://kitsu.app/api/edge/" private const val BASE_URL = "https://kitsu.app/api/edge/"
private const val loginUrl = "https://kitsu.app/api/oauth/token" private const val LOGIN_URL = "https://kitsu.app/api/oauth/token"
private const val baseMangaUrl = "https://kitsu.app/manga/" private const val BASE_MANGA_URL = "https://kitsu.app/manga/"
private const val algoliaKeyUrl = "https://kitsu.app/api/edge/algolia-keys/media/" private const val ALGOLIA_KEY_URL = "https://kitsu.app/api/edge/algolia-keys/media/"
private const val algoliaUrl = private const val ALGOLIA_APP_ID = "AWQO5J657S"
"https://AWQO5J657S-dsn.algolia.net/1/indexes/production_media/query/" private const val ALGOLIA_URL = "https://$ALGOLIA_APP_ID-dsn.algolia.net/1/indexes/production_media/query/"
private const val algoliaAppId = "AWQO5J657S" private const val ALGOLIA_FILTER = "&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=" +
private const val algoliaFilter =
"&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=" +
"%5B%22synopsis%22%2C%22averageRating%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22" + "%5B%22synopsis%22%2C%22averageRating%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22" +
"posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D" "posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D"
fun mangaUrl(remoteId: Long): String { fun mangaUrl(remoteId: Long): String {
return baseMangaUrl + remoteId return BASE_MANGA_URL + remoteId
} }
fun refreshTokenRequest(token: String) = POST( fun refreshTokenRequest(token: String) = POST(
loginUrl, LOGIN_URL,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "refresh_token") .add("grant_type", "refresh_token")
.add("refresh_token", token) .add("refresh_token", token)
.add("client_id", clientId) .add("client_id", CLIENT_ID)
.add("client_secret", clientSecret) .add("client_secret", CLIENT_SECRET)
.build(), .build(),
) )
} }

View File

@ -6,8 +6,8 @@ import java.util.Locale
object KitsuDateHelper { object KitsuDateHelper {
private const val pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" private const val PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
private val formatter = SimpleDateFormat(pattern, Locale.ENGLISH) private val formatter = SimpleDateFormat(PATTERN, Locale.ENGLISH)
fun convert(dateValue: Long): String? { fun convert(dateValue: Long): String? {
if (dateValue == 0L) return null if (dateValue == 0L) return null

View File

@ -1,3 +1,5 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
package eu.kanade.tachiyomi.data.track.model package eu.kanade.tachiyomi.data.track.model
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track

View File

@ -54,7 +54,7 @@ class ShikimoriApi(
} }
authClient.newCall( authClient.newCall(
POST( POST(
"$apiUrl/v2/user_rates", "$API_URL/v2/user_rates",
body = payload.toString().toRequestBody(jsonMime), body = payload.toString().toRequestBody(jsonMime),
), ),
).awaitSuccess() ).awaitSuccess()
@ -72,14 +72,14 @@ class ShikimoriApi(
suspend fun deleteLibManga(track: DomainTrack) { suspend fun deleteLibManga(track: DomainTrack) {
withIOContext { withIOContext {
authClient authClient
.newCall(DELETE("$apiUrl/v2/user_rates/${track.libraryId}")) .newCall(DELETE("$API_URL/v2/user_rates/${track.libraryId}"))
.awaitSuccess() .awaitSuccess()
} }
} }
suspend fun search(search: String): List<TrackSearch> { suspend fun search(search: String): List<TrackSearch> {
return withIOContext { return withIOContext {
val url = "$apiUrl/mangas".toUri().buildUpon() val url = "$API_URL/mangas".toUri().buildUpon()
.appendQueryParameter("order", "popularity") .appendQueryParameter("order", "popularity")
.appendQueryParameter("search", search) .appendQueryParameter("search", search)
.appendQueryParameter("limit", "20") .appendQueryParameter("limit", "20")
@ -102,10 +102,10 @@ class ShikimoriApi(
remote_id = obj["id"]!!.jsonPrimitive.long remote_id = obj["id"]!!.jsonPrimitive.long
title = obj["name"]!!.jsonPrimitive.content title = obj["name"]!!.jsonPrimitive.content
total_chapters = obj["chapters"]!!.jsonPrimitive.long total_chapters = obj["chapters"]!!.jsonPrimitive.long
cover_url = baseUrl + obj["image"]!!.jsonObject["preview"]!!.jsonPrimitive.content cover_url = BASE_URL + obj["image"]!!.jsonObject["preview"]!!.jsonPrimitive.content
summary = "" summary = ""
score = obj["score"]!!.jsonPrimitive.double score = obj["score"]!!.jsonPrimitive.double
tracking_url = baseUrl + obj["url"]!!.jsonPrimitive.content tracking_url = BASE_URL + obj["url"]!!.jsonPrimitive.content
publishing_status = obj["status"]!!.jsonPrimitive.content publishing_status = obj["status"]!!.jsonPrimitive.content
publishing_type = obj["kind"]!!.jsonPrimitive.content publishing_type = obj["kind"]!!.jsonPrimitive.content
start_date = obj["aired_on"]!!.jsonPrimitive.contentOrNull ?: "" start_date = obj["aired_on"]!!.jsonPrimitive.contentOrNull ?: ""
@ -121,13 +121,13 @@ class ShikimoriApi(
last_chapter_read = obj["chapters"]!!.jsonPrimitive.double last_chapter_read = obj["chapters"]!!.jsonPrimitive.double
score = obj["score"]!!.jsonPrimitive.int.toDouble() score = obj["score"]!!.jsonPrimitive.int.toDouble()
status = toTrackStatus(obj["status"]!!.jsonPrimitive.content) status = toTrackStatus(obj["status"]!!.jsonPrimitive.content)
tracking_url = baseUrl + mangas["url"]!!.jsonPrimitive.content tracking_url = BASE_URL + mangas["url"]!!.jsonPrimitive.content
} }
} }
suspend fun findLibManga(track: Track, userId: String): Track? { suspend fun findLibManga(track: Track, userId: String): Track? {
return withIOContext { return withIOContext {
val urlMangas = "$apiUrl/mangas".toUri().buildUpon() val urlMangas = "$API_URL/mangas".toUri().buildUpon()
.appendPath(track.remote_id.toString()) .appendPath(track.remote_id.toString())
.build() .build()
val mangas = with(json) { val mangas = with(json) {
@ -136,7 +136,7 @@ class ShikimoriApi(
.parseAs<JsonObject>() .parseAs<JsonObject>()
} }
val url = "$apiUrl/v2/user_rates".toUri().buildUpon() val url = "$API_URL/v2/user_rates".toUri().buildUpon()
.appendQueryParameter("user_id", userId) .appendQueryParameter("user_id", userId)
.appendQueryParameter("target_id", track.remote_id.toString()) .appendQueryParameter("target_id", track.remote_id.toString())
.appendQueryParameter("target_type", "Manga") .appendQueryParameter("target_type", "Manga")
@ -160,7 +160,7 @@ class ShikimoriApi(
suspend fun getCurrentUser(): Int { suspend fun getCurrentUser(): Int {
return with(json) { return with(json) {
authClient.newCall(GET("$apiUrl/users/whoami")) authClient.newCall(GET("$API_URL/users/whoami"))
.awaitSuccess() .awaitSuccess()
.parseAs<JsonObject>() .parseAs<JsonObject>()
.let { .let {
@ -180,39 +180,39 @@ class ShikimoriApi(
} }
private fun accessTokenRequest(code: String) = POST( private fun accessTokenRequest(code: String) = POST(
oauthUrl, OAUTH_URL,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "authorization_code") .add("grant_type", "authorization_code")
.add("client_id", clientId) .add("client_id", CLIENT_ID)
.add("client_secret", clientSecret) .add("client_secret", CLIENT_SECRET)
.add("code", code) .add("code", code)
.add("redirect_uri", redirectUrl) .add("redirect_uri", REDIRECT_URL)
.build(), .build(),
) )
companion object { companion object {
private const val clientId = "PB9dq8DzI405s7wdtwTdirYqHiyVMh--djnP7lBUqSA" private const val CLIENT_ID = "PB9dq8DzI405s7wdtwTdirYqHiyVMh--djnP7lBUqSA"
private const val clientSecret = "NajpZcOBKB9sJtgNcejf8OB9jBN1OYYoo-k4h2WWZus" private const val CLIENT_SECRET = "NajpZcOBKB9sJtgNcejf8OB9jBN1OYYoo-k4h2WWZus"
private const val baseUrl = "https://shikimori.one" private const val BASE_URL = "https://shikimori.one"
private const val apiUrl = "$baseUrl/api" private const val API_URL = "$BASE_URL/api"
private const val oauthUrl = "$baseUrl/oauth/token" private const val OAUTH_URL = "$BASE_URL/oauth/token"
private const val loginUrl = "$baseUrl/oauth/authorize" private const val LOGIN_URL = "$BASE_URL/oauth/authorize"
private const val redirectUrl = "mihon://shikimori-auth" private const val REDIRECT_URL = "mihon://shikimori-auth"
fun authUrl(): Uri = loginUrl.toUri().buildUpon() fun authUrl(): Uri = LOGIN_URL.toUri().buildUpon()
.appendQueryParameter("client_id", clientId) .appendQueryParameter("client_id", CLIENT_ID)
.appendQueryParameter("redirect_uri", redirectUrl) .appendQueryParameter("redirect_uri", REDIRECT_URL)
.appendQueryParameter("response_type", "code") .appendQueryParameter("response_type", "code")
.build() .build()
fun refreshTokenRequest(token: String) = POST( fun refreshTokenRequest(token: String) = POST(
oauthUrl, OAUTH_URL,
body = FormBody.Builder() body = FormBody.Builder()
.add("grant_type", "refresh_token") .add("grant_type", "refresh_token")
.add("client_id", clientId) .add("client_id", CLIENT_ID)
.add("client_secret", clientSecret) .add("client_secret", CLIENT_SECRET)
.add("refresh_token", token) .add("refresh_token", token)
.build(), .build(),
) )

View File

@ -69,17 +69,18 @@ class ExtensionManager(
private val iconMap = mutableMapOf<String, Drawable>() private val iconMap = mutableMapOf<String, Drawable>()
private val _installedExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Installed>()) private val installedExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Installed>())
val installedExtensionsFlow = _installedExtensionsMapFlow.mapExtensions(scope) val installedExtensionsFlow = installedExtensionMapFlow.mapExtensions(scope)
private val availableExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Available>())
private val _availableExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Available>())
// SY --> // SY -->
val availableExtensionsFlow = _availableExtensionsMapFlow.map { it.filterNotBlacklisted().values.toList() } val availableExtensionsFlow = availableExtensionMapFlow.map { it.filterNotBlacklisted().values.toList() }
.stateIn(scope, SharingStarted.Lazily, _availableExtensionsMapFlow.value.values.toList()) .stateIn(scope, SharingStarted.Lazily, availableExtensionMapFlow.value.values.toList())
// SY <-- // SY <--
private val _untrustedExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Untrusted>()) private val untrustedExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Untrusted>())
val untrustedExtensionsFlow = _untrustedExtensionsMapFlow.mapExtensions(scope) val untrustedExtensionsFlow = untrustedExtensionMapFlow.mapExtensions(scope)
init { init {
initExtensions() initExtensions()
@ -89,7 +90,7 @@ class ExtensionManager(
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet() private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()
fun getAppIconForSource(sourceId: Long): Drawable? { fun getAppIconForSource(sourceId: Long): Drawable? {
val pkgName = _installedExtensionsMapFlow.value.values val pkgName = installedExtensionMapFlow.value.values
.find { ext -> .find { ext ->
ext.sources.any { it.id == sourceId } ext.sources.any { it.id == sourceId }
} }
@ -128,11 +129,11 @@ class ExtensionManager(
private fun initExtensions() { private fun initExtensions() {
val extensions = ExtensionLoader.loadExtensions(context) val extensions = ExtensionLoader.loadExtensions(context)
_installedExtensionsMapFlow.value = extensions installedExtensionMapFlow.value = extensions
.filterIsInstance<LoadResult.Success>() .filterIsInstance<LoadResult.Success>()
.associate { it.extension.pkgName to it.extension } .associate { it.extension.pkgName to it.extension }
_untrustedExtensionsMapFlow.value = extensions untrustedExtensionMapFlow.value = extensions
.filterIsInstance<LoadResult.Untrusted>() .filterIsInstance<LoadResult.Untrusted>()
.associate { it.extension.pkgName to it.extension } .associate { it.extension.pkgName to it.extension }
// SY --> // SY -->
@ -159,7 +160,7 @@ class ExtensionManager(
// EXH <-- // EXH <--
/** /**
* Finds the available extensions in the [api] and updates [_availableExtensionsMapFlow]. * Finds the available extensions in the [api] and updates [availableExtensionMapFlow].
*/ */
suspend fun findAvailableExtensions() { suspend fun findAvailableExtensions() {
val extensions: List<Extension.Available> = try { val extensions: List<Extension.Available> = try {
@ -172,7 +173,7 @@ class ExtensionManager(
enableAdditionalSubLanguages(extensions) enableAdditionalSubLanguages(extensions)
_availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName } availableExtensionMapFlow.value = extensions.associateBy { it.pkgName }
updatedInstalledExtensionsStatuses(extensions) updatedInstalledExtensionsStatuses(extensions)
setupAvailableExtensionsSourcesDataMap(extensions) setupAvailableExtensionsSourcesDataMap(extensions)
} }
@ -218,7 +219,7 @@ class ExtensionManager(
return return
} }
val installedExtensionsMap = _installedExtensionsMapFlow.value.toMutableMap() val installedExtensionsMap = installedExtensionMapFlow.value.toMutableMap()
var changed = false var changed = false
for ((pkgName, extension) in installedExtensionsMap) { for ((pkgName, extension) in installedExtensionsMap) {
val availableExt = availableExtensions.find { it.pkgName == pkgName } val availableExt = availableExtensions.find { it.pkgName == pkgName }
@ -247,7 +248,7 @@ class ExtensionManager(
} }
} }
if (changed) { if (changed) {
_installedExtensionsMapFlow.value = installedExtensionsMap installedExtensionMapFlow.value = installedExtensionsMap
} }
updatePendingUpdatesCount() updatePendingUpdatesCount()
} }
@ -271,7 +272,7 @@ class ExtensionManager(
* @param extension The extension to be updated. * @param extension The extension to be updated.
*/ */
fun updateExtension(extension: Extension.Installed): Flow<InstallStep> { fun updateExtension(extension: Extension.Installed): Flow<InstallStep> {
val availableExt = _availableExtensionsMapFlow.value[extension.pkgName] ?: return emptyFlow() val availableExt = availableExtensionMapFlow.value[extension.pkgName] ?: return emptyFlow()
return installExtension(availableExt) return installExtension(availableExt)
} }
@ -308,11 +309,11 @@ class ExtensionManager(
* @param extension the extension to trust * @param extension the extension to trust
*/ */
suspend fun trust(extension: Extension.Untrusted) { suspend fun trust(extension: Extension.Untrusted) {
_untrustedExtensionsMapFlow.value[extension.pkgName] ?: return untrustedExtensionMapFlow.value[extension.pkgName] ?: return
trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash) trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash)
_untrustedExtensionsMapFlow.value -= extension.pkgName untrustedExtensionMapFlow.value -= extension.pkgName
ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName)
.let { it as? LoadResult.Success } .let { it as? LoadResult.Success }
@ -332,7 +333,7 @@ class ExtensionManager(
} }
// SY <-- // SY <--
_installedExtensionsMapFlow.value += extension installedExtensionMapFlow.value += extension
} }
/** /**
@ -349,7 +350,7 @@ class ExtensionManager(
} }
// SY <-- // SY <--
_installedExtensionsMapFlow.value += extension installedExtensionMapFlow.value += extension
} }
/** /**
@ -359,8 +360,8 @@ class ExtensionManager(
* @param pkgName The package name of the uninstalled application. * @param pkgName The package name of the uninstalled application.
*/ */
private fun unregisterExtension(pkgName: String) { private fun unregisterExtension(pkgName: String) {
_installedExtensionsMapFlow.value -= pkgName installedExtensionMapFlow.value -= pkgName
_untrustedExtensionsMapFlow.value -= pkgName untrustedExtensionMapFlow.value -= pkgName
} }
/** /**
@ -379,8 +380,8 @@ class ExtensionManager(
} }
override fun onExtensionUntrusted(extension: Extension.Untrusted) { override fun onExtensionUntrusted(extension: Extension.Untrusted) {
_installedExtensionsMapFlow.value -= extension.pkgName installedExtensionMapFlow.value -= extension.pkgName
_untrustedExtensionsMapFlow.value += extension untrustedExtensionMapFlow.value += extension
updatePendingUpdatesCount() updatePendingUpdatesCount()
} }
@ -404,14 +405,14 @@ class ExtensionManager(
private fun Extension.Installed.updateExists(availableExtension: Extension.Available? = null): Boolean { private fun Extension.Installed.updateExists(availableExtension: Extension.Available? = null): Boolean {
val availableExt = availableExtension val availableExt = availableExtension
?: _availableExtensionsMapFlow.value[pkgName] ?: availableExtensionMapFlow.value[pkgName]
?: return false ?: return false
return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion) return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
} }
private fun updatePendingUpdatesCount() { private fun updatePendingUpdatesCount() {
val pendingUpdateCount = _installedExtensionsMapFlow.value.values.count { it.hasUpdate } val pendingUpdateCount = installedExtensionMapFlow.value.values.count { it.hasUpdate }
preferences.extensionUpdatesCount().set(pendingUpdateCount) preferences.extensionUpdatesCount().set(pendingUpdateCount)
if (pendingUpdateCount == 0) { if (pendingUpdateCount == 0) {
ExtensionUpdateNotifier(context).dismiss() ExtensionUpdateNotifier(context).dismiss()

View File

@ -34,8 +34,10 @@ internal class ExtensionApi {
private val getExtensionRepo: GetExtensionRepo by injectLazy() private val getExtensionRepo: GetExtensionRepo by injectLazy()
private val updateExtensionRepo: UpdateExtensionRepo by injectLazy() private val updateExtensionRepo: UpdateExtensionRepo by injectLazy()
private val extensionManager: ExtensionManager by injectLazy() private val extensionManager: ExtensionManager by injectLazy()
// SY --> // SY -->
private val sourcePreferences: SourcePreferences by injectLazy() private val sourcePreferences: SourcePreferences by injectLazy()
// SY <-- // SY <--
private val json: Json by injectLazy() private val json: Json by injectLazy()

View File

@ -1,7 +1,13 @@
package eu.kanade.tachiyomi.extension.model package eu.kanade.tachiyomi.extension.model
enum class InstallStep { enum class InstallStep {
Idle, Pending, Downloading, Installing, Installed, Error; Idle,
Pending,
Downloading,
Installing,
Installed,
Error,
;
fun isCompleted(): Boolean { fun isCompleted(): Boolean {
return this == Installed || this == Error || this == Idle return this == Installed || this == Error || this == Idle

View File

@ -223,7 +223,6 @@ internal object ExtensionLoader {
* @param context The application context. * @param context The application context.
* @param extensionInfo The extension to load. * @param extensionInfo The extension to load.
*/ */
@Suppress("LongMethod", "CyclomaticComplexMethod", "ReturnCount")
private suspend fun loadExtension(context: Context, extensionInfo: ExtensionInfo): LoadResult { private suspend fun loadExtension(context: Context, extensionInfo: ExtensionInfo): LoadResult {
val pkgManager = context.packageManager val pkgManager = context.packageManager
val pkgInfo = extensionInfo.packageInfo val pkgInfo = extensionInfo.packageInfo

View File

@ -383,7 +383,7 @@ class EHentai(
doc.select("#gdd .gdt1").find { el -> doc.select("#gdd .gdt1").find { el ->
el.text().lowercase() == "posted:" el.text().lowercase() == "posted:"
}!!.nextElementSibling()!!.text(), }!!.nextElementSibling()!!.text(),
MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC) MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC),
)!!.toInstant().toEpochMilli(), )!!.toInstant().toEpochMilli(),
scanlator = EHentaiSearchMetadata.galleryId(location), scanlator = EHentaiSearchMetadata.galleryId(location),
) )
@ -401,7 +401,7 @@ class EHentai(
chapter_number = index + 2f, chapter_number = index + 2f,
date_upload = ZonedDateTime.parse( date_upload = ZonedDateTime.parse(
posted, posted,
MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC) MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC),
).toInstant().toEpochMilli(), ).toInstant().toEpochMilli(),
scanlator = EHentaiSearchMetadata.galleryId(link), scanlator = EHentaiSearchMetadata.galleryId(link),
) )
@ -542,7 +542,8 @@ class EHentai(
if ( if (
MATCH_SEEK_REGEX.matches(jumpSeekValue) || MATCH_SEEK_REGEX.matches(jumpSeekValue) ||
( (
MATCH_YEAR_REGEX.matches(jumpSeekValue) && jumpSeekValue.toIntOrNull()?.let { MATCH_YEAR_REGEX.matches(jumpSeekValue) &&
jumpSeekValue.toIntOrNull()?.let {
it in 2007..2099 it in 2007..2099
} == true } == true
) )
@ -715,7 +716,7 @@ class EHentai(
when (left.removeSuffix(":").lowercase()) { when (left.removeSuffix(":").lowercase()) {
"posted" -> datePosted = ZonedDateTime.parse( "posted" -> datePosted = ZonedDateTime.parse(
right, right,
MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC) MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC),
).toInstant().toEpochMilli() ).toInstant().toEpochMilli()
// Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/ // Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/
// Example JP gallery: https://exhentai.org/g/1375385/03519d541b/ // Example JP gallery: https://exhentai.org/g/1375385/03519d541b/

View File

@ -119,7 +119,8 @@ class MergedSource : HttpSource() {
"Manga references are empty, chapters unavailable, merge is likely corrupted" "Manga references are empty, chapters unavailable, merge is likely corrupted"
} }
val ifDownloadNewChapters = downloadChapters && manga.shouldDownloadNewChapters( val ifDownloadNewChapters = downloadChapters &&
manga.shouldDownloadNewChapters(
getCategories.await(manga.id).map { getCategories.await(manga.id).map {
it.id it.id
}, },

View File

@ -73,7 +73,8 @@ interface SecureActivityDelegate {
} }
val lockedDays = preferences.authenticatorDays().get() val lockedDays = preferences.authenticatorDays().get()
val canLockToday = lockedDays == LOCK_ALL_DAYS || when (today.get(Calendar.DAY_OF_WEEK)) { val canLockToday = lockedDays == LOCK_ALL_DAYS ||
when (today.get(Calendar.DAY_OF_WEEK)) {
Calendar.SUNDAY -> (lockedDays and LOCK_SUNDAY) == LOCK_SUNDAY Calendar.SUNDAY -> (lockedDays and LOCK_SUNDAY) == LOCK_SUNDAY
Calendar.MONDAY -> (lockedDays and LOCK_MONDAY) == LOCK_MONDAY Calendar.MONDAY -> (lockedDays and LOCK_MONDAY) == LOCK_MONDAY
Calendar.TUESDAY -> (lockedDays and LOCK_TUESDAY) == LOCK_TUESDAY Calendar.TUESDAY -> (lockedDays and LOCK_TUESDAY) == LOCK_TUESDAY
@ -99,7 +100,9 @@ interface SecureActivityDelegate {
// `requireUnlock` can be true on process start or if app was closed in locked state // `requireUnlock` can be true on process start or if app was closed in locked state
if (!AuthenticatorUtil.isAuthenticating && !requireUnlock) { if (!AuthenticatorUtil.isAuthenticating && !requireUnlock) {
requireUnlock = /* SY --> */ canLockNow(preferences) && /* SY <-- */ when (val lockDelay = preferences.lockAppAfter().get()) { requireUnlock =
/* SY --> */ canLockNow(preferences) &&
/* SY <-- */ when (val lockDelay = preferences.lockAppAfter().get()) {
-1 -> false // Never -1 -> false // Never
0 -> true // Always 0 -> true // Always
else -> lastClosedPref.get() + lockDelay * 60_000 <= System.currentTimeMillis() else -> lastClosedPref.get() + lockDelay * 60_000 <= System.currentTimeMillis()
@ -140,7 +143,7 @@ class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObser
val incognitoModeFlow = preferences.incognitoMode().changes() val incognitoModeFlow = preferences.incognitoMode().changes()
combine(secureScreenFlow, incognitoModeFlow) { secureScreen, incognitoMode -> combine(secureScreenFlow, incognitoModeFlow) { secureScreen, incognitoMode ->
secureScreen == SecurityPreferences.SecureScreenMode.ALWAYS || secureScreen == SecurityPreferences.SecureScreenMode.ALWAYS ||
secureScreen == SecurityPreferences.SecureScreenMode.INCOGNITO && incognitoMode (secureScreen == SecurityPreferences.SecureScreenMode.INCOGNITO && incognitoMode)
} }
.onEach(activity.window::setSecureScreen) .onEach(activity.window::setSecureScreen)
.launchIn(activity.lifecycleScope) .launchIn(activity.lifecycleScope)

View File

@ -41,7 +41,7 @@ class ExtensionsScreenModel(
private val getExtensions: GetExtensionsByType = Injekt.get(), private val getExtensions: GetExtensionsByType = Injekt.get(),
) : StateScreenModel<ExtensionsScreenModel.State>(State()) { ) : StateScreenModel<ExtensionsScreenModel.State>(State()) {
private var _currentDownloads = MutableStateFlow<Map<String, InstallStep>>(hashMapOf()) private val currentDownloads = MutableStateFlow<Map<String, InstallStep>>(hashMapOf())
init { init {
val context = Injekt.get<Application>() val context = Injekt.get<Application>()
@ -62,14 +62,20 @@ class ExtensionsScreenModel(
it.name.contains(input, ignoreCase = true) || it.name.contains(input, ignoreCase = true) ||
it.baseUrl.contains(input, ignoreCase = true) || it.baseUrl.contains(input, ignoreCase = true) ||
it.id == input.toLongOrNull() it.id == input.toLongOrNull()
} || extension.name.contains(input, ignoreCase = true) } ||
extension.name.contains(input, ignoreCase = true)
} }
is Extension.Installed -> { is Extension.Installed -> {
extension.sources.any { extension.sources.any {
it.name.contains(input, ignoreCase = true) || it.name.contains(input, ignoreCase = true) ||
it.id == input.toLongOrNull() || it.id == input.toLongOrNull() ||
if (it is HttpSource) { it.baseUrl.contains(input, ignoreCase = true) } else false if (it is HttpSource) {
} || extension.name.contains(input, ignoreCase = true) it.baseUrl.contains(input, ignoreCase = true)
} else {
false
}
} ||
extension.name.contains(input, ignoreCase = true)
} }
is Extension.Untrusted -> extension.name.contains(input, ignoreCase = true) is Extension.Untrusted -> extension.name.contains(input, ignoreCase = true)
} }
@ -80,7 +86,7 @@ class ExtensionsScreenModel(
screenModelScope.launchIO { screenModelScope.launchIO {
combine( combine(
state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS),
_currentDownloads, currentDownloads,
getExtensions.subscribe(), getExtensions.subscribe(),
) { query, downloads, (_updates, _installed, _available, _untrusted) -> ) { query, downloads, (_updates, _installed, _available, _untrusted) ->
val searchQuery = query ?: "" val searchQuery = query ?: ""
@ -103,7 +109,8 @@ class ExtensionsScreenModel(
.groupBy { it.lang } .groupBy { it.lang }
.toSortedMap(LocaleHelper.comparator) .toSortedMap(LocaleHelper.comparator)
.map { (lang, exts) -> .map { (lang, exts) ->
ExtensionUiModel.Header.Text(LocaleHelper.getSourceDisplayName(lang, context)) to exts.map(extensionMapper(downloads)) ExtensionUiModel.Header.Text(LocaleHelper.getSourceDisplayName(lang, context)) to
exts.map(extensionMapper(downloads))
} }
if (languagesWithExtensions.isNotEmpty()) { if (languagesWithExtensions.isNotEmpty()) {
itemsGroups.putAll(languagesWithExtensions) itemsGroups.putAll(languagesWithExtensions)
@ -165,11 +172,11 @@ class ExtensionsScreenModel(
} }
private fun addDownloadState(extension: Extension, installStep: InstallStep) { private fun addDownloadState(extension: Extension, installStep: InstallStep) {
_currentDownloads.update { it + Pair(extension.pkgName, installStep) } currentDownloads.update { it + Pair(extension.pkgName, installStep) }
} }
private fun removeDownloadState(extension: Extension) { private fun removeDownloadState(extension: Extension) {
_currentDownloads.update { it - extension.pkgName } currentDownloads.update { it - extension.pkgName }
} }
private suspend fun Flow<InstallStep>.collectToInstallUpdate(extension: Extension) = private suspend fun Flow<InstallStep>.collectToInstallUpdate(extension: Extension) =

View File

@ -121,7 +121,11 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen
) )
val onDismissRequest = { screenModel.dialog.value = null } val onDismissRequest = { screenModel.dialog.value = null }
when (@Suppress("NAME_SHADOWING") val dialog = dialog) { when
(
@Suppress("NAME_SHADOWING")
val dialog = dialog
) {
is MigrationListScreenModel.Dialog.MigrateMangaDialog -> { is MigrationListScreenModel.Dialog.MigrateMangaDialog -> {
MigrationMangaDialog( MigrationMangaDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,

View File

@ -223,7 +223,9 @@ class MigrationListScreenModel(
smartSearchEngine.normalSearch(source, mangaObj.ogTitle) smartSearchEngine.normalSearch(source, mangaObj.ogTitle)
} }
if (searchResult != null && !(searchResult.url == mangaObj.url && source.id == mangaObj.source)) { if (searchResult != null &&
!(searchResult.url == mangaObj.url && source.id == mangaObj.source)
) {
val localManga = networkToLocalManga.await(searchResult) val localManga = networkToLocalManga.await(searchResult)
val chapters = if (source is EHentai) { val chapters = if (source is EHentai) {
@ -237,7 +239,8 @@ class MigrationListScreenModel(
} catch (e: Exception) { } catch (e: Exception) {
return@async2 null return@async2 null
} }
manga.progress.value = validSources.size to processedSources.incrementAndGet() manga.progress.value =
validSources.size to processedSources.incrementAndGet()
localManga to chapters.size localManga to chapters.size
} else { } else {
null null
@ -314,7 +317,8 @@ class MigrationListScreenModel(
if (result == null && hideNotFound) { if (result == null && hideNotFound) {
removeManga(manga) removeManga(manga)
} }
if (result != null && showOnlyUpdates && if (result != null &&
showOnlyUpdates &&
(getChapterInfo(result.id).latestChapter ?: 0.0) <= (manga.chapterInfo.latestChapter ?: 0.0) (getChapterInfo(result.id).latestChapter ?: 0.0) <= (manga.chapterInfo.latestChapter ?: 0.0)
) { ) {
removeManga(manga) removeManga(manga)
@ -363,7 +367,10 @@ class MigrationListScreenModel(
dbChapters.forEach { chapter -> dbChapters.forEach { chapter ->
if (chapter.isRecognizedNumber) { if (chapter.isRecognizedNumber) {
val prevChapter = prevMangaChapters.find { it.isRecognizedNumber && it.chapterNumber == chapter.chapterNumber } val prevChapter = prevMangaChapters.find {
it.isRecognizedNumber &&
it.chapterNumber == chapter.chapterNumber
}
if (prevChapter != null) { if (prevChapter != null) {
chapterUpdates += ChapterUpdate( chapterUpdates += ChapterUpdate(
id = chapter.id, id = chapter.id,

View File

@ -119,7 +119,10 @@ class SourcesScreenModel(
items = byLang items = byLang
.flatMap { .flatMap {
listOf( listOf(
SourceUiModel.Header(it.key.removePrefix(CATEGORY_KEY_PREFIX), it.value.firstOrNull()?.category != null), SourceUiModel.Header(
it.key.removePrefix(CATEGORY_KEY_PREFIX),
it.value.firstOrNull()?.category != null,
),
*it.value.map { source -> *it.value.map { source ->
SourceUiModel.Item(source) SourceUiModel.Item(source)
}.toTypedArray(), }.toTypedArray(),

View File

@ -48,7 +48,15 @@ class BiometricTimesScreen : Screen() {
fun showTimePicker(startTime: Duration? = null) { fun showTimePicker(startTime: Duration? = null) {
val activity = context as? MainActivity ?: return val activity = context as? MainActivity ?: return
val picker = MaterialTimePicker.Builder() val picker = MaterialTimePicker.Builder()
.setTitleText(if (startTime == null) SYMR.strings.biometric_lock_start_time.getString(context) else SYMR.strings.biometric_lock_end_time.getString(context)) .setTitleText(
if (startTime ==
null
) {
SYMR.strings.biometric_lock_start_time.getString(context)
} else {
SYMR.strings.biometric_lock_end_time.getString(context)
},
)
.setInputMode(MaterialTimePicker.INPUT_MODE_CLOCK) .setInputMode(MaterialTimePicker.INPUT_MODE_CLOCK)
.build() .build()
picker.addOnPositiveButtonClickListener { picker.addOnPositiveButtonClickListener {

View File

@ -70,8 +70,8 @@ object HomeScreen : Screen() {
private val openTabEvent = Channel<Tab>() private val openTabEvent = Channel<Tab>()
private val showBottomNavEvent = Channel<Boolean>() private val showBottomNavEvent = Channel<Boolean>()
private const val TabFadeDuration = 200 private const val TAB_FADE_DURATION = 200
private const val TabNavigatorKey = "HomeTabs" private const val TAB_NAVIGATOR_KEY = "HomeTabs"
private val tabs = listOf( private val tabs = listOf(
LibraryTab, LibraryTab,
@ -94,7 +94,7 @@ object HomeScreen : Screen() {
TabNavigator( TabNavigator(
tab = LibraryTab, tab = LibraryTab,
key = TabNavigatorKey, key = TAB_NAVIGATOR_KEY,
) { tabNavigator -> ) { tabNavigator ->
// Provide usable navigator to content screen // Provide usable navigator to content screen
CompositionLocalProvider(LocalNavigator provides navigator) { CompositionLocalProvider(LocalNavigator provides navigator) {
@ -144,8 +144,11 @@ object HomeScreen : Screen() {
AnimatedContent( AnimatedContent(
targetState = tabNavigator.current, targetState = tabNavigator.current,
transitionSpec = { transitionSpec = {
materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) togetherWith materialFadeThroughIn(
materialFadeThroughOut(durationMillis = TabFadeDuration) initialScale = 1f,
durationMillis = TAB_FADE_DURATION,
) togetherWith
materialFadeThroughOut(durationMillis = TAB_FADE_DURATION)
}, },
label = "tabContent", label = "tabContent",
) { ) {

View File

@ -184,7 +184,11 @@ class LibraryScreenModel(
.applyGrouping(groupType) .applyGrouping(groupType)
// SY <-- // SY <--
.applyFilters(tracks, trackingFiler) .applyFilters(tracks, trackingFiler)
.applySort(tracks, trackingFiler.keys,/* SY --> */sort.takeIf { groupType != LibraryGroup.BY_DEFAULT } /* SY <-- */) .applySort(
tracks, trackingFiler.keys, /* SY --> */sort.takeIf {
groupType != LibraryGroup.BY_DEFAULT
}, /* SY <-- */
)
.mapValues { (_, value) -> .mapValues { (_, value) ->
if (searchQuery != null) { if (searchQuery != null) {
// Filter query // Filter query
@ -278,7 +282,6 @@ class LibraryScreenModel(
/** /**
* Applies library filters to the given map of manga. * Applies library filters to the given map of manga.
*/ */
@Suppress("LongMethod", "CyclomaticComplexMethod")
private suspend fun LibraryMap.applyFilters( private suspend fun LibraryMap.applyFilters(
trackMap: Map<Long, List<Track>>, trackMap: Map<Long, List<Track>>,
trackingFiler: Map<Long, TriState>, trackingFiler: Map<Long, TriState>,
@ -373,7 +376,6 @@ class LibraryScreenModel(
/** /**
* Applies library sorting to the given map of manga. * Applies library sorting to the given map of manga.
*/ */
@Suppress("LongMethod", "CyclomaticComplexMethod")
private fun LibraryMap.applySort( private fun LibraryMap.applySort(
// Map<MangaId, List<Track>> // Map<MangaId, List<Track>>
trackMap: Map<Long, List<Track>>, trackMap: Map<Long, List<Track>>,
@ -387,7 +389,8 @@ class LibraryScreenModel(
.asSequence() .asSequence()
.mapNotNull { .mapNotNull {
val list = it.split("|") val list = it.split("|")
(list.getOrNull(0)?.toIntOrNull() ?: return@mapNotNull null) to (list.getOrNull(1) ?: return@mapNotNull null) (list.getOrNull(0)?.toIntOrNull() ?: return@mapNotNull null) to
(list.getOrNull(1) ?: return@mapNotNull null)
} }
.sortedBy { it.first } .sortedBy { it.first }
.map { it.second } .map { it.second }
@ -453,8 +456,12 @@ class LibraryScreenModel(
} }
// SY --> // SY -->
LibrarySort.Type.TagList -> { LibrarySort.Type.TagList -> {
val manga1IndexOfTag = listOfTags.indexOfFirst { i1.libraryManga.manga.genre?.contains(it) ?: false } val manga1IndexOfTag = listOfTags.indexOfFirst {
val manga2IndexOfTag = listOfTags.indexOfFirst { i2.libraryManga.manga.genre?.contains(it) ?: false } i1.libraryManga.manga.genre?.contains(it) ?: false
}
val manga2IndexOfTag = listOfTags.indexOfFirst {
i2.libraryManga.manga.genre?.contains(it) ?: false
}
manga1IndexOfTag.compareTo(manga2IndexOfTag) manga1IndexOfTag.compareTo(manga2IndexOfTag)
} }
// SY <-- // SY <--
@ -822,9 +829,12 @@ class LibraryScreenModel(
if (source != null) { if (source != null) {
if (source is MergedSource) { if (source is MergedSource) {
val mergedMangas = getMergedMangaById.await(manga.id) val mergedMangas = getMergedMangaById.await(manga.id)
val sources = mergedMangas.distinctBy { it.source }.map { sourceManager.getOrStub(it.source) } val sources = mergedMangas.distinctBy {
it.source
}.map { sourceManager.getOrStub(it.source) }
mergedMangas.forEach merge@{ mergedManga -> mergedMangas.forEach merge@{ mergedManga ->
val mergedSource = sources.firstOrNull { mergedManga.source == it.id } as? HttpSource ?: return@merge val mergedSource =
sources.firstOrNull { mergedManga.source == it.id } as? HttpSource ?: return@merge
downloadManager.deleteManga(mergedManga, mergedSource) downloadManager.deleteManga(mergedManga, mergedSource)
} }
} else { } else {
@ -903,7 +913,8 @@ class LibraryScreenModel(
} else { } else {
categoryName categoryName
} }
LibraryGroup.BY_TRACK_STATUS -> TrackStatus.entries LibraryGroup.BY_TRACK_STATUS ->
TrackStatus.entries
.find { it.int.toLong() == category?.id } .find { it.int.toLong() == category?.id }
.let { it ?: TrackStatus.OTHER } .let { it ?: TrackStatus.OTHER }
.let { context.stringResource(it.res) } .let { context.stringResource(it.res) }
@ -993,15 +1004,23 @@ class LibraryScreenModel(
(manga.description?.contains(query, true) == true) || (manga.description?.contains(query, true) == true) ||
(source?.name?.contains(query, true) == true) || (source?.name?.contains(query, true) == true) ||
(sourceIdString != null && sourceIdString == query) || (sourceIdString != null && sourceIdString == query) ||
(loggedInTrackServices.isNotEmpty() && tracks != null && filterTracks(query, tracks, context)) || (
loggedInTrackServices.isNotEmpty() &&
tracks != null &&
filterTracks(query, tracks, context)
) ||
(genre.fastAny { it.contains(query, true) }) || (genre.fastAny { it.contains(query, true) }) ||
(searchTags?.fastAny { it.name.contains(query, true) } == true) || (searchTags?.fastAny { it.name.contains(query, true) } == true) ||
(searchTitles?.fastAny { it.title.contains(query, true) } == true) (searchTitles?.fastAny { it.title.contains(query, true) } == true)
} }
is Namespace -> { is Namespace -> {
searchTags != null && searchTags.fastAny { searchTags != null &&
searchTags.fastAny {
val tag = queryComponent.tag val tag = queryComponent.tag
(it.namespace.equals(queryComponent.namespace, true) && tag?.run { it.name.contains(tag.asQuery(), true) } == true) || (
it.namespace.equals(queryComponent.namespace, true) &&
tag?.run { it.name.contains(tag.asQuery(), true) } == true
) ||
(tag == null && it.namespace.equals(queryComponent.namespace, true)) (tag == null && it.namespace.equals(queryComponent.namespace, true))
} }
} }
@ -1010,14 +1029,19 @@ class LibraryScreenModel(
true -> when (queryComponent) { true -> when (queryComponent) {
is Text -> { is Text -> {
val query = queryComponent.asQuery() val query = queryComponent.asQuery()
query.isBlank() || ( query.isBlank() ||
(
(!manga.title.contains(query, true)) && (!manga.title.contains(query, true)) &&
(manga.author?.contains(query, true) != true) && (manga.author?.contains(query, true) != true) &&
(manga.artist?.contains(query, true) != true) && (manga.artist?.contains(query, true) != true) &&
(manga.description?.contains(query, true) != true) && (manga.description?.contains(query, true) != true) &&
(source?.name?.contains(query, true) != true) && (source?.name?.contains(query, true) != true) &&
(sourceIdString != null && sourceIdString != query) && (sourceIdString != null && sourceIdString != query) &&
(loggedInTrackServices.isEmpty() || tracks == null || !filterTracks(query, tracks, context)) && (
loggedInTrackServices.isEmpty() ||
tracks == null ||
!filterTracks(query, tracks, context)
) &&
(!genre.fastAny { it.contains(query, true) }) && (!genre.fastAny { it.contains(query, true) }) &&
(searchTags?.fastAny { it.name.contains(query, true) } != true) && (searchTags?.fastAny { it.name.contains(query, true) } != true) &&
(searchTitles?.fastAny { it.title.contains(query, true) } != true) (searchTitles?.fastAny { it.title.contains(query, true) } != true)
@ -1025,15 +1049,19 @@ class LibraryScreenModel(
} }
is Namespace -> { is Namespace -> {
val searchedTag = queryComponent.tag?.asQuery() val searchedTag = queryComponent.tag?.asQuery()
searchTags == null || (queryComponent.namespace.isBlank() && searchedTag.isNullOrBlank()) || searchTags.fastAll { mangaTag -> searchTags == null ||
(queryComponent.namespace.isBlank() && searchedTag.isNullOrBlank()) ||
searchTags.fastAll { mangaTag ->
if (queryComponent.namespace.isBlank() && !searchedTag.isNullOrBlank()) { if (queryComponent.namespace.isBlank() && !searchedTag.isNullOrBlank()) {
!mangaTag.name.contains(searchedTag, true) !mangaTag.name.contains(searchedTag, true)
} else if (searchedTag.isNullOrBlank()) { } else if (searchedTag.isNullOrBlank()) {
mangaTag.namespace == null || !mangaTag.namespace.equals(queryComponent.namespace, true) mangaTag.namespace == null ||
!mangaTag.namespace.equals(queryComponent.namespace, true)
} else if (mangaTag.namespace.isNullOrBlank()) { } else if (mangaTag.namespace.isNullOrBlank()) {
true true
} else { } else {
!mangaTag.name.contains(searchedTag, true) || !mangaTag.namespace.equals(queryComponent.namespace, true) !mangaTag.name.contains(searchedTag, true) ||
!mangaTag.namespace.equals(queryComponent.namespace, true)
} }
} }
} }

View File

@ -34,7 +34,7 @@ class LibrarySettingsScreenModel(
.stateIn( .stateIn(
scope = screenModelScope, scope = screenModelScope,
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds), started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds),
initialValue = trackerManager.loggedInTrackers() initialValue = trackerManager.loggedInTrackers(),
) )
// SY --> // SY -->

View File

@ -189,14 +189,14 @@ private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDial
binding.mangaDescription.hint = binding.mangaDescription.hint =
context.stringResource( context.stringResource(
SYMR.strings.description_hint, SYMR.strings.description_hint,
manga.ogDescription?.takeIf { it.isNotBlank() }?.replace("\n", " ")?.chop(20) ?: "" manga.ogDescription?.takeIf { it.isNotBlank() }?.replace("\n", " ")?.chop(20) ?: "",
) )
binding.thumbnailUrl.hint = binding.thumbnailUrl.hint =
context.stringResource( context.stringResource(
SYMR.strings.thumbnail_url_hint, SYMR.strings.thumbnail_url_hint,
manga.ogThumbnailUrl?.let { manga.ogThumbnailUrl?.let {
it.chop(40) + if (it.length > 46) "." + it.substringAfterLast(".").chop(6) else "" it.chop(40) + if (it.length > 46) "." + it.substringAfterLast(".").chop(6) else ""
} ?: "" } ?: "",
) )
} }
binding.mangaGenresTags.clearFocus() binding.mangaGenresTags.clearFocus()

View File

@ -209,10 +209,14 @@ class MangaScreen(
onMigrateClicked = { migrateManga(navigator, screenModel.manga!!) }.takeIf { successState.manga.favorite }, onMigrateClicked = { migrateManga(navigator, screenModel.manga!!) }.takeIf { successState.manga.favorite },
onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) }, onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) },
onEditInfoClicked = screenModel::showEditMangaInfoDialog, onEditInfoClicked = screenModel::showEditMangaInfoDialog,
onRecommendClicked = { openRecommends(context, navigator, screenModel.source?.getMainSource(), successState.manga) }, onRecommendClicked = {
openRecommends(context, navigator, screenModel.source?.getMainSource(), successState.manga)
},
onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog, onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog,
onMergeClicked = { openSmartSearch(navigator, successState.manga) }, onMergeClicked = { openSmartSearch(navigator, successState.manga) },
onMergeWithAnotherClicked = { mergeWithAnother(navigator, context, successState.manga, screenModel::smartSearchMerge) }, onMergeWithAnotherClicked = {
mergeWithAnother(navigator, context, successState.manga, screenModel::smartSearchMerge)
},
onOpenPagePreview = { onOpenPagePreview = {
openPagePreview(context, successState.chapters.minByOrNull { it.chapter.sourceOrder }?.chapter, it) openPagePreview(context, successState.chapters.minByOrNull { it.chapter.sourceOrder }?.chapter, it)
}, },

View File

@ -35,14 +35,12 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.track.EnhancedTracker import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.data.track.mdlist.MdList
import eu.kanade.tachiyomi.network.HttpException import eu.kanade.tachiyomi.network.HttpException
import eu.kanade.tachiyomi.source.PagePreviewSource import eu.kanade.tachiyomi.source.PagePreviewSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.getNameForMangaInfo import eu.kanade.tachiyomi.source.getNameForMangaInfo
import eu.kanade.tachiyomi.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.chapter.getNextUnread import eu.kanade.tachiyomi.util.chapter.getNextUnread
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
@ -208,7 +206,8 @@ class MangaScreenModel(
private val skipFiltered by readerPreferences.skipFiltered().asState(screenModelScope) private val skipFiltered by readerPreferences.skipFiltered().asState(screenModelScope)
val isUpdateIntervalEnabled = LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateMangaRestrictions().get() val isUpdateIntervalEnabled =
LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateMangaRestrictions().get()
private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list
private val selectedChapterIds: HashSet<Long> = HashSet() private val selectedChapterIds: HashSet<Long> = HashSet()
@ -262,7 +261,10 @@ class MangaScreenModel(
} }
} }
.onEach { (manga, chapters) -> .onEach { (manga, chapters) ->
if (chapters.isNotEmpty() && manga.isEhBasedManga() && DebugToggles.ENABLE_EXH_ROOT_REDIRECT.enabled) { if (chapters.isNotEmpty() &&
manga.isEhBasedManga() &&
DebugToggles.ENABLE_EXH_ROOT_REDIRECT.enabled
) {
// Check for gallery in library and accept manga with lowest id // Check for gallery in library and accept manga with lowest id
// Find chapters sharing same root // Find chapters sharing same root
launchIO { launchIO {
@ -350,7 +352,7 @@ class MangaScreenModel(
} else { } else {
flowOf(emptySet()) flowOf(emptySet())
} }
} },
) { mangaScanlators, mergeScanlators -> ) { mangaScanlators, mergeScanlators ->
mangaScanlators + mergeScanlators mangaScanlators + mergeScanlators
} // SY <-- } // SY <--
@ -366,7 +368,15 @@ class MangaScreenModel(
screenModelScope.launchIO { screenModelScope.launchIO {
val manga = getMangaAndChapters.awaitManga(mangaId) val manga = getMangaAndChapters.awaitManga(mangaId)
// SY --> // SY -->
val chapters = (if (manga.source == MERGED_SOURCE_ID) getMergedChaptersByMangaId.await(mangaId, applyScanlatorFilter = true) else getMangaAndChapters.awaitChapters(mangaId, applyScanlatorFilter = true)) val chapters = (
if (manga.source ==
MERGED_SOURCE_ID
) {
getMergedChaptersByMangaId.await(mangaId, applyScanlatorFilter = true)
} else {
getMangaAndChapters.awaitChapters(mangaId, applyScanlatorFilter = true)
}
)
.toChapterListItems(manga, null) .toChapterListItems(manga, null)
val mergedData = getMergedReferencesById.await(mangaId).takeIf { it.isNotEmpty() }?.let { references -> val mergedData = getMergedReferencesById.await(mangaId).takeIf { it.isNotEmpty() }?.let { references ->
MergedMangaData( MergedMangaData(
@ -416,7 +426,8 @@ class MangaScreenModel(
} else { } else {
PagePreviewState.Unused PagePreviewState.Unused
}, },
alwaysShowReadingProgress = readerPreferences.preserveReadingPosition().get() && manga.isEhBasedManga(), alwaysShowReadingProgress =
readerPreferences.preserveReadingPosition().get() && manga.isEhBasedManga(),
previewsRowCount = uiPreferences.previewsRowCount().get(), previewsRowCount = uiPreferences.previewsRowCount().get(),
// SY <-- // SY <--
) )
@ -920,7 +931,12 @@ class MangaScreenModel(
screenModelScope.launchIO { screenModelScope.launchIO {
downloadManager.statusFlow() downloadManager.statusFlow()
.filter { .filter {
/* SY --> */ if (isMergedSource) it.manga.id in mergedIds else /* SY <-- */ it.manga.id == successState?.manga?.id /* SY --> */ if (isMergedSource) {
it.manga.id in mergedIds
} else {
/* SY <-- */ it.manga.id ==
successState?.manga?.id
}
} }
.catch { error -> logcat(LogPriority.ERROR, error) } .catch { error -> logcat(LogPriority.ERROR, error) }
.collect { .collect {
@ -933,7 +949,12 @@ class MangaScreenModel(
screenModelScope.launchIO { screenModelScope.launchIO {
downloadManager.progressFlow() downloadManager.progressFlow()
.filter { .filter {
/* SY --> */ if (isMergedSource) it.manga.id in mergedIds else /* SY <-- */ it.manga.id == successState?.manga?.id /* SY --> */ if (isMergedSource) {
it.manga.id in mergedIds
} else {
/* SY <-- */ it.manga.id ==
successState?.manga?.id
}
} }
.catch { error -> logcat(LogPriority.ERROR, error) } .catch { error -> logcat(LogPriority.ERROR, error) }
.collect { .collect {
@ -974,6 +995,7 @@ class MangaScreenModel(
} else { } else {
downloadManager.getQueuedDownloadOrNull(chapter.id) downloadManager.getQueuedDownloadOrNull(chapter.id)
} }
// SY --> // SY -->
@Suppress("NAME_SHADOWING") @Suppress("NAME_SHADOWING")
val manga = mergedData?.manga?.get(chapter.mangaId) ?: manga val manga = mergedData?.manga?.get(chapter.mangaId) ?: manga
@ -1292,7 +1314,12 @@ class MangaScreenModel(
screenModelScope.launchNonCancellable { screenModelScope.launchNonCancellable {
val manga = successState?.manga ?: return@launchNonCancellable val manga = successState?.manga ?: return@launchNonCancellable
val categories = getCategories.await(manga.id).map { it.id } val categories = getCategories.await(manga.id).map { it.id }
if (chapters.isEmpty() || !manga.shouldDownloadNewChapters(categories, downloadPreferences) || manga.isEhBasedManga()) return@launchNonCancellable if (chapters.isEmpty() ||
!manga.shouldDownloadNewChapters(categories, downloadPreferences) ||
manga.isEhBasedManga()
) {
return@launchNonCancellable
}
downloadChapters(chapters) downloadChapters(chapters)
} }
} }
@ -1493,7 +1520,8 @@ class MangaScreenModel(
getTracks.subscribe(manga.id) getTracks.subscribe(manga.id)
// SY --> // SY -->
.map { trackItems -> .map { trackItems ->
if (manga.source in mangaDexSourceIds || state.mergedData?.manga?.values.orEmpty().any { if (manga.source in mangaDexSourceIds ||
state.mergedData?.manga?.values.orEmpty().any {
it.source in mangaDexSourceIds it.source in mangaDexSourceIds
} }
) { ) {
@ -1524,7 +1552,6 @@ class MangaScreenModel(
trackingCount to supportedTrackers.isNotEmpty() trackingCount to supportedTrackers.isNotEmpty()
// SY <-- // SY <--
} }
.distinctUntilChanged() .distinctUntilChanged()
.collectLatest { (trackingCount, hasLoggedInTrackers) -> .collectLatest { (trackingCount, hasLoggedInTrackers) ->
updateSuccessState { updateSuccessState {
@ -1559,6 +1586,7 @@ class MangaScreenModel(
) : Dialog ) : Dialog
data class DeleteChapters(val chapters: List<Chapter>) : Dialog data class DeleteChapters(val chapters: List<Chapter>) : Dialog
data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog
/* SY --> /* SY -->
data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog
SY <-- */ SY <-- */

View File

@ -59,7 +59,8 @@ class EditMergedSettingsState(
}.map { reference -> mergedManga.firstOrNull { it.id == reference.mangaId } to reference } }.map { reference -> mergedManga.firstOrNull { it.id == reference.mangaId } to reference }
mergeReference = mergedReferences.firstOrNull { it.mangaSourceId == MERGED_SOURCE_ID } mergeReference = mergedReferences.firstOrNull { it.mangaSourceId == MERGED_SOURCE_ID }
val isPriorityOrder = mergeReference?.let { it.chapterSortMode == MergedMangaReference.CHAPTER_SORT_PRIORITY } ?: false val isPriorityOrder =
mergeReference?.let { it.chapterSortMode == MergedMangaReference.CHAPTER_SORT_PRIORITY } ?: false
mergedMangaAdapter = EditMergedMangaAdapter(this, isPriorityOrder) mergedMangaAdapter = EditMergedMangaAdapter(this, isPriorityOrder)
mergedMangaHeaderAdapter = EditMergedSettingsHeaderAdapter(this, mergedMangaAdapter!!) mergedMangaHeaderAdapter = EditMergedSettingsHeaderAdapter(this, mergedMangaAdapter!!)

View File

@ -161,7 +161,8 @@ class EditMergedSettingsHeaderAdapter(private val state: EditMergedSettingsState
} }
} }
fun canMove() = state.mergeReference?.let { it.chapterSortMode == MergedMangaReference.CHAPTER_SORT_PRIORITY } ?: false fun canMove() =
state.mergeReference?.let { it.chapterSortMode == MergedMangaReference.CHAPTER_SORT_PRIORITY } ?: false
interface SortingListener { interface SortingListener {
fun onSetPrioritySort(isPriorityOrder: Boolean) fun onSetPrioritySort(isPriorityOrder: Boolean)

View File

@ -108,8 +108,8 @@ private class MoreScreenModel(
val showNavHistory by uiPreferences.showNavHistory().asState(screenModelScope) val showNavHistory by uiPreferences.showNavHistory().asState(screenModelScope)
// SY <-- // SY <--
private var _state: MutableStateFlow<DownloadQueueState> = MutableStateFlow(DownloadQueueState.Stopped) private var _downloadQueueState: MutableStateFlow<DownloadQueueState> = MutableStateFlow(DownloadQueueState.Stopped)
val downloadQueueState: StateFlow<DownloadQueueState> = _state.asStateFlow() val downloadQueueState: StateFlow<DownloadQueueState> = _downloadQueueState.asStateFlow()
init { init {
// Handle running/paused status change and queue progress updating // Handle running/paused status change and queue progress updating
@ -120,7 +120,7 @@ private class MoreScreenModel(
) { isRunning, downloadQueue -> Pair(isRunning, downloadQueue.size) } ) { isRunning, downloadQueue -> Pair(isRunning, downloadQueue.size) }
.collectLatest { (isDownloading, downloadQueueSize) -> .collectLatest { (isDownloading, downloadQueueSize) ->
val pendingDownloadExists = downloadQueueSize != 0 val pendingDownloadExists = downloadQueueSize != 0
_state.value = when { _downloadQueueState.value = when {
!pendingDownloadExists -> DownloadQueueState.Stopped !pendingDownloadExists -> DownloadQueueState.Stopped
!isDownloading -> DownloadQueueState.Paused(downloadQueueSize) !isDownloading -> DownloadQueueState.Paused(downloadQueueSize)
else -> DownloadQueueState.Downloading(downloadQueueSize) else -> DownloadQueueState.Downloading(downloadQueueSize)

View File

@ -8,7 +8,6 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.graphics.ColorMatrix import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter import android.graphics.ColorMatrixColorFilter
@ -438,10 +437,12 @@ class ReaderActivity : BaseActivity() {
val landscapeVerticalSeekbar by readerPreferences.landscapeVerticalSeekbar().collectAsState() val landscapeVerticalSeekbar by readerPreferences.landscapeVerticalSeekbar().collectAsState()
val leftHandedVerticalSeekbar by readerPreferences.leftVerticalSeekbar().collectAsState() val leftHandedVerticalSeekbar by readerPreferences.leftVerticalSeekbar().collectAsState()
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
val verticalSeekbarLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && landscapeVerticalSeekbar val verticalSeekbarLandscape =
configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && landscapeVerticalSeekbar
val verticalSeekbarHorizontal = configuration.orientation == Configuration.ORIENTATION_PORTRAIT val verticalSeekbarHorizontal = configuration.orientation == Configuration.ORIENTATION_PORTRAIT
val viewerIsVertical = (state.viewer is WebtoonViewer || state.viewer is VerticalPagerViewer) val viewerIsVertical = (state.viewer is WebtoonViewer || state.viewer is VerticalPagerViewer)
val showVerticalSeekbar = !forceHorizontalSeekbar && (verticalSeekbarLandscape || verticalSeekbarHorizontal) && viewerIsVertical val showVerticalSeekbar =
!forceHorizontalSeekbar && (verticalSeekbarLandscape || verticalSeekbarHorizontal) && viewerIsVertical
val navBarType = when { val navBarType = when {
!showVerticalSeekbar -> NavBarType.Bottom !showVerticalSeekbar -> NavBarType.Bottom
leftHandedVerticalSeekbar -> NavBarType.VerticalLeft leftHandedVerticalSeekbar -> NavBarType.VerticalLeft
@ -828,7 +829,8 @@ class ReaderActivity : BaseActivity() {
} else { } else {
if (readerPreferences.fullscreen().get()) { if (readerPreferences.fullscreen().get()) {
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
} }
} }
} }
@ -1031,7 +1033,13 @@ class ReaderActivity : BaseActivity() {
// SY --> // SY -->
val currentPageText = if (hasExtraPage) { val currentPageText = if (hasExtraPage) {
val invertDoublePage = (viewModel.state.value.viewer as? PagerViewer)?.config?.invertDoublePages ?: false val invertDoublePage = (viewModel.state.value.viewer as? PagerViewer)?.config?.invertDoublePages ?: false
if ((resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) xor invertDoublePage) "${page.number}-${page.number + 1}" else "${page.number + 1}-${page.number}" if ((resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) xor
invertDoublePage
) {
"${page.number}-${page.number + 1}"
} else {
"${page.number + 1}-${page.number}"
}
} else { } else {
"${page.number}" "${page.number}"
} }
@ -1093,7 +1101,16 @@ class ReaderActivity : BaseActivity() {
// SY --> // SY -->
val text = if (secondPage != null) { val text = if (secondPage != null) {
stringResource(SYMR.strings.share_pages_info, manga.title, chapter.name, if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) "${page.number}-${page.number + 1}" else "${page.number + 1}-${page.number}") stringResource(
SYMR.strings.share_pages_info, manga.title, chapter.name,
if (resources.configuration.layoutDirection ==
View.LAYOUT_DIRECTION_LTR
) {
"${page.number}-${page.number + 1}"
} else {
"${page.number + 1}-${page.number}"
},
)
} else { } else {
stringResource(MR.strings.share_page_info, manga.title, chapter.name, page.number) stringResource(MR.strings.share_page_info, manga.title, chapter.name, page.number)
} }
@ -1257,9 +1274,12 @@ class ReaderActivity : BaseActivity() {
.onEach { .onEach {
if (viewModel.state.value.viewer !is PagerViewer) return@onEach if (viewModel.state.value.viewer !is PagerViewer) return@onEach
reloadChapters( reloadChapters(
!it && when (readerPreferences.pageLayout().get()) { !it &&
when (readerPreferences.pageLayout().get()) {
PagerConfig.PageLayout.DOUBLE_PAGES -> true PagerConfig.PageLayout.DOUBLE_PAGES -> true
PagerConfig.PageLayout.AUTOMATIC -> resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE PagerConfig.PageLayout.AUTOMATIC ->
resources.configuration.orientation ==
Configuration.ORIENTATION_LANDSCAPE
else -> false else -> false
}, },
true, true,

View File

@ -134,7 +134,7 @@ class ReaderViewModel @JvmOverloads constructor(
private val getMergedMangaById: GetMergedMangaById = Injekt.get(), private val getMergedMangaById: GetMergedMangaById = Injekt.get(),
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(), private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
private val getMergedChaptersByMangaId: GetMergedChaptersByMangaId = Injekt.get(), private val getMergedChaptersByMangaId: GetMergedChaptersByMangaId = Injekt.get(),
private val setReadStatus: SetReadStatus = Injekt.get() private val setReadStatus: SetReadStatus = Injekt.get(),
// SY <-- // SY <--
) : ViewModel() { ) : ViewModel() {
@ -189,7 +189,8 @@ class ReaderViewModel @JvmOverloads constructor(
// SY --> // SY -->
val (chapters, mangaMap) = runBlocking { val (chapters, mangaMap) = runBlocking {
if (manga.source == MERGED_SOURCE_ID) { if (manga.source == MERGED_SOURCE_ID) {
getMergedChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) to getMergedMangaById.await(manga.id) getMergedChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) to
getMergedMangaById.await(manga.id)
.associateBy { it.id } .associateBy { it.id }
} else { } else {
getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) to null getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) to null

View File

@ -62,9 +62,11 @@ class ChapterLoader(
// If the chapter is partially read, set the starting page to the last the user read // If the chapter is partially read, set the starting page to the last the user read
// otherwise use the requested page. // otherwise use the requested page.
if (!chapter.chapter.read /* --> EH */ || readerPrefs if (!chapter.chapter.read /* --> EH */ ||
readerPrefs
.preserveReadingPosition() .preserveReadingPosition()
.get() || page != null // <-- EH .get() ||
page != null // <-- EH
) { ) {
chapter.requestedPage = /* SY --> */ page ?: /* SY <-- */ chapter.chapter.last_page_read chapter.requestedPage = /* SY --> */ page ?: /* SY <-- */ chapter.chapter.last_page_read
} }

View File

@ -191,7 +191,7 @@ class ReaderPreferences(
enum class FlashColor { enum class FlashColor {
BLACK, BLACK,
WHITE, WHITE,
WHITE_BLACK WHITE_BLACK,
} }
enum class TappingInvertMode( enum class TappingInvertMode(
@ -285,7 +285,7 @@ class ReaderPreferences(
val archiveModeTypes = listOf( val archiveModeTypes = listOf(
SYMR.strings.archive_mode_load_from_file, SYMR.strings.archive_mode_load_from_file,
SYMR.strings.archive_mode_load_into_memory, SYMR.strings.archive_mode_load_into_memory,
SYMR.strings.archive_mode_cache_to_disk SYMR.strings.archive_mode_cache_to_disk,
) )
// SY <-- // SY <--
} }

View File

@ -116,7 +116,12 @@ open class ReaderPageImageView @JvmOverloads constructor(
} }
private fun SubsamplingScaleImageView.landscapeZoom(forward: Boolean) { private fun SubsamplingScaleImageView.landscapeZoom(forward: Boolean) {
if (config != null && config!!.landscapeZoom && config!!.minimumScaleType == SCALE_TYPE_CENTER_INSIDE && sWidth > sHeight && scale == minScale) { if (config != null &&
config!!.landscapeZoom &&
config!!.minimumScaleType == SCALE_TYPE_CENTER_INSIDE &&
sWidth > sHeight &&
scale == minScale
) {
handler?.postDelayed(500) { handler?.postDelayed(500) {
val point = when (config!!.zoomStartPosition) { val point = when (config!!.zoomStartPosition) {
ZoomStartPosition.LEFT -> if (forward) PointF(0F, 0F) else PointF(sWidth.toFloat(), 0F) ZoomStartPosition.LEFT -> if (forward) PointF(0F, 0F) else PointF(sWidth.toFloat(), 0F)
@ -398,7 +403,9 @@ open class ReaderPageImageView @JvmOverloads constructor(
) )
enum class ZoomStartPosition { enum class ZoomStartPosition {
LEFT, CENTER, RIGHT LEFT,
CENTER,
RIGHT,
} }
} }

View File

@ -32,7 +32,8 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
Data( Data(
transition = transition, transition = transition,
currChapterDownloaded = transition.from.pageLoader?.isLocal == true, currChapterDownloaded = transition.from.pageLoader?.isLocal == true,
goingToChapterDownloaded = manga.isLocal() || transition.to?.chapter?.let { goingToChapter -> goingToChapterDownloaded = manga.isLocal() ||
transition.to?.chapter?.let { goingToChapter ->
downloadManager.isChapterDownloaded( downloadManager.isChapterDownloaded(
chapterName = goingToChapter.name, chapterName = goingToChapter.name,
chapterScanlator = goingToChapter.scanlator, chapterScanlator = goingToChapter.scanlator,

View File

@ -57,7 +57,8 @@ class PagerConfig(
var shiftDoublePage = false var shiftDoublePage = false
var doublePages = readerPreferences.pageLayout().get() == PageLayout.DOUBLE_PAGES && !readerPreferences.dualPageSplitPaged().get() var doublePages =
readerPreferences.pageLayout().get() == PageLayout.DOUBLE_PAGES && !readerPreferences.dualPageSplitPaged().get()
set(value) { set(value) {
field = value field = value
if (!value) { if (!value) {

View File

@ -330,7 +330,9 @@ class PagerPageHolder(
} }
private fun calculateCenterMargin(height: Int, height2: Int): Int { private fun calculateCenterMargin(height: Int, height2: Int): Int {
return if (viewer.config.centerMarginType and PagerConfig.CenterMarginType.DOUBLE_PAGE_CENTER_MARGIN > 0 && !viewer.config.imageCropBorders) { return if (viewer.config.centerMarginType and PagerConfig.CenterMarginType.DOUBLE_PAGE_CENTER_MARGIN > 0 &&
!viewer.config.imageCropBorders
) {
96 / (this.height.coerceAtLeast(1) / max(height, height2).coerceAtLeast(1)).coerceAtLeast(1) 96 / (this.height.coerceAtLeast(1) / max(height, height2).coerceAtLeast(1)).coerceAtLeast(1)
} else { } else {
0 0
@ -373,8 +375,10 @@ class PagerPageHolder(
} }
} }
val sideMargin = if ((viewer.config.centerMarginType and PagerConfig.CenterMarginType.DOUBLE_PAGE_CENTER_MARGIN) > 0 && val sideMargin = if ((viewer.config.centerMarginType and PagerConfig.CenterMarginType.DOUBLE_PAGE_CENTER_MARGIN) >
viewer.config.doublePages && !viewer.config.imageCropBorders 0 &&
viewer.config.doublePages &&
!viewer.config.imageCropBorders
) { ) {
48 48
} else { } else {

View File

@ -291,7 +291,9 @@ abstract class PagerViewer(val activity: ReaderActivity) : Viewer {
* Sets the active [chapters] on this pager. * Sets the active [chapters] on this pager.
*/ */
private fun setChaptersInternal(chapters: ViewerChapters) { private fun setChaptersInternal(chapters: ViewerChapters) {
val forceTransition = config.alwaysShowChapterTransition || adapter.joinedItems.getOrNull(pager.currentItem)?.first is ChapterTransition val forceTransition =
config.alwaysShowChapterTransition ||
adapter.joinedItems.getOrNull(pager.currentItem)?.first is ChapterTransition
adapter.setChapters(chapters, forceTransition) adapter.setChapters(chapters, forceTransition)
// Layout the pager once a chapter is being set // Layout the pager once a chapter is being set

View File

@ -109,7 +109,9 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
// Add next chapter transition and pages. // Add next chapter transition and pages.
nextTransition = ChapterTransition.Next(chapters.currChapter, chapters.nextChapter) nextTransition = ChapterTransition.Next(chapters.currChapter, chapters.nextChapter)
.also { .also {
if (nextHasMissingChapters || forceTransition || if (
nextHasMissingChapters ||
forceTransition ||
chapters.nextChapter?.state !is ReaderChapter.State.Loaded chapters.nextChapter?.state !is ReaderChapter.State.Loaded
) { ) {
newItems.add(it) newItems.add(it)
@ -248,7 +250,9 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
subItems.forEach { readerItem -> subItems.forEach { readerItem ->
when (readerItem) { when (readerItem) {
is ReaderPage -> { is ReaderPage -> {
if (pagedItems.last().isNotEmpty() && pagedItems.last().last()?.chapter?.chapter?.id != readerItem.chapter.chapter.id) { if (pagedItems.last().isNotEmpty() &&
pagedItems.last().last()?.chapter?.chapter?.id != readerItem.chapter.chapter.id
) {
pagedItems.add(mutableListOf()) pagedItems.add(mutableListOf())
} }
pagedItems.last().add(readerItem) pagedItems.last().add(readerItem)
@ -298,7 +302,8 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
// Add a 'blank' page after each full page. It will be used when chunked to solo a page // Add a 'blank' page after each full page. It will be used when chunked to solo a page
items.add(itemIndex + 1, null) items.add(itemIndex + 1, null)
if ( if (
currentItem.fullPage && itemIndex > 0 && currentItem.fullPage &&
itemIndex > 0 &&
items[itemIndex - 1] != null && items[itemIndex - 1] != null &&
(itemIndex - 1) % 2 == 0 (itemIndex - 1) % 2 == 0
) { ) {
@ -340,7 +345,8 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
// We will however shift to the first page of the new chapter if the last page we were are // We will however shift to the first page of the new chapter if the last page we were are
// on is not in the new chapter that has loaded // on is not in the new chapter that has loaded
val newPage = when { val newPage = when {
oldCurrent?.first is ReaderPage && (oldCurrent.first as ReaderPage).chapter != currentChapter && oldCurrent?.first is ReaderPage &&
(oldCurrent.first as ReaderPage).chapter != currentChapter &&
(oldCurrent.second as? ChapterTransition)?.from != currentChapter -> (oldCurrent.second as? ChapterTransition)?.from != currentChapter ->
subItems.find { it is ReaderPage && it.chapter == currentChapter } subItems.find { it is ReaderPage && it.chapter == currentChapter }
useSecondPage -> oldCurrent?.second ?: oldCurrent?.first useSecondPage -> oldCurrent?.second ?: oldCurrent?.first
@ -349,7 +355,10 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
val index = when (newPage) { val index = when (newPage) {
is ChapterTransition -> { is ChapterTransition -> {
val filteredPages = joinedItems.filter { it.first is ReaderPage && (it.first as ReaderPage).chapter == newPage.to } val filteredPages = joinedItems.filter {
it.first is ReaderPage &&
(it.first as ReaderPage).chapter == newPage.to
}
val page = if (newPage is ChapterTransition.Next) { val page = if (newPage is ChapterTransition.Next) {
filteredPages.minByOrNull { (it.first as ReaderPage).index }?.first filteredPages.minByOrNull { (it.first as ReaderPage).index }?.first
} else { } else {

View File

@ -89,7 +89,7 @@ class WebtoonConfig(
readerPreferences.webtoonDisableZoomOut() readerPreferences.webtoonDisableZoomOut()
.register( .register(
{ zoomOutDisabled = it }, { zoomOutDisabled = it },
{ zoomPropertyChangedListener?.invoke(it) } { zoomPropertyChangedListener?.invoke(it) },
) )
readerPreferences.webtoonDoubleTapZoomEnabled() readerPreferences.webtoonDoubleTapZoomEnabled()

View File

@ -199,7 +199,9 @@ class WebtoonPageHolder(
ReaderPageImageView.Config( ReaderPageImageView.Config(
zoomDuration = viewer.config.doubleTapAnimDuration, zoomDuration = viewer.config.doubleTapAnimDuration,
minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH, minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH,
cropBorders = (viewer.config.imageCropBorders && viewer.isContinuous) || (viewer.config.continuousCropBorders && !viewer.isContinuous), cropBorders =
(viewer.config.imageCropBorders && viewer.isContinuous) ||
(viewer.config.continuousCropBorders && !viewer.isContinuous),
), ),
) )
removeErrorLayout() removeErrorLayout()

View File

@ -182,7 +182,11 @@ class WebtoonRecyclerView @JvmOverloads constructor(
setScaleRate(currentScale) setScaleRate(currentScale)
layoutParams.height = if (currentScale < 1) { (originalHeight / currentScale).toInt() } else { originalHeight } layoutParams.height = if (currentScale < 1) {
(originalHeight / currentScale).toInt()
} else {
originalHeight
}
halfHeight = layoutParams.height / 2 halfHeight = layoutParams.height / 2
if (currentScale != DEFAULT_RATE) { if (currentScale != DEFAULT_RATE) {

View File

@ -87,7 +87,7 @@ class WebtoonViewer(
.threshold .threshold
init { init {
recycler.setItemViewCacheSize(RecyclerViewCacheSize) recycler.setItemViewCacheSize(RECYCLER_VIEW_CACHE_SIZE)
recycler.isVisible = false // Don't let the recycler layout yet recycler.isVisible = false // Don't let the recycler layout yet
recycler.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT) recycler.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
recycler.isFocusable = false recycler.isFocusable = false
@ -404,4 +404,4 @@ class WebtoonViewer(
} }
// Double the cache size to reduce rebinds/recycles incurred by the extra layout space on scroll direction changes // Double the cache size to reduce rebinds/recycles incurred by the extra layout space on scroll direction changes
private val RecyclerViewCacheSize = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 4 else 2 private val RECYCLER_VIEW_CACHE_SIZE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 4 else 2

Some files were not shown because too many files have changed in this diff Show More