diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fd294bb74..a7d394f99 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,7 +23,7 @@ android { defaultConfig { applicationId = "eu.kanade.tachiyomi" - versionCode = 104 + versionCode = 105 versionName = "0.14.6" buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") @@ -194,7 +194,7 @@ dependencies { implementation(androidx.bundles.workmanager) // RxJava - implementation(libs.bundles.reactivex) + implementation(libs.rxjava) implementation(libs.flowreactivenetwork) // Networking diff --git a/app/proguard-android-optimize.txt b/app/proguard-android-optimize.txt index 9f5340316..7072ff703 100644 --- a/app/proguard-android-optimize.txt +++ b/app/proguard-android-optimize.txt @@ -14,7 +14,7 @@ } -keepclassmembers class * implements android.os.Parcelable { - public static final ** CREATOR; + public static final ** CREATOR; } -keep class androidx.annotation.Keep diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index b3529d60f..b1ea84c40 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -11,8 +11,8 @@ -keep,allowoptimization class kotlin.time.** { public protected *; } -keep,allowoptimization class okhttp3.** { public protected *; } -keep,allowoptimization class okio.** { public protected *; } --keep,allowoptimization class rx.** { public protected *; } -keep,allowoptimization class org.jsoup.** { public protected *; } +-keep,allowoptimization class rx.** { public protected *; } -keep,allowoptimization class app.cash.quickjs.** { public protected *; } -keep,allowoptimization class uy.kohesive.injekt.** { public protected *; } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8235f8962..f75bf4b8c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -65,10 +65,10 @@ android:exported="false" /> diff --git a/app/src/main/java/eu/kanade/core/util/CollectionUtils.kt b/app/src/main/java/eu/kanade/core/util/CollectionUtils.kt index 501763150..8dab8a054 100644 --- a/app/src/main/java/eu/kanade/core/util/CollectionUtils.kt +++ b/app/src/main/java/eu/kanade/core/util/CollectionUtils.kt @@ -1,7 +1,6 @@ package eu.kanade.core.util import androidx.compose.ui.util.fastForEach -import java.util.concurrent.ConcurrentHashMap import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -20,15 +19,6 @@ fun List.insertSeparators( return newList } -/** - * Returns a new map containing only the key entries of [transform] that are not null. - */ -inline fun Map.mapNotNullKeys(transform: (Map.Entry) -> R?): ConcurrentHashMap { - val mutableMap = ConcurrentHashMap() - forEach { element -> transform(element)?.let { mutableMap[it] = element.value } } - return mutableMap -} - fun HashSet.addOrRemove(value: E, shouldAdd: Boolean) { if (shouldAdd) { add(value) diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index aac1f060d..b245d9057 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -16,6 +16,7 @@ import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.domain.source.interactor.ToggleLanguage import eu.kanade.domain.source.interactor.ToggleSource import eu.kanade.domain.source.interactor.ToggleSourcePin +import eu.kanade.domain.track.interactor.RefreshTracks import eu.kanade.domain.track.interactor.TrackChapter import tachiyomi.data.category.CategoryRepositoryImpl import tachiyomi.data.chapter.ChapterRepositoryImpl @@ -113,6 +114,7 @@ class DomainModule : InjektModule { addSingletonFactory { TrackRepositoryImpl(get()) } addFactory { TrackChapter(get(), get(), get(), get()) } + addFactory { RefreshTracks(get(), get(), get(), get()) } addFactory { DeleteTrack(get()) } addFactory { GetTracksPerManga(get()) } addFactory { GetTracks(get()) } @@ -125,7 +127,7 @@ class DomainModule : InjektModule { addFactory { SetReadStatus(get(), get(), get(), get()) } addFactory { ShouldUpdateDbChapter() } addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get()) } - addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) } + addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get(), get()) } addSingletonFactory { HistoryRepositoryImpl(get()) } addFactory { GetHistory(get()) } diff --git a/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithTrackServiceTwoWay.kt b/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithTrackServiceTwoWay.kt index da9ee05ea..ddd8ebaa3 100644 --- a/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithTrackServiceTwoWay.kt +++ b/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithTrackServiceTwoWay.kt @@ -1,11 +1,12 @@ package eu.kanade.domain.chapter.interactor import eu.kanade.domain.track.model.toDbTrack +import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.TrackService import logcat.LogPriority import tachiyomi.core.util.system.logcat +import tachiyomi.domain.chapter.interactor.GetChapterByMangaId import tachiyomi.domain.chapter.interactor.UpdateChapter -import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.toChapterUpdate import tachiyomi.domain.track.interactor.InsertTrack import tachiyomi.domain.track.model.Track @@ -13,14 +14,22 @@ import tachiyomi.domain.track.model.Track class SyncChaptersWithTrackServiceTwoWay( private val updateChapter: UpdateChapter, private val insertTrack: InsertTrack, + private val getChapterByMangaId: GetChapterByMangaId, ) { suspend fun await( - chapters: List, + mangaId: Long, remoteTrack: Track, service: TrackService, ) { - val sortedChapters = chapters.sortedBy { it.chapterNumber } + if (service !is EnhancedTrackService) { + return + } + + val sortedChapters = getChapterByMangaId.await(mangaId) + .sortedBy { it.chapterNumber } + .filter { it.isRecognizedNumber } + val chapterUpdates = sortedChapters .filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read } .map { it.copy(read = true).toChapterUpdate() } diff --git a/app/src/main/java/eu/kanade/domain/track/interactor/RefreshTracks.kt b/app/src/main/java/eu/kanade/domain/track/interactor/RefreshTracks.kt new file mode 100644 index 000000000..dbff0e934 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/track/interactor/RefreshTracks.kt @@ -0,0 +1,43 @@ +package eu.kanade.domain.track.interactor + +import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay +import eu.kanade.domain.track.model.toDbTrack +import eu.kanade.domain.track.model.toDomainTrack +import eu.kanade.tachiyomi.data.track.TrackManager +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.supervisorScope +import logcat.LogPriority +import tachiyomi.core.util.system.logcat +import tachiyomi.domain.track.interactor.GetTracks +import tachiyomi.domain.track.interactor.InsertTrack + +class RefreshTracks( + private val getTracks: GetTracks, + private val trackManager: TrackManager, + private val insertTrack: InsertTrack, + private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay, +) { + + suspend fun await(mangaId: Long) { + supervisorScope { + getTracks.await(mangaId) + .map { track -> + async { + val service = trackManager.getService(track.syncId) + if (service != null && service.isLoggedIn) { + try { + val updatedTrack = service.refresh(track.toDbTrack()) + insertTrack.await(updatedTrack.toDomainTrack()!!) + syncChaptersWithTrackServiceTwoWay.await(mangaId, track, service) + } catch (e: Throwable) { + // Ignore errors and continue + logcat(LogPriority.ERROR, e) + } + } + } + } + .awaitAll() + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/track/interactor/TrackChapter.kt b/app/src/main/java/eu/kanade/domain/track/interactor/TrackChapter.kt index 57a49006a..96f2f6ca4 100644 --- a/app/src/main/java/eu/kanade/domain/track/interactor/TrackChapter.kt +++ b/app/src/main/java/eu/kanade/domain/track/interactor/TrackChapter.kt @@ -24,33 +24,31 @@ class TrackChapter( suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) = coroutineScope { launchNonCancellable { val tracks = getTracks.await(mangaId) - if (tracks.isEmpty()) return@launchNonCancellable tracks.mapNotNull { track -> val service = trackManager.getService(track.syncId) - if (service != null && service.isLogged && chapterNumber > track.lastChapterRead) { - val updatedTrack = track.copy(lastChapterRead = chapterNumber) + if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) { + return@mapNotNull null + } - async { - runCatching { - try { - service.update(updatedTrack.toDbTrack(), true) - insertTrack.await(updatedTrack) - } catch (e: Exception) { - delayedTrackingStore.addItem(updatedTrack) - DelayedTrackingUpdateJob.setupTask(context) - throw e - } + val updatedTrack = track.copy(lastChapterRead = chapterNumber) + async { + runCatching { + try { + service.update(updatedTrack.toDbTrack(), true) + insertTrack.await(updatedTrack) + } catch (e: Exception) { + delayedTrackingStore.addItem(updatedTrack) + DelayedTrackingUpdateJob.setupTask(context) + throw e } } - } else { - null } } .awaitAll() .mapNotNull { it.exceptionOrNull() } - .forEach { logcat(LogPriority.INFO, it) } + .forEach { logcat(LogPriority.WARN, it) } } } } diff --git a/app/src/main/java/eu/kanade/domain/track/service/DelayedTrackingUpdateJob.kt b/app/src/main/java/eu/kanade/domain/track/service/DelayedTrackingUpdateJob.kt index 3959c6707..0273e0fdc 100644 --- a/app/src/main/java/eu/kanade/domain/track/service/DelayedTrackingUpdateJob.kt +++ b/app/src/main/java/eu/kanade/domain/track/service/DelayedTrackingUpdateJob.kt @@ -48,7 +48,7 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters) .forEach { track -> try { val service = trackManager.getService(track.syncId) - if (service != null && service.isLogged) { + if (service != null && service.isLoggedIn) { logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" } service.update(track.toDbTrack(), true) insertTrack.await(track) diff --git a/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt index 433add984..fab831da7 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt @@ -76,7 +76,7 @@ fun ExtensionScreen( enabled = !state.isLoading, ) { when { - state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) + state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) state.isEmpty -> { val msg = if (!searchQuery.isNullOrEmpty()) { R.string.no_results_found diff --git a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt index aaf23646a..2c4f4fc46 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt @@ -51,7 +51,7 @@ fun MigrateSourceScreen( ) { val context = LocalContext.current when { - state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) + state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) state.isEmpty -> EmptyScreen( textResource = R.string.information_empty_library, modifier = Modifier.padding(contentPadding), diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt index 12175e4bd..de5434595 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt @@ -47,7 +47,7 @@ fun SourcesScreen( onLongClickItem: (Source) -> Unit, ) { when { - state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) + state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) state.isEmpty -> EmptyScreen( textResource = R.string.source_empty_screen, modifier = Modifier.padding(contentPadding), diff --git a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt index 1a80c8f01..3bac670d0 100644 --- a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt @@ -65,7 +65,7 @@ fun HistoryScreen( ) { contentPadding -> state.list.let { if (it == null) { - LoadingScreen(modifier = Modifier.padding(contentPadding)) + LoadingScreen(Modifier.padding(contentPadding)) } else if (it.isEmpty()) { val msg = if (!state.searchQuery.isNullOrEmpty()) { R.string.no_results_found diff --git a/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt index 78329d3e5..aaf846943 100644 --- a/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt @@ -180,7 +180,7 @@ private val displayModes = listOf( private fun ColumnScope.DisplayPage( screenModel: LibrarySettingsScreenModel, ) { - val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState() + val displayMode by screenModel.libraryPreferences.displayMode().collectAsState() SettingsChipRow(R.string.action_display_mode) { displayModes.map { (titleRes, mode) -> FilterChip( diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt index 33a277c94..940a48225 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt @@ -164,7 +164,7 @@ internal fun PreferenceItem( TrackingPreferenceWidget( service = this, checked = uName.isNotEmpty(), - onClick = { if (isLogged) item.logout() else item.login() }, + onClick = { if (isLoggedIn) item.logout() else item.login() }, ) } } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt index 5ae741135..35552e225 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt @@ -31,7 +31,6 @@ import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.ResetCategoryFlags import tachiyomi.domain.category.model.Category import tachiyomi.domain.library.service.LibraryPreferences -import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_BATTERY_NOT_LOW import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_NETWORK_NOT_METERED import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI @@ -123,14 +122,14 @@ object SettingsLibraryScreen : SearchableSettings { ): Preference.PreferenceGroup { val context = LocalContext.current - val libraryUpdateIntervalPref = libraryPreferences.libraryUpdateInterval() - val libraryUpdateCategoriesPref = libraryPreferences.libraryUpdateCategories() - val libraryUpdateCategoriesExcludePref = libraryPreferences.libraryUpdateCategoriesExclude() + val autoUpdateIntervalPref = libraryPreferences.autoUpdateInterval() + val autoUpdateCategoriesPref = libraryPreferences.updateCategories() + val autoUpdateCategoriesExcludePref = libraryPreferences.updateCategoriesExclude() - val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState() + val autoUpdateInterval by autoUpdateIntervalPref.collectAsState() - val included by libraryUpdateCategoriesPref.collectAsState() - val excluded by libraryUpdateCategoriesExcludePref.collectAsState() + val included by autoUpdateCategoriesPref.collectAsState() + val excluded by autoUpdateCategoriesExcludePref.collectAsState() var showCategoriesDialog by rememberSaveable { mutableStateOf(false) } if (showCategoriesDialog) { TriStateListDialog( @@ -142,8 +141,8 @@ object SettingsLibraryScreen : SearchableSettings { itemLabel = { it.visualName }, onDismissRequest = { showCategoriesDialog = false }, onValueChanged = { newIncluded, newExcluded -> - libraryUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet()) - libraryUpdateCategoriesExcludePref.set(newExcluded.map { it.id.toString() }.toSet()) + autoUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet()) + autoUpdateCategoriesExcludePref.set(newExcluded.map { it.id.toString() }.toSet()) showCategoriesDialog = false }, ) @@ -153,7 +152,7 @@ object SettingsLibraryScreen : SearchableSettings { title = stringResource(R.string.pref_category_library_update), preferenceItems = listOf( Preference.PreferenceItem.ListPreference( - pref = libraryUpdateIntervalPref, + pref = autoUpdateIntervalPref, title = stringResource(R.string.pref_library_update_interval), entries = mapOf( 0 to stringResource(R.string.update_never), @@ -169,15 +168,14 @@ object SettingsLibraryScreen : SearchableSettings { }, ), Preference.PreferenceItem.MultiSelectListPreference( - pref = libraryPreferences.libraryUpdateDeviceRestriction(), - enabled = libraryUpdateInterval > 0, + pref = libraryPreferences.autoUpdateDeviceRestrictions(), + enabled = autoUpdateInterval > 0, title = stringResource(R.string.pref_library_update_restriction), subtitle = stringResource(R.string.restrictions), entries = mapOf( DEVICE_ONLY_ON_WIFI to stringResource(R.string.connected_to_wifi), DEVICE_NETWORK_NOT_METERED to stringResource(R.string.network_not_metered), DEVICE_CHARGING to stringResource(R.string.charging), - DEVICE_BATTERY_NOT_LOW to stringResource(R.string.battery_not_low), ), onValueChanged = { // Post to event looper to allow the preference to be updated. @@ -206,7 +204,7 @@ object SettingsLibraryScreen : SearchableSettings { subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary), ), Preference.PreferenceItem.MultiSelectListPreference( - pref = libraryPreferences.libraryUpdateMangaRestriction(), + pref = libraryPreferences.autoUpdateMangaRestrictions(), title = stringResource(R.string.pref_library_update_manga_restriction), entries = mapOf( MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read), diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt index dbb5e3305..1f4a56d5f 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt @@ -81,7 +81,7 @@ fun UpdateScreen( snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { contentPadding -> when { - state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) + state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) state.items.isEmpty() -> EmptyScreen( textResource = R.string.information_no_recent, modifier = Modifier.padding(contentPadding), diff --git a/app/src/main/java/eu/kanade/presentation/util/ExceptionFormatter.kt b/app/src/main/java/eu/kanade/presentation/util/ExceptionFormatter.kt index 7169c9e9b..9b4d4286f 100644 --- a/app/src/main/java/eu/kanade/presentation/util/ExceptionFormatter.kt +++ b/app/src/main/java/eu/kanade/presentation/util/ExceptionFormatter.kt @@ -4,16 +4,26 @@ import android.content.Context import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.network.HttpException import eu.kanade.tachiyomi.source.online.LicensedMangaChaptersException +import eu.kanade.tachiyomi.util.system.isOnline import tachiyomi.data.source.NoResultsException import tachiyomi.domain.source.model.SourceNotInstalledException +import java.net.UnknownHostException context(Context) val Throwable.formattedMessage: String get() { when (this) { + is HttpException -> return getString(R.string.exception_http, code) + is UnknownHostException -> { + return if (!isOnline()) { + getString(R.string.exception_offline) + } else { + getString(R.string.exception_unknown_host, message) + } + } + is NoResultsException -> return getString(R.string.no_results_found) is SourceNotInstalledException -> return getString(R.string.loader_not_implemented_error) - is HttpException -> return "$message: ${getString(R.string.http_error_hint)}" is LicensedMangaChaptersException -> return getString(R.string.licensed_manga_chapters_error) } return when (val className = this::class.simpleName) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index a493d216f..75edfdd46 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.workManager import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.TriState +import tachiyomi.core.preference.getAndSet import tachiyomi.core.preference.getEnum import tachiyomi.core.preference.minusAssign import tachiyomi.core.preference.plusAssign @@ -101,11 +102,11 @@ object Migrations { } if (oldVersion < 44) { // Reset sorting preference if using removed sort by source - val oldSortingMode = prefs.getInt(libraryPreferences.librarySortingMode().key(), 0) + val oldSortingMode = prefs.getInt(libraryPreferences.sortingMode().key(), 0) if (oldSortingMode == 5) { // SOURCE = 5 prefs.edit { - putInt(libraryPreferences.librarySortingMode().key(), 0) // ALPHABETICAL = 0 + putInt(libraryPreferences.sortingMode().key(), 0) // ALPHABETICAL = 0 } } } @@ -134,7 +135,7 @@ object Migrations { // Force MAL log out due to login flow change // v52: switched from scraping to WebView // v53: switched from WebView to OAuth - if (trackManager.myAnimeList.isLogged) { + if (trackManager.myAnimeList.isLoggedIn) { trackManager.myAnimeList.logout() context.toast(R.string.myanimelist_relogin) } @@ -180,14 +181,14 @@ object Migrations { } if (oldVersion < 61) { // Handle removed every 1 or 2 hour library updates - val updateInterval = libraryPreferences.libraryUpdateInterval().get() + val updateInterval = libraryPreferences.autoUpdateInterval().get() if (updateInterval == 1 || updateInterval == 2) { - libraryPreferences.libraryUpdateInterval().set(3) + libraryPreferences.autoUpdateInterval().set(3) LibraryUpdateJob.setupTask(context, 3) } } if (oldVersion < 64) { - val oldSortingMode = prefs.getInt(libraryPreferences.librarySortingMode().key(), 0) + val oldSortingMode = prefs.getInt(libraryPreferences.sortingMode().key(), 0) val oldSortingDirection = prefs.getBoolean("library_sorting_ascending", true) val newSortingMode = when (oldSortingMode) { @@ -208,12 +209,12 @@ object Migrations { } prefs.edit(commit = true) { - remove(libraryPreferences.librarySortingMode().key()) + remove(libraryPreferences.sortingMode().key()) remove("library_sorting_ascending") } prefs.edit { - putString(libraryPreferences.librarySortingMode().key(), newSortingMode) + putString(libraryPreferences.sortingMode().key(), newSortingMode) putString("library_sorting_ascending", newSortingDirection) } } @@ -224,16 +225,16 @@ object Migrations { } if (oldVersion < 71) { // Handle removed every 3, 4, 6, and 8 hour library updates - val updateInterval = libraryPreferences.libraryUpdateInterval().get() + val updateInterval = libraryPreferences.autoUpdateInterval().get() if (updateInterval in listOf(3, 4, 6, 8)) { - libraryPreferences.libraryUpdateInterval().set(12) + libraryPreferences.autoUpdateInterval().set(12) LibraryUpdateJob.setupTask(context, 12) } } if (oldVersion < 72) { val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true) if (!oldUpdateOngoingOnly) { - libraryPreferences.libraryUpdateMangaRestriction() -= MANGA_NON_COMPLETED + libraryPreferences.autoUpdateMangaRestrictions() -= MANGA_NON_COMPLETED } } if (oldVersion < 75) { @@ -258,20 +259,20 @@ object Migrations { if (oldVersion < 81) { // Handle renamed enum values prefs.edit { - val newSortingMode = when (val oldSortingMode = prefs.getString(libraryPreferences.librarySortingMode().key(), "ALPHABETICAL")) { + val newSortingMode = when (val oldSortingMode = prefs.getString(libraryPreferences.sortingMode().key(), "ALPHABETICAL")) { "LAST_CHECKED" -> "LAST_MANGA_UPDATE" "UNREAD" -> "UNREAD_COUNT" "DATE_FETCHED" -> "CHAPTER_FETCH_DATE" else -> oldSortingMode } - putString(libraryPreferences.librarySortingMode().key(), newSortingMode) + putString(libraryPreferences.sortingMode().key(), newSortingMode) } } if (oldVersion < 82) { prefs.edit { - val sort = prefs.getString(libraryPreferences.librarySortingMode().key(), null) ?: return@edit + val sort = prefs.getString(libraryPreferences.sortingMode().key(), null) ?: return@edit val direction = prefs.getString("library_sorting_ascending", "ASCENDING")!! - putString(libraryPreferences.librarySortingMode().key(), "$sort,$direction") + putString(libraryPreferences.sortingMode().key(), "$sort,$direction") remove("library_sorting_ascending") } } @@ -368,6 +369,12 @@ object Migrations { readerPreferences.longStripSplitWebtoon().set(false) } } + if (oldVersion < 105) { + val pref = libraryPreferences.autoUpdateDeviceRestrictions() + if (pref.isSet() && "battery_not_low" in pref.get()) { + pref.getAndSet { it - "battery_not_low" } + } + } return true } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateJob.kt index a5c4bbf58..6f960edec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateJob.kt @@ -4,6 +4,7 @@ import android.content.Context import android.net.Uri import androidx.core.net.toUri import androidx.work.BackoffPolicy +import androidx.work.Constraints import androidx.work.CoroutineWorker import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingWorkPolicy @@ -76,6 +77,10 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete val backupPreferences = Injekt.get() val interval = prefInterval ?: backupPreferences.backupInterval().get() if (interval > 0) { + val constraints = Constraints( + requiresBatteryNotLow = true, + ) + val request = PeriodicWorkRequestBuilder( interval.toLong(), TimeUnit.HOURS, @@ -84,6 +89,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete ) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10.minutes.toJavaDuration()) .addTag(TAG_AUTO) + .setConstraints(constraints) .setInputData(workDataOf(IS_AUTO_BACKUP_KEY to true)) .build() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt index e2cd12e3b..56ac0b8e0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt @@ -51,7 +51,7 @@ class BackupFileValidator( .distinct() val missingTrackers = trackers .mapNotNull { trackManager.getService(it.toLong()) } - .filter { !it.isLogged } + .filter { !it.isLoggedIn } .map { context.getString(it.nameRes()) } .sorted() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt index f9391b3aa..81dfd7168 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt @@ -5,7 +5,6 @@ import android.content.Context import android.net.Uri import androidx.core.net.toUri import com.hippo.unifile.UniFile -import eu.kanade.core.util.mapNotNullKeys import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.source.Source import kotlinx.coroutines.CancellationException @@ -327,14 +326,16 @@ class DownloadCache( } } + val sourceMap = sources.associate { provider.getSourceDirName(it).lowercase() to it.id } + rootDownloadsDirLock.withLock { val sourceDirs = rootDownloadsDir.dir.listFiles().orEmpty() - .associate { it.name to SourceDirectory(it) } - .mapNotNullKeys { entry -> - sources.find { - provider.getSourceDirName(it).equals(entry.key, ignoreCase = true) - }?.id + .filter { it.isDirectory && !it.name.isNullOrBlank() } + .mapNotNull { dir -> + val sourceId = sourceMap[dir.name!!.lowercase()] + sourceId?.let { it to SourceDirectory(dir) } } + .toMap() rootDownloadsDir.sourceDirs = sourceDirs @@ -342,7 +343,7 @@ class DownloadCache( .map { sourceDir -> async { sourceDir.mangaDirs = sourceDir.dir.listFiles().orEmpty() - .filterNot { it.name.isNullOrBlank() } + .filter { it.isDirectory && !it.name.isNullOrBlank() } .associate { it.name!! to MangaDirectory(it) } sourceDir.mangaDirs.values.forEach { mangaDir -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt index 40bb3a4ee..7b0c2b8f3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt @@ -15,19 +15,14 @@ import androidx.work.WorkQuery import androidx.work.WorkerParameters import androidx.work.workDataOf import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource -import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.copyFrom import eu.kanade.domain.manga.model.toSManga -import eu.kanade.domain.track.model.toDbTrack -import eu.kanade.domain.track.model.toDomainTrack +import eu.kanade.domain.track.interactor.RefreshTracks import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.notification.Notifications -import eu.kanade.tachiyomi.data.track.EnhancedTrackService -import eu.kanade.tachiyomi.data.track.TrackManager -import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.source.UnmeteredSource import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.UpdateStrategy @@ -44,7 +39,6 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.ensureActive import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import logcat.LogPriority @@ -53,13 +47,11 @@ import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.model.Category -import tachiyomi.domain.chapter.interactor.GetChapterByMangaId import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.NoChaptersException import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.library.service.LibraryPreferences -import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_BATTERY_NOT_LOW import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_NETWORK_NOT_METERED import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI @@ -74,8 +66,6 @@ import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.toMangaUpdate import tachiyomi.domain.source.model.SourceNotInstalledException import tachiyomi.domain.source.service.SourceManager -import tachiyomi.domain.track.interactor.GetTracks -import tachiyomi.domain.track.interactor.InsertTrack import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File @@ -93,17 +83,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet private val downloadPreferences: DownloadPreferences = Injekt.get() private val libraryPreferences: LibraryPreferences = Injekt.get() private val downloadManager: DownloadManager = Injekt.get() - private val trackManager: TrackManager = Injekt.get() private val coverCache: CoverCache = Injekt.get() private val getLibraryManga: GetLibraryManga = Injekt.get() private val getManga: GetManga = Injekt.get() private val updateManga: UpdateManga = Injekt.get() - private val getChapterByMangaId: GetChapterByMangaId = Injekt.get() private val getCategories: GetCategories = Injekt.get() private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get() - private val getTracks: GetTracks = Injekt.get() - private val insertTrack: InsertTrack = Injekt.get() - private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get() + private val refreshTracks: RefreshTracks = Injekt.get() private val setFetchInterval: SetFetchInterval = Injekt.get() private val notifier = LibraryUpdateNotifier(context) @@ -113,7 +99,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet override suspend fun doWork(): Result { if (tags.contains(WORK_NAME_AUTO)) { val preferences = Injekt.get() - val restrictions = preferences.libraryUpdateDeviceRestriction().get() + val restrictions = preferences.autoUpdateDeviceRestrictions().get() if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) { return Result.retry() } @@ -134,7 +120,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet // If this is a chapter update, set the last update time to now if (target == Target.CHAPTERS) { - libraryPreferences.libraryUpdateLastTimestamp().set(Date().time) + libraryPreferences.lastUpdatedTimestamp().set(Date().time) } val categoryId = inputData.getLong(KEY_CATEGORY, -1L) @@ -181,14 +167,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val listToUpdate = if (categoryId != -1L) { libraryManga.filter { it.category == categoryId } } else { - val categoriesToUpdate = libraryPreferences.libraryUpdateCategories().get().map { it.toLong() } + val categoriesToUpdate = libraryPreferences.updateCategories().get().map { it.toLong() } val includedManga = if (categoriesToUpdate.isNotEmpty()) { libraryManga.filter { it.category in categoriesToUpdate } } else { libraryManga } - val categoriesToExclude = libraryPreferences.libraryUpdateCategoriesExclude().get().map { it.toLong() } + val categoriesToExclude = libraryPreferences.updateCategoriesExclude().get().map { it.toLong() } val excludedMangaIds = if (categoriesToExclude.isNotEmpty()) { libraryManga.filter { it.category in categoriesToExclude }.map { it.manga.id } } else { @@ -229,7 +215,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val skippedUpdates = CopyOnWriteArrayList>() val failedUpdates = CopyOnWriteArrayList>() val hasDownloads = AtomicBoolean(false) - val restrictions = libraryPreferences.libraryUpdateMangaRestriction().get() + val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get() val fetchWindow = setFetchInterval.getWindow(ZonedDateTime.now()) coroutineScope { @@ -297,8 +283,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet } if (libraryPreferences.autoUpdateTrackers().get()) { - val loggedServices = trackManager.services.filter { it.isLogged } - updateTrackings(manga, loggedServices) + refreshTracks.await(manga.id) } } } @@ -418,49 +403,19 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet private suspend fun updateTrackings() { coroutineScope { var progressCount = 0 - val loggedServices = trackManager.services.filter { it.isLogged } mangaToUpdate.forEach { libraryManga -> - val manga = libraryManga.manga - ensureActive() + val manga = libraryManga.manga notifier.showProgressNotification(listOf(manga), progressCount++, mangaToUpdate.size) - - // Update the tracking details. - updateTrackings(manga, loggedServices) + refreshTracks.await(manga.id) } notifier.cancelProgressNotification() } } - private suspend fun updateTrackings(manga: Manga, loggedServices: List) { - getTracks.await(manga.id) - .map { track -> - supervisorScope { - async { - val service = trackManager.getService(track.syncId) - if (service != null && service in loggedServices) { - try { - val updatedTrack = service.refresh(track.toDbTrack()) - insertTrack.await(updatedTrack.toDomainTrack()!!) - - if (service is EnhancedTrackService) { - val chapters = getChapterByMangaId.await(manga.id) - syncChaptersWithTrackServiceTwoWay.await(chapters, track, service) - } - } catch (e: Throwable) { - // Ignore errors and continue - logcat(LogPriority.ERROR, e) - } - } - } - } - } - .awaitAll() - } - private suspend fun withUpdateNotification( updatingManga: CopyOnWriteArrayList, completed: AtomicInteger, @@ -558,13 +513,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet prefInterval: Int? = null, ) { val preferences = Injekt.get() - val interval = prefInterval ?: preferences.libraryUpdateInterval().get() + val interval = prefInterval ?: preferences.autoUpdateInterval().get() if (interval > 0) { - val restrictions = preferences.libraryUpdateDeviceRestriction().get() + val restrictions = preferences.autoUpdateDeviceRestrictions().get() val constraints = Constraints( requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { NetworkType.UNMETERED } else { NetworkType.CONNECTED }, requiresCharging = DEVICE_CHARGING in restrictions, - requiresBatteryNotLow = DEVICE_BATTERY_NOT_LOW in restrictions, + requiresBatteryNotLow = true, ) val request = PeriodicWorkRequestBuilder( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt index e1abbec83..e668aea35 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt @@ -39,5 +39,5 @@ class TrackManager(context: Context) { fun getService(id: Long) = services.find { it.id == id } - fun hasLoggedServices() = services.any { it.isLogged } + fun hasLoggedServices() = services.any { it.isLoggedIn } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt index d7dd11c94..1a0957964 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt @@ -33,6 +33,7 @@ abstract class TrackService(val id: Long) { val trackPreferences: TrackPreferences by injectLazy() val networkService: NetworkHelper by injectLazy() private val insertTrack: InsertTrack by injectLazy() + private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay by injectLazy() open val client: OkHttpClient get() = networkService.client @@ -89,7 +90,7 @@ abstract class TrackService(val id: Long) { trackPreferences.setTrackCredentials(this, "", "") } - open val isLogged: Boolean + open val isLoggedIn: Boolean get() = getUsername().isNotEmpty() && getPassword().isNotEmpty() @@ -101,6 +102,7 @@ abstract class TrackService(val id: Long) { trackPreferences.setTrackCredentials(this, username, password) } + // TODO: move this to an interactor, and update all trackers based on common data suspend fun registerTracking(item: Track, mangaId: Long) { item.manga_id = mangaId try { @@ -113,6 +115,7 @@ abstract class TrackService(val id: Long) { insertTrack.await(track) + // TODO: merge into SyncChaptersWithTrackServiceTwoWay? // Update chapter progress if newer chapters marked read locally if (hasReadChapters) { val latestLocalReadChapterNumber = allChapters @@ -144,9 +147,7 @@ abstract class TrackService(val id: Long) { } } - if (this is EnhancedTrackService) { - Injekt.get().await(allChapters, track, this@TrackService) - } + syncChaptersWithTrackServiceTwoWay.await(mangaId, track, this@TrackService) } } catch (e: Throwable) { withUIContext { Injekt.get().toast(e.message) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt index a2a5e0fe3..2c1044d88 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt @@ -45,7 +45,6 @@ import tachiyomi.core.util.system.logcat import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.SetMangaCategories import tachiyomi.domain.category.model.Category -import tachiyomi.domain.chapter.interactor.GetChapterByMangaId import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga @@ -72,7 +71,6 @@ class BrowseSourceScreenModel( private val getRemoteManga: GetRemoteManga = Injekt.get(), private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(), private val getCategories: GetCategories = Injekt.get(), - private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val setMangaCategories: SetMangaCategories = Injekt.get(), private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(), private val getManga: GetManga = Injekt.get(), @@ -82,7 +80,7 @@ class BrowseSourceScreenModel( private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(), ) : StateScreenModel(State(Listing.valueOf(listingQuery))) { - private val loggedServices by lazy { Injekt.get().services.filter { it.isLogged } } + private val loggedServices by lazy { Injekt.get().services.filter { it.isLoggedIn } } var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope) @@ -299,8 +297,7 @@ class BrowseSourceScreenModel( (service as TrackService).bind(track) insertTrack.await(track.toDomainTrack()!!) - val chapters = getChapterByMangaId.await(manga.id) - syncChaptersWithTrackServiceTwoWay.await(chapters, track.toDomainTrack()!!, service) + syncChaptersWithTrackServiceTwoWay.await(manga.id, track.toDomainTrack()!!, service) } } catch (e: Exception) { logcat(LogPriority.WARN, e) { "Could not match manga: ${manga.title} with service $service" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/DeepLinkActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkActivity.kt similarity index 84% rename from app/src/main/java/eu/kanade/tachiyomi/ui/main/DeepLinkActivity.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkActivity.kt index 1e381c09a..0635b03bb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/DeepLinkActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkActivity.kt @@ -1,8 +1,9 @@ -package eu.kanade.tachiyomi.ui.main +package eu.kanade.tachiyomi.ui.deeplink import android.app.Activity import android.content.Intent import android.os.Bundle +import eu.kanade.tachiyomi.ui.main.MainActivity class DeepLinkActivity : Activity() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkScreen.kt new file mode 100644 index 000000000..4b7c989dc --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkScreen.kt @@ -0,0 +1,59 @@ +package eu.kanade.tachiyomi.ui.deeplink + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.util.Screen +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen +import eu.kanade.tachiyomi.ui.manga.MangaScreen +import tachiyomi.presentation.core.components.material.Scaffold +import tachiyomi.presentation.core.screens.LoadingScreen + +class DeepLinkScreen( + val query: String = "", +) : Screen() { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + + val screenModel = rememberScreenModel { + DeepLinkScreenModel(query = query) + } + val state by screenModel.state.collectAsState() + Scaffold( + topBar = { scrollBehavior -> + AppBar( + title = stringResource(R.string.action_search_hint), + navigateUp = navigator::pop, + scrollBehavior = scrollBehavior, + ) + }, + ) { contentPadding -> + when (state) { + is DeepLinkScreenModel.State.Loading -> { + LoadingScreen(Modifier.padding(contentPadding)) + } + is DeepLinkScreenModel.State.NoResults -> { + navigator.replace(GlobalSearchScreen(query)) + } + is DeepLinkScreenModel.State.Result -> { + navigator.replace( + MangaScreen( + (state as DeepLinkScreenModel.State.Result).manga.id, + true, + ), + ) + } + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkScreenModel.kt new file mode 100644 index 000000000..4446c28a4 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkScreenModel.kt @@ -0,0 +1,47 @@ +package eu.kanade.tachiyomi.ui.deeplink + +import androidx.compose.runtime.Immutable +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.coroutineScope +import eu.kanade.domain.manga.model.toDomainManga +import eu.kanade.tachiyomi.source.online.ResolvableSource +import kotlinx.coroutines.flow.update +import tachiyomi.core.util.lang.launchIO +import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.source.service.SourceManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class DeepLinkScreenModel( + query: String = "", + private val sourceManager: SourceManager = Injekt.get(), +) : StateScreenModel(State.Loading) { + + init { + coroutineScope.launchIO { + val manga = sourceManager.getCatalogueSources() + .filterIsInstance() + .filter { it.canResolveUri(query) } + .firstNotNullOfOrNull { it.getManga(query)?.toDomainManga(it.id) } + + mutableState.update { + if (manga == null) { + State.NoResults + } else { + State.Result(manga) + } + } + } + } + + sealed interface State { + @Immutable + data object Loading : State + + @Immutable + data object NoResults : State + + @Immutable + data class Result(val manga: Manga) : State + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt index 2441707fe..99b964cae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt @@ -366,7 +366,7 @@ class LibraryScreenModel( * @return map of track id with the filter value */ private fun getTrackingFilterFlow(): Flow> { - val loggedServices = trackManager.services.filter { it.isLogged } + val loggedServices = trackManager.services.filter { it.isLoggedIn } return if (loggedServices.isNotEmpty()) { val prefFlows = loggedServices .map { libraryPreferences.filterTracking(it.id.toInt()).changes() } @@ -519,7 +519,7 @@ class LibraryScreenModel( } fun getDisplayMode(): PreferenceMutableState { - return libraryPreferences.libraryDisplayMode().asState(coroutineScope) + return libraryPreferences.displayMode().asState(coroutineScope) } fun getColumnsPreferenceForCurrentOrientation(isLandscape: Boolean): PreferenceMutableState { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt index a5a21317e..1a9ce4208 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt @@ -26,7 +26,7 @@ class LibrarySettingsScreenModel( ) : ScreenModel { val trackServices - get() = trackManager.services.filter { it.isLogged } + get() = trackManager.services.filter { it.isLoggedIn } fun toggleFilter(preference: (LibraryPreferences) -> Preference) { preference(libraryPreferences).getAndSet { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt index c3a11d034..cde9762d5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt @@ -148,7 +148,7 @@ object LibraryTab : Tab { snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { contentPadding -> when { - state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) + state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) state.searchQuery.isNullOrEmpty() && !state.hasActiveFilters && state.isLibraryEmpty -> { val handler = LocalUriHandler.current EmptyScreen( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index e2516ea7c..868a8cee7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -71,6 +71,7 @@ import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen +import eu.kanade.tachiyomi.ui.deeplink.DeepLinkScreen import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.more.NewUpdateScreen @@ -409,7 +410,7 @@ class MainActivity : BaseActivity() { val query = intent.getStringExtra(SearchManager.QUERY) ?: intent.getStringExtra(Intent.EXTRA_TEXT) if (!query.isNullOrEmpty()) { navigator.popUntilRoot() - navigator.push(GlobalSearchScreen(query)) + navigator.push(DeepLinkScreen(query)) } null } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index da1cd4ccd..c288733a0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -105,7 +105,7 @@ class MangaScreenModel( private val successState: State.Success? get() = state.value as? State.Success - private val loggedServices by lazy { trackManager.services.filter { it.isLogged } } + private val loggedServices by lazy { trackManager.services.filter { it.isLoggedIn } } val manga: Manga? get() = successState?.manga @@ -128,7 +128,7 @@ class MangaScreenModel( val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get())) private val skipFiltered by readerPreferences.skipFiltered().asState(coroutineScope) - val isUpdateIntervalEnabled = LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.libraryUpdateMangaRestriction().get() + val isUpdateIntervalEnabled = LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateMangaRestrictions().get() private val selectedPositions: Array = arrayOf(-1, -1) // first and last selected index in list private val selectedChapterIds: HashSet = HashSet() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt index 815c8d454..91fea703b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt @@ -71,7 +71,6 @@ import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.manga.interactor.GetManga -import tachiyomi.domain.manga.interactor.GetMangaWithChapters import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.track.interactor.DeleteTrack import tachiyomi.domain.track.interactor.GetTracks @@ -218,8 +217,7 @@ data class TrackInfoDialogHomeScreen( private suspend fun refreshTrackers() { val insertTrack = Injekt.get() - val getMangaWithChapters = Injekt.get() - val syncTwoWayService = Injekt.get() + val syncChaptersWithTrackServiceTwoWay = Injekt.get() val context = Injekt.get() try { @@ -229,11 +227,7 @@ data class TrackInfoDialogHomeScreen( val track = trackItem.track ?: continue val domainTrack = trackItem.service.refresh(track.toDbTrack()).toDomainTrack() ?: continue insertTrack.await(domainTrack) - - if (trackItem.service is EnhancedTrackService) { - val allChapters = getMangaWithChapters.awaitChapters(mangaId) - syncTwoWayService.await(allChapters, domainTrack, trackItem.service) - } + syncChaptersWithTrackServiceTwoWay.await(mangaId, domainTrack, trackItem.service) } catch (e: Exception) { logcat( LogPriority.ERROR, @@ -257,7 +251,7 @@ data class TrackInfoDialogHomeScreen( } private fun List.mapToTrackItem(): List { - val loggedServices = Injekt.get().services.filter { it.isLogged } + val loggedServices = Injekt.get().services.filter { it.isLoggedIn } val source = Injekt.get().getOrStub(sourceId) return loggedServices // Map to TrackItem diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt index 68cc4b324..873bd2723 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt @@ -36,7 +36,7 @@ class StatsScreenModel( private val trackManager: TrackManager = Injekt.get(), ) : StateScreenModel(StatsScreenState.Loading) { - private val loggedServices by lazy { trackManager.services.fastFilter { it.isLogged } } + private val loggedServices by lazy { trackManager.services.fastFilter { it.isLoggedIn } } init { coroutineScope.launchIO { @@ -87,14 +87,14 @@ class StatsScreenModel( } private fun getGlobalUpdateItemCount(libraryManga: List): Int { - val includedCategories = preferences.libraryUpdateCategories().get().map { it.toLong() } + val includedCategories = preferences.updateCategories().get().map { it.toLong() } val includedManga = if (includedCategories.isNotEmpty()) { libraryManga.filter { it.category in includedCategories } } else { libraryManga } - val excludedCategories = preferences.libraryUpdateCategoriesExclude().get().map { it.toLong() } + val excludedCategories = preferences.updateCategoriesExclude().get().map { it.toLong() } val excludedMangaIds = if (excludedCategories.isNotEmpty()) { libraryManga.fastMapNotNull { manga -> manga.id.takeIf { manga.category in excludedCategories } @@ -103,7 +103,7 @@ class StatsScreenModel( emptyList() } - val updateRestrictions = preferences.libraryUpdateMangaRestriction().get() + val updateRestrictions = preferences.autoUpdateMangaRestrictions().get() return includedManga .fastFilterNot { it.manga.id in excludedMangaIds } .fastDistinctBy { it.manga.id } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt index d05d54fe8..6459d6525 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt @@ -64,7 +64,7 @@ class UpdatesScreenModel( private val _events: Channel = Channel(Int.MAX_VALUE) val events: Flow = _events.receiveAsFlow() - val lastUpdated by libraryPreferences.libraryUpdateLastTimestamp().asState(coroutineScope) + val lastUpdated by libraryPreferences.lastUpdatedTimestamp().asState(coroutineScope) // First and last selected index in list private val selectedPositions: Array = arrayOf(-1, -1) diff --git a/data/src/main/java/tachiyomi/data/release/GithubRelease.kt b/data/src/main/java/tachiyomi/data/release/GithubRelease.kt index 3677dc122..94394cef0 100644 --- a/data/src/main/java/tachiyomi/data/release/GithubRelease.kt +++ b/data/src/main/java/tachiyomi/data/release/GithubRelease.kt @@ -21,10 +21,25 @@ data class GithubRelease( @Serializable data class GitHubAssets(@SerialName("browser_download_url") val downloadLink: String) +/** + * Regular expression that matches a mention to a valid GitHub username, like it's + * done in GitHub Flavored Markdown. It follows these constraints: + * + * - Alphanumeric with single hyphens (no consecutive hyphens) + * - Cannot begin or end with a hyphen + * - Max length of 39 characters + * + * Reference: https://stackoverflow.com/a/30281147 + */ +val gitHubUsernameMentionRegex = + """\B@([a-z0-9](?:-(?=[a-z0-9])|[a-z0-9]){0,38}(?<=[a-z0-9]))""".toRegex(RegexOption.IGNORE_CASE) + val releaseMapper: (GithubRelease) -> Release = { Release( it.version, - it.info, + it.info.replace(gitHubUsernameMentionRegex) { mention -> + "[${mention.value}](https://github.com/${mention.value.substring(1)})" + }, it.releaseLink, it.assets.map(GitHubAssets::downloadLink), ) diff --git a/domain/src/main/java/tachiyomi/domain/category/interactor/CreateCategoryWithName.kt b/domain/src/main/java/tachiyomi/domain/category/interactor/CreateCategoryWithName.kt index f8b4ba802..b9528950d 100644 --- a/domain/src/main/java/tachiyomi/domain/category/interactor/CreateCategoryWithName.kt +++ b/domain/src/main/java/tachiyomi/domain/category/interactor/CreateCategoryWithName.kt @@ -14,7 +14,7 @@ class CreateCategoryWithName( private val initialFlags: Long get() { - val sort = preferences.librarySortingMode().get() + val sort = preferences.sortingMode().get() return sort.type.flag or sort.direction.flag } diff --git a/domain/src/main/java/tachiyomi/domain/category/interactor/ResetCategoryFlags.kt b/domain/src/main/java/tachiyomi/domain/category/interactor/ResetCategoryFlags.kt index a8014186c..4032206c3 100644 --- a/domain/src/main/java/tachiyomi/domain/category/interactor/ResetCategoryFlags.kt +++ b/domain/src/main/java/tachiyomi/domain/category/interactor/ResetCategoryFlags.kt @@ -10,7 +10,7 @@ class ResetCategoryFlags( ) { suspend fun await() { - val sort = preferences.librarySortingMode().get() + val sort = preferences.sortingMode().get() categoryRepository.updateAllFlags(sort.type + sort.direction) } } diff --git a/domain/src/main/java/tachiyomi/domain/category/interactor/SetDisplayMode.kt b/domain/src/main/java/tachiyomi/domain/category/interactor/SetDisplayMode.kt index 9ef5ca942..32e23148f 100644 --- a/domain/src/main/java/tachiyomi/domain/category/interactor/SetDisplayMode.kt +++ b/domain/src/main/java/tachiyomi/domain/category/interactor/SetDisplayMode.kt @@ -8,6 +8,6 @@ class SetDisplayMode( ) { fun await(display: LibraryDisplayMode) { - preferences.libraryDisplayMode().set(display) + preferences.displayMode().set(display) } } diff --git a/domain/src/main/java/tachiyomi/domain/category/interactor/SetSortModeForCategory.kt b/domain/src/main/java/tachiyomi/domain/category/interactor/SetSortModeForCategory.kt index bca8ca062..28c999e9f 100644 --- a/domain/src/main/java/tachiyomi/domain/category/interactor/SetSortModeForCategory.kt +++ b/domain/src/main/java/tachiyomi/domain/category/interactor/SetSortModeForCategory.kt @@ -23,7 +23,7 @@ class SetSortModeForCategory( ), ) } else { - preferences.librarySortingMode().set(LibrarySort(type, direction)) + preferences.sortingMode().set(LibrarySort(type, direction)) categoryRepository.updateAllFlags(flags) } } diff --git a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt index b120dcaf6..e66c705d9 100644 --- a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt @@ -11,24 +11,24 @@ class LibraryPreferences( private val preferenceStore: PreferenceStore, ) { - fun libraryDisplayMode() = preferenceStore.getObject("pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize) + fun displayMode() = preferenceStore.getObject("pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize) - fun librarySortingMode() = preferenceStore.getObject("library_sorting_mode", LibrarySort.default, LibrarySort.Serializer::serialize, LibrarySort.Serializer::deserialize) + fun sortingMode() = preferenceStore.getObject("library_sorting_mode", LibrarySort.default, LibrarySort.Serializer::serialize, LibrarySort.Serializer::deserialize) fun portraitColumns() = preferenceStore.getInt("pref_library_columns_portrait_key", 0) fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0) - fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0) - fun libraryUpdateLastTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L) + fun lastUpdatedTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L) + fun autoUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0) - fun libraryUpdateDeviceRestriction() = preferenceStore.getStringSet( + fun autoUpdateDeviceRestrictions() = preferenceStore.getStringSet( "library_update_restriction", setOf( DEVICE_ONLY_ON_WIFI, ), ) - fun libraryUpdateMangaRestriction() = preferenceStore.getStringSet( + fun autoUpdateMangaRestrictions() = preferenceStore.getStringSet( "library_update_manga_restriction", setOf( MANGA_HAS_UNREAD, @@ -95,9 +95,9 @@ class LibraryPreferences( fun categorizedDisplaySettings() = preferenceStore.getBoolean("categorized_display", false) - fun libraryUpdateCategories() = preferenceStore.getStringSet("library_update_categories", emptySet()) + fun updateCategories() = preferenceStore.getStringSet("library_update_categories", emptySet()) - fun libraryUpdateCategoriesExclude() = preferenceStore.getStringSet("library_update_categories_exclude", emptySet()) + fun updateCategoriesExclude() = preferenceStore.getStringSet("library_update_categories_exclude", emptySet()) // endregion @@ -148,7 +148,6 @@ class LibraryPreferences( const val DEVICE_ONLY_ON_WIFI = "wifi" const val DEVICE_NETWORK_NOT_METERED = "network_not_metered" const val DEVICE_CHARGING = "ac" - const val DEVICE_BATTERY_NOT_LOW = "battery_not_low" const val MANGA_NON_COMPLETED = "manga_ongoing" const val MANGA_HAS_UNREAD = "manga_fully_read" diff --git a/domain/src/main/java/tachiyomi/domain/source/model/StubSource.kt b/domain/src/main/java/tachiyomi/domain/source/model/StubSource.kt index 3fbd02101..38d38ef57 100644 --- a/domain/src/main/java/tachiyomi/domain/source/model/StubSource.kt +++ b/domain/src/main/java/tachiyomi/domain/source/model/StubSource.kt @@ -6,20 +6,19 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import rx.Observable -@Suppress("OverridingDeprecatedMember") class StubSource( override val id: Long, override val lang: String, override val name: String, ) : Source { - val isInvalid: Boolean = name.isBlank() || lang.isBlank() + private val isInvalid: Boolean = name.isBlank() || lang.isBlank() override suspend fun getMangaDetails(manga: SManga): SManga { throw SourceNotInstalledException() } - @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getMangaDetails")) + @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getMangaDetails")) override fun fetchMangaDetails(manga: SManga): Observable { return Observable.error(SourceNotInstalledException()) } @@ -28,7 +27,7 @@ class StubSource( throw SourceNotInstalledException() } - @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getChapterList")) + @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getChapterList")) override fun fetchChapterList(manga: SManga): Observable> { return Observable.error(SourceNotInstalledException()) } @@ -37,7 +36,7 @@ class StubSource( throw SourceNotInstalledException() } - @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getPageList")) + @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPageList")) override fun fetchPageList(chapter: SChapter): Observable> { return Observable.error(SourceNotInstalledException()) } diff --git a/gradle.properties b/gradle.properties index 39f9c9b71..8102dd176 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,4 +25,4 @@ kotlin.mpp.androidSourceSetLayoutVersion=2 android.useAndroidX=true android.defaults.buildfeatures.buildconfig=true android.nonTransitiveRClass=false -android.nonFinalResIds=false \ No newline at end of file +#android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 9cee3f113..9fe6455c3 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -1,12 +1,12 @@ [versions] -agp_version = "8.1.0" +agp_version = "8.1.1" lifecycle_version = "2.6.1" paging_version = "3.2.0" [libraries] gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" } -annotation = "androidx.annotation:annotation:1.7.0-beta01" +annotation = "androidx.annotation:annotation:1.7.0-rc01" appcompat = "androidx.appcompat:appcompat:1.6.1" biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05" constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4" @@ -27,7 +27,7 @@ guava = "com.google.guava:guava:32.1.2-android" paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" } paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" } -benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.0-beta03" +benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.0-beta04" test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha01" test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha01" test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0-alpha04" diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index d1f1250f5..e21536263 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,7 +1,7 @@ [versions] -compiler = "1.5.1" +compiler = "1.5.2" compose-bom = "2023.09.00-alpha02" -accompanist = "0.33.0-alpha" +accompanist = "0.33.1-alpha" [libraries] activity = "androidx.activity:activity-compose:1.7.2" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9f0dcc91d..0cd15c4fa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,6 @@ desugar = "com.android.tools:desugar_jdk_libs:2.0.3" android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2" google-services-gradle = "com.google.gms:google-services:4.3.15" -rxandroid = "io.reactivex:rxandroid:1.2.1" rxjava = "io.reactivex:rxjava:1.3.8" flowreactivenetwork = "ru.beryukhov:flowreactivenetwork:1.0.4" @@ -97,7 +96,6 @@ google-api-client-oauth = "com.google.oauth-client:google-oauth-client:1.34.1" kotlinter = "org.jmailen.gradle:kotlinter-gradle:3.13.0" [bundles] -reactivex = ["rxandroid", "rxjava"] okhttp = ["okhttp-core", "okhttp-logging", "okhttp-dnsoverhttps"] js-engine = ["quickjs-android"] sqlite = ["sqlite-framework", "sqlite-ktx", "sqlite-android"] diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index fbf138eb5..9fb1151aa 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -254,7 +254,6 @@ Only on Wi-Fi Only on unmetered network When charging - When battery not low Restrictions: %s Skip updating entries @@ -646,8 +645,6 @@ No more results No results found - - Check website in WebView Licensed - No chapters to show Local source Other @@ -987,4 +984,10 @@ See your recently updated library entries Widget not available when app lock is enabled You are about to remove \"%s\" from your library + + + + HTTP %d, check website in WebView + No Internet connection + Couldn\'t reach %s diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt index 5f5dd57ee..15747af98 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt @@ -24,13 +24,44 @@ interface Source { val lang: String get() = "" + /** + * Get the updated details for a manga. + * + * @param manga the manga to update. + */ + @Suppress("DEPRECATION") + suspend fun getMangaDetails(manga: SManga): SManga { + return fetchMangaDetails(manga).awaitSingle() + } + + /** + * Get all the available chapters for a manga. + * + * @param manga the manga to update. + */ + @Suppress("DEPRECATION") + suspend fun getChapterList(manga: SManga): List { + return fetchChapterList(manga).awaitSingle() + } + + /** + * Get the list of pages a chapter has. Pages should be returned + * in the expected order; the index is ignored. + * + * @param chapter the chapter. + */ + @Suppress("DEPRECATION") + suspend fun getPageList(chapter: SChapter): List { + return fetchPageList(chapter).awaitSingle() + } + /** * Returns an observable with the updated details for a manga. * * @param manga the manga to update. */ @Deprecated( - "Use the 1.x API instead", + "Use the non-RxJava API instead", ReplaceWith("getMangaDetails"), ) fun fetchMangaDetails(manga: SManga): Observable = throw IllegalStateException("Not used") @@ -41,7 +72,7 @@ interface Source { * @param manga the manga to update. */ @Deprecated( - "Use the 1.x API instead", + "Use the non-RxJava API instead", ReplaceWith("getChapterList"), ) fun fetchChapterList(manga: SManga): Observable> = throw IllegalStateException("Not used") @@ -53,33 +84,8 @@ interface Source { * @param chapter the chapter. */ @Deprecated( - "Use the 1.x API instead", + "Use the non-RxJava API instead", ReplaceWith("getPageList"), ) fun fetchPageList(chapter: SChapter): Observable> = Observable.empty() - - /** - * [1.x API] Get the updated details for a manga. - */ - @Suppress("DEPRECATION") - suspend fun getMangaDetails(manga: SManga): SManga { - return fetchMangaDetails(manga).awaitSingle() - } - - /** - * [1.x API] Get all the available chapters for a manga. - */ - @Suppress("DEPRECATION") - suspend fun getChapterList(manga: SManga): List { - return fetchChapterList(manga).awaitSingle() - } - - /** - * [1.x API] Get the list of pages a chapter has. Pages should be returned - * in the expected order; the index is ignored. - */ - @Suppress("DEPRECATION") - suspend fun getPageList(chapter: SChapter): List { - return fetchPageList(chapter).awaitSingle() - } } diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/ResolvableSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/ResolvableSource.kt new file mode 100644 index 000000000..6a00c2e55 --- /dev/null +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/ResolvableSource.kt @@ -0,0 +1,26 @@ +package eu.kanade.tachiyomi.source.online + +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.model.SManga + +/** + * A source that may handle opening an SManga for a given URI. + * + * @since extensions-lib 1.5 + */ +interface ResolvableSource : Source { + + /** + * Whether this source may potentially handle the given URI. + * + * @since extensions-lib 1.5 + */ + fun canResolveUri(uri: String): Boolean + + /** + * Called if canHandleUri is true. Returns the corresponding SManga, if possible. + * + * @since extensions-lib 1.5 + */ + suspend fun getManga(uri: String): SManga? +}