diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d02884591f..5bbf3fa2f6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,7 +23,7 @@ android { defaultConfig { applicationId = "xyz.jmir.tachiyomi.mi" - versionCode = 101 + versionCode = 102 versionName = "0.14.6" buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 123a76360f..343be8d652 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,5 +1,8 @@ -dontobfuscate +-keep,allowoptimization class eu.kanade.** +-keep,allowoptimization class tachiyomi.** + # Keep common dependencies used in extensions -keep,allowoptimization class androidx.preference.** { public protected *; } -keep,allowoptimization class android.content.** { *; } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt index ae1f6f5a81..f6f62eca9d 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt @@ -227,7 +227,7 @@ object AboutScreen : Screen() { } } - private fun getFormattedBuildTime(): String { + internal fun getFormattedBuildTime(): String { return try { val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US) inputDf.timeZone = TimeZone.getTimeZone("UTC") diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt index 0cd65d96f3..5128cf5e24 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt @@ -1,29 +1,132 @@ package eu.kanade.presentation.more.settings.screen.debug -import androidx.annotation.StringRes +import android.os.Build +import android.webkit.WebView import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.ui.platform.LocalContext +import androidx.profileinstaller.ProfileVerifier import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.more.settings.Preference -import eu.kanade.presentation.more.settings.screen.SearchableSettings +import eu.kanade.presentation.more.settings.PreferenceScaffold +import eu.kanade.presentation.more.settings.screen.AboutScreen +import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.system.DeviceUtil +import kotlinx.coroutines.guava.await -object DebugInfoScreen : SearchableSettings { - @ReadOnlyComposable +object DebugInfoScreen : Screen() { @Composable - @StringRes - override fun getTitleRes() = R.string.pref_debug_info + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + PreferenceScaffold( + titleRes = R.string.pref_debug_info, + onBackPressed = navigator::pop, + itemsProvider = { + listOf( + Preference.PreferenceItem.TextPreference( + title = WorkerInfoScreen.title, + onClick = { navigator.push(WorkerInfoScreen) }, + ), + getAppInfoGroup(), + getDeviceInfoGroup(), + ) + }, + ) + } @Composable - override fun getPreferences(): List { - val navigator = LocalNavigator.currentOrThrow + private fun getAppInfoGroup(): Preference.PreferenceGroup { + return Preference.PreferenceGroup( + title = "App info", + preferenceItems = listOf( + Preference.PreferenceItem.TextPreference( + title = "Version", + subtitle = AboutScreen.getVersionName(false), + ), + Preference.PreferenceItem.TextPreference( + title = "Build time", + subtitle = AboutScreen.getFormattedBuildTime(), + ), + getProfileVerifierPreference(), + Preference.PreferenceItem.TextPreference( + title = "WebView version", + subtitle = getWebViewVersion(), + ), + ), + ) + } + + @Composable + @ReadOnlyComposable + private fun getWebViewVersion(): String { + val webView = WebView.getCurrentWebViewPackage() ?: return "how did you get here?" + val pm = LocalContext.current.packageManager + val label = webView.applicationInfo.loadLabel(pm) + val version = webView.versionName + return "$label $version" + } + + @Composable + private fun getProfileVerifierPreference(): Preference.PreferenceItem.TextPreference { + val status by produceState(initialValue = "-") { + val result = ProfileVerifier.getCompilationStatusAsync().await().profileInstallResultCode + value = when (result) { + ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE -> "No profile installed" + ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled" + ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING -> "Compiled non-matching" + ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ, + ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE, + ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST, + -> "Error $result" + ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION -> "Not supported" + ProfileVerifier.CompilationStatus.RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION -> "Pending compilation" + else -> "Unknown code $result" + } + } + return Preference.PreferenceItem.TextPreference( + title = "Profile compilation status", + subtitle = status, + ) + } - return listOf( + private fun getDeviceInfoGroup(): Preference.PreferenceGroup { + val items = mutableListOf( Preference.PreferenceItem.TextPreference( - title = WorkerInfoScreen.title, - onClick = { navigator.push(WorkerInfoScreen) }, + title = "Model", + subtitle = "${Build.MANUFACTURER} ${Build.MODEL} (${Build.DEVICE})", ), ) + if (DeviceUtil.oneUiVersion != null) { + items += Preference.PreferenceItem.TextPreference( + title = "OneUI version", + subtitle = "${DeviceUtil.oneUiVersion}", + ) + } else if (DeviceUtil.miuiMajorVersion != null) { + items += Preference.PreferenceItem.TextPreference( + title = "MIUI version", + subtitle = "${DeviceUtil.miuiMajorVersion}", + ) + } + + val androidVersion = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + Build.VERSION.RELEASE_OR_CODENAME + } else { + Build.VERSION.RELEASE + } + items += Preference.PreferenceItem.TextPreference( + title = "Android version", + subtitle = "$androidVersion (${Build.DISPLAY})", + ) + + return Preference.PreferenceGroup( + title = "Device info", + preferenceItems = items, + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 8f8ecdee64..f612c661ab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.util.preference.minusAssign import eu.kanade.tachiyomi.util.preference.plusAssign import eu.kanade.tachiyomi.util.system.DeviceUtil +import eu.kanade.tachiyomi.util.system.isReleaseBuildType import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.workManager import tachiyomi.core.preference.PreferenceStore @@ -449,6 +450,13 @@ object Migrations { if (oldVersion < 100) { BackupCreateJob.setupTask(context) } + if (oldVersion < 102) { + // This was accidentally visible from the reader settings sheet, but should always + // be disabled in release builds. + if (isReleaseBuildType) { + readerPreferences.longStripSplitWebtoon().set(false) + } + } return true } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt index fa3f97ecad..2ef9324d35 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt @@ -138,14 +138,26 @@ class AnimeDownloadProvider( * @param episodeScanlator scanlator of the episode to query */ fun getEpisodeDirName(episodeName: String, episodeScanlator: String?): String { + val newEpisodeName = sanitizeEpisodeName(episodeName) return DiskUtil.buildValidFilename( when { - episodeScanlator.isNullOrBlank().not() -> "${episodeScanlator}_$episodeName" - else -> episodeName + episodeScanlator.isNullOrBlank().not() -> "${episodeScanlator}_$newEpisodeName" + else -> newEpisodeName }, ) } + /** + * Return the new name for the episode (in case it's empty or blank) + * + * @param episodeName the name of the episode + */ + private fun sanitizeEpisodeName(episodeName: String): String { + return episodeName.ifBlank { + "Episode" + } + } + /** * Returns the episode directory name for an episode. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt index 19f90a45a1..f9b12f82ca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt @@ -138,14 +138,26 @@ class MangaDownloadProvider( * @param chapterScanlator scanlator of the chapter to query */ fun getChapterDirName(chapterName: String, chapterScanlator: String?): String { + val newChapterName = sanitizeChapterName(chapterName) return DiskUtil.buildValidFilename( when { - chapterScanlator.isNullOrBlank().not() -> "${chapterScanlator}_$chapterName" - else -> chapterName + chapterScanlator.isNullOrBlank().not() -> "${chapterScanlator}_$newChapterName" + else -> newChapterName }, ) } + /** + * Return the new name for the chapter (in case it's empty or blank) + * + * @param chapterName the name of the chapter + */ + private fun sanitizeChapterName(chapterName: String): String { + return chapterName.ifBlank { + "Chapter" + } + } + fun isChapterDirNameChanged(oldChapter: Chapter, newChapter: Chapter): Boolean { return oldChapter.name != newChapter.name || oldChapter.scanlator?.takeIf { it.isNotBlank() } != newChapter.scanlator?.takeIf { it.isNotBlank() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterTransition.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterTransition.kt new file mode 100644 index 0000000000..8a2f23cb91 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterTransition.kt @@ -0,0 +1,170 @@ +package eu.kanade.tachiyomi.ui.reader + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.OfflinePin +import androidx.compose.material.icons.outlined.Warning +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.manga.Chapter +import eu.kanade.tachiyomi.data.database.models.manga.toDomainChapter +import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager +import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader +import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition +import tachiyomi.domain.entries.manga.model.Manga +import tachiyomi.domain.items.service.calculateChapterGap +import tachiyomi.presentation.core.components.material.SecondaryItemAlpha + +@Composable +fun ChapterTransition( + transition: ChapterTransition, + downloadManager: MangaDownloadManager, + manga: Manga?, +) { + manga ?: return + + val currChapter = transition.from.chapter + val currChapterDownloaded = transition.from.pageLoader is DownloadPageLoader + + val goingToChapter = transition.to?.chapter + val goingToChapterDownloaded = if (goingToChapter != null) { + downloadManager.isChapterDownloaded( + goingToChapter.name, + goingToChapter.scanlator, + manga.title, + manga.source, + skipCache = true, + ) + } else { + false + } + + ProvideTextStyle(MaterialTheme.typography.bodyMedium) { + when (transition) { + is ChapterTransition.Prev -> { + TransitionText( + topLabel = stringResource(R.string.transition_previous), + topChapter = goingToChapter, + topChapterDownloaded = goingToChapterDownloaded, + bottomLabel = stringResource(R.string.transition_current), + bottomChapter = currChapter, + bottomChapterDownloaded = currChapterDownloaded, + fallbackLabel = stringResource(R.string.transition_no_previous), + chapterGap = calculateChapterGap(currChapter.toDomainChapter(), goingToChapter?.toDomainChapter()), + ) + } + is ChapterTransition.Next -> { + TransitionText( + topLabel = stringResource(R.string.transition_finished), + topChapter = currChapter, + topChapterDownloaded = currChapterDownloaded, + bottomLabel = stringResource(R.string.transition_next), + bottomChapter = goingToChapter, + bottomChapterDownloaded = goingToChapterDownloaded, + fallbackLabel = stringResource(R.string.transition_no_next), + chapterGap = calculateChapterGap(goingToChapter?.toDomainChapter(), currChapter.toDomainChapter()), + ) + } + } + } +} + +@Composable +private fun TransitionText( + topLabel: String, + topChapter: Chapter? = null, + topChapterDownloaded: Boolean, + bottomLabel: String, + bottomChapter: Chapter? = null, + bottomChapterDownloaded: Boolean, + fallbackLabel: String, + chapterGap: Int, +) { + val hasTopChapter = topChapter != null + val hasBottomChapter = bottomChapter != null + + Column { + Text( + text = if (hasTopChapter) topLabel else fallbackLabel, + fontWeight = FontWeight.Bold, + textAlign = if (hasTopChapter) TextAlign.Start else TextAlign.Center, + ) + topChapter?.let { ChapterText(chapter = it, downloaded = topChapterDownloaded) } + + Spacer(Modifier.height(16.dp)) + + if (chapterGap > 0) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = Icons.Outlined.Warning, + tint = MaterialTheme.colorScheme.error, + contentDescription = null, + ) + + Text(text = pluralStringResource(R.plurals.missing_chapters_warning, count = chapterGap, chapterGap)) + } + + Spacer(Modifier.height(16.dp)) + } + + Text( + text = if (hasBottomChapter) bottomLabel else fallbackLabel, + fontWeight = FontWeight.Bold, + textAlign = if (hasBottomChapter) TextAlign.Start else TextAlign.Center, + ) + bottomChapter?.let { ChapterText(chapter = it, downloaded = bottomChapterDownloaded) } + } +} + +@Composable +private fun ColumnScope.ChapterText( + chapter: Chapter, + downloaded: Boolean, +) { + FlowRow( + verticalAlignment = Alignment.CenterVertically, + ) { + if (downloaded) { + Icon( + imageVector = Icons.Outlined.OfflinePin, + contentDescription = stringResource(R.string.label_downloaded), + ) + + Spacer(Modifier.width(8.dp)) + } + + Text(chapter.name) + } + + chapter.scanlator?.let { + ProvideTextStyle( + MaterialTheme.typography.bodyMedium.copy( + color = LocalContentColor.current.copy(alpha = SecondaryItemAlpha), + ), + ) { + Text(it) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt index 4224fe84e6..0b0859ea08 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt @@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer import eu.kanade.tachiyomi.util.preference.bindToPreference +import eu.kanade.tachiyomi.util.system.isReleaseBuildType import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import uy.kohesive.injekt.injectLazy @@ -136,6 +137,7 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr .onEach { binding.webtoonPrefsGroup.dualPageInvert.isVisible = it } .launchIn((context as ReaderActivity).lifecycleScope) binding.webtoonPrefsGroup.dualPageInvert.bindToPreference(readerPreferences.dualPageInvertWebtoon()) + binding.webtoonPrefsGroup.longStripSplit.isVisible = !isReleaseBuildType binding.webtoonPrefsGroup.longStripSplit.bindToPreference(readerPreferences.longStripSplitWebtoon()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt index 14386d3c55..0f684736a8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt @@ -2,20 +2,27 @@ package eu.kanade.tachiyomi.ui.reader.setting import android.animation.ValueAnimator import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import com.google.android.material.tabs.TabLayout import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.databinding.CommonTabbedSheetBinding import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import eu.kanade.tachiyomi.widget.ViewPagerAdapter import eu.kanade.tachiyomi.widget.listener.SimpleTabSelectedListener -import eu.kanade.tachiyomi.widget.sheet.TabbedBottomSheetDialog +import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog class ReaderSettingsSheet( private val activity: ReaderActivity, private val showColorFilterSettings: Boolean = false, -) : TabbedBottomSheetDialog(activity) { +) : BaseBottomSheetDialog(activity) { - private val readingModeSettings = ReaderReadingModeSettings(activity) - private val generalSettings = ReaderGeneralSettings(activity) - private val colorFilterSettings = ReaderColorFilterSettings(activity) + private val tabs = listOf( + ReaderReadingModeSettings(activity) to R.string.pref_category_reading_mode, + ReaderGeneralSettings(activity) to R.string.pref_category_general, + ReaderColorFilterSettings(activity) to R.string.custom_filter, + ) private val backgroundDimAnimator by lazy { val sheetBackgroundDim = window?.attributes?.dimAmount ?: 0.25f @@ -27,13 +34,26 @@ class ReaderSettingsSheet( } } + private lateinit var binding: CommonTabbedSheetBinding + + override fun createView(inflater: LayoutInflater): View { + binding = CommonTabbedSheetBinding.inflate(activity.layoutInflater) + + val adapter = Adapter() + binding.pager.offscreenPageLimit = 2 + binding.pager.adapter = adapter + binding.tabs.setupWithViewPager(binding.pager) + + return binding.root + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) behavior.isFitToContents = false behavior.halfExpandedRatio = 0.25f - val filterTabIndex = getTabViews().indexOf(colorFilterSettings) + val filterTabIndex = tabs.indexOfFirst { it.first is ReaderColorFilterSettings } binding.tabs.addOnTabSelectedListener( object : SimpleTabSelectedListener() { override fun onTabSelected(tab: TabLayout.Tab?) { @@ -63,15 +83,18 @@ class ReaderSettingsSheet( } } - override fun getTabViews() = listOf( - readingModeSettings, - generalSettings, - colorFilterSettings, - ) + private inner class Adapter : ViewPagerAdapter() { - override fun getTabTitles() = listOf( - R.string.pref_category_reading_mode, - R.string.pref_category_general, - R.string.custom_filter, - ) + override fun createView(container: ViewGroup, position: Int): View { + return tabs[position].first + } + + override fun getCount(): Int { + return tabs.size + } + + override fun getPageTitle(position: Int): CharSequence { + return activity.resources!!.getString(tabs[position].second) + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt index cbea70e6fc..a2948127ec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt @@ -2,45 +2,11 @@ package eu.kanade.tachiyomi.ui.reader.viewer import eu.kanade.tachiyomi.data.database.models.manga.toDomainChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter -import tachiyomi.domain.items.chapter.model.Chapter -import kotlin.math.floor +import tachiyomi.domain.items.service.calculateChapterGap as domainCalculateChapterGap -private val pattern = Regex("""\d+""") - -fun hasMissingChapters(higherReaderChapter: ReaderChapter?, lowerReaderChapter: ReaderChapter?): Boolean { - if (higherReaderChapter == null || lowerReaderChapter == null) return false - return hasMissingChapters(higherReaderChapter.chapter.toDomainChapter(), lowerReaderChapter.chapter.toDomainChapter()) -} - -fun hasMissingChapters(higherChapter: Chapter?, lowerChapter: Chapter?): Boolean { - if (higherChapter == null || lowerChapter == null) return false - // Check if name contains a number that is potential chapter number - if (!pattern.containsMatchIn(higherChapter.name) || !pattern.containsMatchIn(lowerChapter.name)) return false - // Check if potential chapter number was recognized as chapter number - if (!higherChapter.isRecognizedNumber || !lowerChapter.isRecognizedNumber) return false - return hasMissingChapters(higherChapter.chapterNumber, lowerChapter.chapterNumber) -} - -fun hasMissingChapters(higherChapterNumber: Float, lowerChapterNumber: Float): Boolean { - if (higherChapterNumber < 0f || lowerChapterNumber < 0f) return false - return calculateChapterDifference(higherChapterNumber, lowerChapterNumber) > 0f -} - -fun calculateChapterDifference(higherReaderChapter: ReaderChapter?, lowerReaderChapter: ReaderChapter?): Float { - if (higherReaderChapter == null || lowerReaderChapter == null) return 0f - return calculateChapterDifference(higherReaderChapter.chapter.toDomainChapter(), lowerReaderChapter.chapter.toDomainChapter()) -} - -fun calculateChapterDifference(higherChapter: Chapter?, lowerChapter: Chapter?): Float { - if (higherChapter == null || lowerChapter == null) return 0f - // Check if name contains a number that is potential chapter number - if (!pattern.containsMatchIn(higherChapter.name) || !pattern.containsMatchIn(lowerChapter.name)) return 0f - // Check if potential chapter number was recognized as chapter number - if (!higherChapter.isRecognizedNumber || !lowerChapter.isRecognizedNumber) return 0f - return calculateChapterDifference(higherChapter.chapterNumber, lowerChapter.chapterNumber) -} - -fun calculateChapterDifference(higherChapterNumber: Float, lowerChapterNumber: Float): Float { - if (higherChapterNumber < 0f || lowerChapterNumber < 0f) return 0f - return floor(higherChapterNumber) - floor(lowerChapterNumber) - 1f +fun calculateChapterGap(higherReaderChapter: ReaderChapter?, lowerReaderChapter: ReaderChapter?): Int { + return domainCalculateChapterGap( + higherReaderChapter?.chapter?.toDomainChapter(), + lowerReaderChapter?.chapter?.toDomainChapter(), + ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt index a29dd16448..425937ef66 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt @@ -1,30 +1,17 @@ package eu.kanade.tachiyomi.ui.reader.viewer import android.content.Context -import android.text.SpannableStringBuilder -import android.text.style.ImageSpan import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.LinearLayout -import androidx.core.content.ContextCompat -import androidx.core.text.bold -import androidx.core.text.buildSpannedString -import androidx.core.text.inSpans -import androidx.core.view.isVisible -import eu.kanade.tachiyomi.R +import android.widget.FrameLayout +import androidx.compose.ui.platform.ComposeView import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager -import eu.kanade.tachiyomi.databinding.ReaderTransitionViewBinding -import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader +import eu.kanade.tachiyomi.ui.reader.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition -import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.view.setComposeContent import tachiyomi.domain.entries.manga.model.Manga -import kotlin.math.roundToInt class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - LinearLayout(context, attrs) { - - private val binding: ReaderTransitionViewBinding = - ReaderTransitionViewBinding.inflate(LayoutInflater.from(context), this, true) + FrameLayout(context, attrs) { init { layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) @@ -32,138 +19,18 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At fun bind(transition: ChapterTransition, downloadManager: MangaDownloadManager, manga: Manga?) { manga ?: return - when (transition) { - is ChapterTransition.Prev -> bindPrevChapterTransition(transition, downloadManager, manga) - is ChapterTransition.Next -> bindNextChapterTransition(transition, downloadManager, manga) - } - missingChapterWarning(transition) - } - - /** - * Binds a previous chapter transition on this view and subscribes to the page load status. - */ - private fun bindPrevChapterTransition( - transition: ChapterTransition, - downloadManager: MangaDownloadManager, - manga: Manga, - ) { - val prevChapter = transition.to?.chapter - - binding.lowerText.isVisible = prevChapter != null - if (prevChapter != null) { - binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START - val isPrevDownloaded = downloadManager.isChapterDownloaded( - prevChapter.name, - prevChapter.scanlator, - manga.title, - manga.source, - skipCache = true, - ) - val isCurrentDownloaded = transition.from.pageLoader is DownloadPageLoader - binding.upperText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_previous)) } - append("\n${prevChapter.name}") - if (!prevChapter.scanlator.isNullOrBlank()) { - append(DOT_SEPERATOR) - append("${prevChapter.scanlator}") - } - if (isPrevDownloaded) addDLImageSpan() - } - binding.lowerText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_current)) } - append("\n${transition.from.chapter.name}") - if (!transition.from.chapter.scanlator.isNullOrBlank()) { - append(DOT_SEPERATOR) - append("${transition.from.chapter.scanlator}") - } - if (isCurrentDownloaded) addDLImageSpan() - } - } else { - binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER - binding.upperText.text = context.getString(R.string.transition_no_previous) - } - } - /** - * Binds a next chapter transition on this view and subscribes to the load status. - */ - private fun bindNextChapterTransition( - transition: ChapterTransition, - downloadManager: MangaDownloadManager, - manga: Manga, - ) { - val nextChapter = transition.to?.chapter + removeAllViews() - binding.lowerText.isVisible = nextChapter != null - if (nextChapter != null) { - binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START - val isCurrentDownloaded = transition.from.pageLoader is DownloadPageLoader - val isNextDownloaded = downloadManager.isChapterDownloaded( - nextChapter.name, - nextChapter.scanlator, - manga.title, - manga.source, - skipCache = true, - ) - binding.upperText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_finished)) } - append("\n${transition.from.chapter.name}") - if (!transition.from.chapter.scanlator.isNullOrBlank()) { - append(DOT_SEPERATOR) - append("${transition.from.chapter.scanlator}") - } - if (isCurrentDownloaded) addDLImageSpan() + val transitionView = ComposeView(context).apply { + setComposeContent { + ChapterTransition( + transition = transition, + downloadManager = downloadManager, + manga = manga, + ) } - binding.lowerText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_next)) } - append("\n${nextChapter.name}") - if (!nextChapter.scanlator.isNullOrBlank()) { - append(DOT_SEPERATOR) - append("${nextChapter.scanlator}") - } - if (isNextDownloaded) addDLImageSpan() - } - } else { - binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER - binding.upperText.text = context.getString(R.string.transition_no_next) } - } - - private fun SpannableStringBuilder.addDLImageSpan() { - val icon = ContextCompat.getDrawable(context, R.drawable.ic_offline_pin_24dp)?.mutate() - ?.apply { - val size = binding.lowerText.textSize + 4.dpToPx - setTint(binding.lowerText.currentTextColor) - setBounds(0, 0, size.roundToInt(), size.roundToInt()) - } ?: return - append(" ") - inSpans(ImageSpan(icon)) { append("image") } - } - - private fun missingChapterWarning(transition: ChapterTransition) { - if (transition.to == null) { - binding.warning.isVisible = false - return - } - - val hasMissingChapters = when (transition) { - is ChapterTransition.Prev -> hasMissingChapters(transition.from, transition.to) - is ChapterTransition.Next -> hasMissingChapters(transition.to, transition.from) - } - - if (!hasMissingChapters) { - binding.warning.isVisible = false - return - } - - val chapterDifference = when (transition) { - is ChapterTransition.Prev -> calculateChapterDifference(transition.from, transition.to) - is ChapterTransition.Next -> calculateChapterDifference(transition.to, transition.from) - } - - binding.warningText.text = resources.getQuantityString(R.plurals.missing_chapters_warning, chapterDifference.toInt(), chapterDifference.toInt()) - binding.warning.isVisible = true + addView(transitionView) } } - -private const val DOT_SEPERATOR = " • " diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt index 59c5fb52bd..ec78dc8c6c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt @@ -7,7 +7,7 @@ import eu.kanade.tachiyomi.ui.reader.model.InsertPage import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters -import eu.kanade.tachiyomi.ui.reader.viewer.hasMissingChapters +import eu.kanade.tachiyomi.ui.reader.viewer.calculateChapterGap import eu.kanade.tachiyomi.util.system.createReaderThemeContext import eu.kanade.tachiyomi.widget.ViewPagerAdapter import tachiyomi.core.util.system.logcat @@ -48,8 +48,8 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { val newItems = mutableListOf() // Forces chapter transition if there is missing chapters - val prevHasMissingChapters = hasMissingChapters(chapters.currChapter, chapters.prevChapter) - val nextHasMissingChapters = hasMissingChapters(chapters.nextChapter, chapters.currChapter) + val prevHasMissingChapters = calculateChapterGap(chapters.currChapter, chapters.prevChapter) > 0 + val nextHasMissingChapters = calculateChapterGap(chapters.nextChapter, chapters.currChapter) > 0 // Add previous chapter pages and transition. if (chapters.prevChapter != null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt index 71766a2e6c..87edd1da45 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt @@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.StencilPage import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView -import eu.kanade.tachiyomi.ui.reader.viewer.hasMissingChapters +import eu.kanade.tachiyomi.ui.reader.viewer.calculateChapterGap import eu.kanade.tachiyomi.util.system.createReaderThemeContext import tachiyomi.core.util.system.logcat @@ -62,8 +62,8 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : RecyclerView.Adapter() // Forces chapter transition if there is missing chapters - val prevHasMissingChapters = hasMissingChapters(chapters.currChapter, chapters.prevChapter) - val nextHasMissingChapters = hasMissingChapters(chapters.nextChapter, chapters.currChapter) + val prevHasMissingChapters = calculateChapterGap(chapters.currChapter, chapters.prevChapter) > 0 + val nextHasMissingChapters = calculateChapterGap(chapters.nextChapter, chapters.currChapter) > 0 // Add previous chapter pages and transition. if (chapters.prevChapter != null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 14b0f76d39..60eaacbe93 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -19,7 +19,6 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.TooltipCompat -import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme @@ -34,10 +33,11 @@ import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.getResourceColor -inline fun ComposeView.setComposeContent(crossinline content: @Composable () -> Unit) { - consumeWindowInsets = false - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { +inline fun ComponentActivity.setComposeContent( + parent: CompositionContext? = null, + crossinline content: @Composable () -> Unit, +) { + setContent(parent) { TachiyomiTheme { CompositionLocalProvider( LocalTextStyle provides MaterialTheme.typography.bodySmall, @@ -49,11 +49,11 @@ inline fun ComposeView.setComposeContent(crossinline content: @Composable () -> } } -inline fun ComponentActivity.setComposeContent( - parent: CompositionContext? = null, - crossinline content: @Composable () -> Unit, +fun ComposeView.setComposeContent( + content: @Composable () -> Unit, ) { - setContent(parent) { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { TachiyomiTheme { CompositionLocalProvider( LocalTextStyle provides MaterialTheme.typography.bodySmall, diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/sheet/TabbedBottomSheetDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/sheet/TabbedBottomSheetDialog.kt deleted file mode 100644 index 447a9c6b63..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/sheet/TabbedBottomSheetDialog.kt +++ /dev/null @@ -1,43 +0,0 @@ -package eu.kanade.tachiyomi.widget.sheet - -import android.app.Activity -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import eu.kanade.tachiyomi.databinding.CommonTabbedSheetBinding -import eu.kanade.tachiyomi.widget.ViewPagerAdapter - -abstract class TabbedBottomSheetDialog(private val activity: Activity) : BaseBottomSheetDialog(activity) { - - lateinit var binding: CommonTabbedSheetBinding - - override fun createView(inflater: LayoutInflater): View { - binding = CommonTabbedSheetBinding.inflate(activity.layoutInflater) - - val adapter = LibrarySettingsSheetAdapter() - binding.pager.offscreenPageLimit = 2 - binding.pager.adapter = adapter - binding.tabs.setupWithViewPager(binding.pager) - - return binding.root - } - - abstract fun getTabViews(): List - - abstract fun getTabTitles(): List - - private inner class LibrarySettingsSheetAdapter : ViewPagerAdapter() { - - override fun createView(container: ViewGroup, position: Int): View { - return getTabViews()[position] - } - - override fun getCount(): Int { - return getTabViews().size - } - - override fun getPageTitle(position: Int): CharSequence { - return activity.resources!!.getString(getTabTitles()[position]) - } - } -} diff --git a/app/src/main/res/layout/compose_controller.xml b/app/src/main/res/layout/compose_controller.xml deleted file mode 100644 index 617287296b..0000000000 --- a/app/src/main/res/layout/compose_controller.xml +++ /dev/null @@ -1,4 +0,0 @@ - - diff --git a/app/src/main/res/layout/reader_transition_view.xml b/app/src/main/res/layout/reader_transition_view.xml deleted file mode 100644 index 2ec51224b2..0000000000 --- a/app/src/main/res/layout/reader_transition_view.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/core/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt b/core/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt index 7f3f6eaaf5..57950eda34 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt @@ -14,15 +14,15 @@ object DeviceUtil { /** * Extracts the MIUI major version code from a string like "V12.5.3.0.QFGMIXM". * - * @return MIUI major version code (e.g., 13) or -1 if can't be parsed. + * @return MIUI major version code (e.g., 13) or null if can't be parsed. */ val miuiMajorVersion by lazy { - if (!isMiui) return@lazy -1 + if (!isMiui) return@lazy null Build.VERSION.INCREMENTAL .substringBefore('.') .trimStart('V') - .toIntOrNull() ?: -1 + .toIntOrNull() } @SuppressLint("PrivateApi") @@ -45,6 +45,20 @@ object DeviceUtil { Build.MANUFACTURER.equals("samsung", ignoreCase = true) } + val oneUiVersion by lazy { + try { + val semPlatformIntField = Build.VERSION::class.java.getDeclaredField("SEM_PLATFORM_INT") + val version = semPlatformIntField.getInt(null) - 90000 + if (version < 0) { + 1.0 + } else { + ((version / 10000).toString() + "." + version % 10000 / 100).toDouble() + } + } catch (e: Exception) { + null + } + } + val invalidDefaultBrowsers = listOf( "android", "com.huawei.android.internal.app", diff --git a/domain/src/main/java/tachiyomi/domain/items/service/MissingItems.kt b/domain/src/main/java/tachiyomi/domain/items/service/MissingItems.kt index c98b20aa60..e2485f9de9 100644 --- a/domain/src/main/java/tachiyomi/domain/items/service/MissingItems.kt +++ b/domain/src/main/java/tachiyomi/domain/items/service/MissingItems.kt @@ -1,5 +1,9 @@ package tachiyomi.domain.items.service +import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.items.episode.model.Episode +import kotlin.math.floor + fun List.missingItemsCount(): Int { if (this.isEmpty()) { return 0 @@ -33,3 +37,25 @@ fun List.missingItemsCount(): Int { return missingItemsCount } + +fun calculateChapterGap(higherChapter: Chapter?, lowerChapter: Chapter?): Int { + if (higherChapter == null || lowerChapter == null) return 0 + if (!higherChapter.isRecognizedNumber || !lowerChapter.isRecognizedNumber) return 0 + return calculateChapterGap(higherChapter.chapterNumber, lowerChapter.chapterNumber) +} + +fun calculateChapterGap(higherChapterNumber: Float, lowerChapterNumber: Float): Int { + if (higherChapterNumber < 0f || lowerChapterNumber < 0f) return 0 + return floor(higherChapterNumber).toInt() - floor(lowerChapterNumber).toInt() - 1 +} + +fun calculateEpisodeGap(higherEpisode: Episode?, lowerEpisode: Episode?): Int { + if (higherEpisode == null || lowerEpisode == null) return 0 + if (!higherEpisode.isRecognizedNumber || !lowerEpisode.isRecognizedNumber) return 0 + return calculateChapterGap(higherEpisode.episodeNumber, lowerEpisode.episodeNumber) +} + +fun calculateEpisodeGap(higherEpisodeNumber: Float, lowerEpisodeNumber: Float): Int { + if (higherEpisodeNumber < 0f || lowerEpisodeNumber < 0f) return 0 + return floor(higherEpisodeNumber).toInt() - floor(lowerEpisodeNumber).toInt() - 1 +} diff --git a/domain/src/test/java/tachiyomi/domain/items/service/MissingItemsTest.kt b/domain/src/test/java/tachiyomi/domain/items/service/MissingItemsTest.kt index 82e148f340..2007beffc1 100644 --- a/domain/src/test/java/tachiyomi/domain/items/service/MissingItemsTest.kt +++ b/domain/src/test/java/tachiyomi/domain/items/service/MissingItemsTest.kt @@ -4,27 +4,81 @@ import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode +import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.items.episode.model.Episode @Execution(ExecutionMode.CONCURRENT) class MissingItemsTest { @Test - fun `returns 0 when empty list`() { + fun `missingItemsCount returns 0 when empty list`() { emptyList().missingItemsCount() shouldBe 0 } @Test - fun `returns 0 when all unknown chapter numbers`() { + fun `missingItemsCount returns 0 when all unknown item numbers`() { listOf(-1f, -1f, -1f).missingItemsCount() shouldBe 0 } @Test - fun `handles repeated base chapter numbers`() { + fun `missingItemsCount handles repeated base item numbers`() { listOf(1f, 1.0f, 1.1f, 1.5f, 1.6f, 1.99f).missingItemsCount() shouldBe 0 } @Test - fun `returns number of missing chapters`() { + fun `missingItemsCount returns number of missing items`() { listOf(-1f, 1f, 2f, 2.2f, 4f, 6f, 10f, 11f).missingItemsCount() shouldBe 5 } + + @Test + fun `calculateChapterGap returns difference`() { + calculateChapterGap(chapter(10f), chapter(9f)) shouldBe 0f + calculateChapterGap(chapter(10f), chapter(8f)) shouldBe 1f + calculateChapterGap(chapter(10f), chapter(8.5f)) shouldBe 1f + calculateChapterGap(chapter(10f), chapter(1.1f)) shouldBe 8f + + calculateChapterGap(10f, 9f) shouldBe 0f + calculateChapterGap(10f, 8f) shouldBe 1f + calculateChapterGap(10f, 8.5f) shouldBe 1f + calculateChapterGap(10f, 1.1f) shouldBe 8f + } + + @Test + fun `calculateChapterGap returns 0 if either are not valid chapter numbers`() { + calculateChapterGap(chapter(-1f), chapter(10f)) shouldBe 0 + calculateChapterGap(chapter(99f), chapter(-1f)) shouldBe 0 + + calculateChapterGap(-1f, 10f) shouldBe 0 + calculateChapterGap(99f, -1f) shouldBe 0 + } + + private fun chapter(number: Float) = Chapter.create().copy( + chapterNumber = number, + ) + + @Test + fun `calculateEpisodeGap returns difference`() { + calculateEpisodeGap(episode(10f), episode(9f)) shouldBe 0f + calculateEpisodeGap(episode(10f), episode(8f)) shouldBe 1f + calculateEpisodeGap(episode(10f), episode(8.5f)) shouldBe 1f + calculateEpisodeGap(episode(10f), episode(1.1f)) shouldBe 8f + + calculateEpisodeGap(10f, 9f) shouldBe 0f + calculateEpisodeGap(10f, 8f) shouldBe 1f + calculateEpisodeGap(10f, 8.5f) shouldBe 1f + calculateEpisodeGap(10f, 1.1f) shouldBe 8f + } + + @Test + fun `calculateEpisodeGap returns 0 if either are not valid episode numbers`() { + calculateEpisodeGap(episode(-1f), episode(10f)) shouldBe 0 + calculateEpisodeGap(episode(99f), episode(-1f)) shouldBe 0 + + calculateEpisodeGap(-1f, 10f) shouldBe 0 + calculateEpisodeGap(99f, -1f) shouldBe 0 + } + + private fun episode(number: Float) = Episode.create().copy( + episodeNumber = number, + ) } diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 0594e58e2a..56b1522536 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -10,7 +10,7 @@ appcompat = "androidx.appcompat:appcompat:1.6.1" biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05" constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4" coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:1.2.0" -corektx = "androidx.core:core-ktx:1.11.0-alpha02" +corektx = "androidx.core:core-ktx:1.11.0-alpha03" splashscreen = "androidx.core:core-splashscreen:1.0.0-alpha02" recyclerview = "androidx.recyclerview:recyclerview:1.3.0" viewpager = "androidx.viewpager:viewpager:1.1.0-alpha01" diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 121b4f9158..73cdd7e3bf 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,10 +1,10 @@ [versions] -compiler = "1.4.4" +compiler = "1.4.6" compose-bom = "2023.03.00" accompanist = "0.30.1" [libraries] -activity = "androidx.activity:activity-compose:1.7.0" +activity = "androidx.activity:activity-compose:1.7.1" bom = { group = "dev.chrisbanes.compose", name = "compose-bom", version.ref = "compose-bom" } foundation = { module = "androidx.compose.foundation:foundation" } animation = { module = "androidx.compose.animation:animation" } diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index d462eb7069..16704bf3c7 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin_version = "1.8.10" +kotlin_version = "1.8.20" serialization_version = "1.5.0" xml_serialization_version = "0.85.0" @@ -10,6 +10,7 @@ gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = " coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version = "1.6.4" } coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" } coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" } +coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava" } serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_version" } serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "serialization_version" } @@ -18,7 +19,7 @@ serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core-android", v serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-android", version.ref = "xml_serialization_version" } [bundles] -coroutines = ["coroutines-core", "coroutines-android"] +coroutines = ["coroutines-core", "coroutines-android", "coroutines-guava"] serialization = ["serialization-json", "serialization-json-okio", "serialization-protobuf", "serialization-xml-core", "serialization-xml"] [plugins] diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5f7c89921e..12f030c920 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -62,7 +62,7 @@ photoview = "com.github.chrisbanes:PhotoView:2.3.0" directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0" insetter = "dev.chrisbanes.insetter:insetter:0.6.1" compose-cascade = "me.saket.cascade:cascade-compose:2.0.0-rc02" -compose-materialmotion = "io.github.fornewid:material-motion-compose-core:0.11.2" +compose-materialmotion = "io.github.fornewid:material-motion-compose-core:0.11.3" compose-simpleicons = "br.com.devsrsouza.compose.icons.android:simple-icons:1.0.0" logcat = "com.squareup.logcat:logcat:0.1" @@ -87,7 +87,7 @@ sqldelight-android-paging = { module = "com.squareup.sqldelight:android-paging3- sqldelight-gradle = { module = "com.squareup.sqldelight:gradle-plugin", version.ref = "sqldelight" } junit = "org.junit.jupiter:junit-jupiter:5.9.2" -kotest-assertions = "io.kotest:kotest-assertions-core:5.5.5" +kotest-assertions = "io.kotest:kotest-assertions-core:5.6.1" voyager-navigator = { module = "ca.gosyer:voyager-navigator", version.ref = "voyager" } voyager-tab-navigator = { module = "ca.gosyer:voyager-tab-navigator", version.ref = "voyager" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0c85a1f751..37aef8d3f0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/AnimeSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/AnimeSource.kt index 3182a91959..ce81fefe81 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/AnimeSource.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/AnimeSource.kt @@ -7,12 +7,12 @@ import eu.kanade.tachiyomi.util.awaitSingle import rx.Observable /** - * A basic interface for creating a source. It could be an online source, a local source, etc... + * A basic interface for creating a source. It could be an online source, a local source, etc. */ interface AnimeSource { /** - * Id for the source. Must be unique. + * ID for the source. Must be unique. */ val id: Long @@ -46,9 +46,9 @@ interface AnimeSource { ) fun fetchEpisodeList(anime: SAnime): Observable> = throw IllegalStateException("Not used") - // TODO: remove direct usages on this method /** - * Returns an observable with the list of videos a episode has. + * Returns an observable with the list of videos a episode has. Videos should be returned + * in the expected order; the index is ignored. * * @param episode the episode. */ @@ -75,7 +75,8 @@ interface AnimeSource { } /** - * [1.x API] Get the list of videos a episode has. + * [1.x API] Get the list of videos a episode has. Videos should be returned + * in the expected order; the index is ignored. */ @Suppress("DEPRECATION") suspend fun getVideoList(episode: SEpisode): List