mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-15 15:02:49 +01:00
Merge branch 'master' into sync-part-final
This commit is contained in:
commit
d2290107d3
@ -23,7 +23,7 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi"
|
applicationId = "eu.kanade.tachiyomi"
|
||||||
|
|
||||||
versionCode = 104
|
versionCode = 105
|
||||||
versionName = "0.14.6"
|
versionName = "0.14.6"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
@ -194,7 +194,7 @@ dependencies {
|
|||||||
implementation(androidx.bundles.workmanager)
|
implementation(androidx.bundles.workmanager)
|
||||||
|
|
||||||
// RxJava
|
// RxJava
|
||||||
implementation(libs.bundles.reactivex)
|
implementation(libs.rxjava)
|
||||||
implementation(libs.flowreactivenetwork)
|
implementation(libs.flowreactivenetwork)
|
||||||
|
|
||||||
// Networking
|
// Networking
|
||||||
|
2
app/proguard-rules.pro
vendored
2
app/proguard-rules.pro
vendored
@ -11,8 +11,8 @@
|
|||||||
-keep,allowoptimization class kotlin.time.** { public protected *; }
|
-keep,allowoptimization class kotlin.time.** { public protected *; }
|
||||||
-keep,allowoptimization class okhttp3.** { public protected *; }
|
-keep,allowoptimization class okhttp3.** { public protected *; }
|
||||||
-keep,allowoptimization class okio.** { public protected *; }
|
-keep,allowoptimization class okio.** { public protected *; }
|
||||||
-keep,allowoptimization class rx.** { public protected *; }
|
|
||||||
-keep,allowoptimization class org.jsoup.** { 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 app.cash.quickjs.** { public protected *; }
|
||||||
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
||||||
|
|
||||||
|
@ -65,10 +65,10 @@
|
|||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.DeepLinkActivity"
|
android:name=".ui.deeplink.DeepLinkActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
android:theme="@android:style/Theme.NoDisplay"
|
||||||
android:label="@string/action_global_search"
|
android:label="@string/action_search"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.core.util
|
package eu.kanade.core.util
|
||||||
|
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
@ -20,15 +19,6 @@ fun <T : R, R : Any> List<T>.insertSeparators(
|
|||||||
return newList
|
return newList
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new map containing only the key entries of [transform] that are not null.
|
|
||||||
*/
|
|
||||||
inline fun <K, V, R> Map<out K, V>.mapNotNullKeys(transform: (Map.Entry<K?, V>) -> R?): ConcurrentHashMap<R, V> {
|
|
||||||
val mutableMap = ConcurrentHashMap<R, V>()
|
|
||||||
forEach { element -> transform(element)?.let { mutableMap[it] = element.value } }
|
|
||||||
return mutableMap
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
||||||
if (shouldAdd) {
|
if (shouldAdd) {
|
||||||
add(value)
|
add(value)
|
||||||
|
@ -16,6 +16,7 @@ import eu.kanade.domain.source.interactor.SetMigrateSorting
|
|||||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||||
|
import eu.kanade.domain.track.interactor.RefreshTracks
|
||||||
import eu.kanade.domain.track.interactor.TrackChapter
|
import eu.kanade.domain.track.interactor.TrackChapter
|
||||||
import tachiyomi.data.category.CategoryRepositoryImpl
|
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||||
@ -113,6 +114,7 @@ class DomainModule : InjektModule {
|
|||||||
|
|
||||||
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
||||||
addFactory { TrackChapter(get(), get(), get(), get()) }
|
addFactory { TrackChapter(get(), get(), get(), get()) }
|
||||||
|
addFactory { RefreshTracks(get(), get(), get(), get()) }
|
||||||
addFactory { DeleteTrack(get()) }
|
addFactory { DeleteTrack(get()) }
|
||||||
addFactory { GetTracksPerManga(get()) }
|
addFactory { GetTracksPerManga(get()) }
|
||||||
addFactory { GetTracks(get()) }
|
addFactory { GetTracks(get()) }
|
||||||
@ -125,7 +127,7 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||||
addFactory { ShouldUpdateDbChapter() }
|
addFactory { ShouldUpdateDbChapter() }
|
||||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get()) }
|
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get()) }
|
||||||
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
|
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||||
addFactory { GetHistory(get()) }
|
addFactory { GetHistory(get()) }
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
|
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
|
||||||
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
|
||||||
import tachiyomi.domain.chapter.model.toChapterUpdate
|
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
import tachiyomi.domain.track.model.Track
|
import tachiyomi.domain.track.model.Track
|
||||||
@ -13,14 +14,22 @@ import tachiyomi.domain.track.model.Track
|
|||||||
class SyncChaptersWithTrackServiceTwoWay(
|
class SyncChaptersWithTrackServiceTwoWay(
|
||||||
private val updateChapter: UpdateChapter,
|
private val updateChapter: UpdateChapter,
|
||||||
private val insertTrack: InsertTrack,
|
private val insertTrack: InsertTrack,
|
||||||
|
private val getChapterByMangaId: GetChapterByMangaId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(
|
suspend fun await(
|
||||||
chapters: List<Chapter>,
|
mangaId: Long,
|
||||||
remoteTrack: Track,
|
remoteTrack: Track,
|
||||||
service: TrackService,
|
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
|
val chapterUpdates = sortedChapters
|
||||||
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
|
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
|
||||||
.map { it.copy(read = true).toChapterUpdate() }
|
.map { it.copy(read = true).toChapterUpdate() }
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,14 +24,15 @@ class TrackChapter(
|
|||||||
suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) = coroutineScope {
|
suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) = coroutineScope {
|
||||||
launchNonCancellable {
|
launchNonCancellable {
|
||||||
val tracks = getTracks.await(mangaId)
|
val tracks = getTracks.await(mangaId)
|
||||||
|
|
||||||
if (tracks.isEmpty()) return@launchNonCancellable
|
if (tracks.isEmpty()) return@launchNonCancellable
|
||||||
|
|
||||||
tracks.mapNotNull { track ->
|
tracks.mapNotNull { track ->
|
||||||
val service = trackManager.getService(track.syncId)
|
val service = trackManager.getService(track.syncId)
|
||||||
if (service != null && service.isLogged && chapterNumber > track.lastChapterRead) {
|
if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) {
|
||||||
val updatedTrack = track.copy(lastChapterRead = chapterNumber)
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
|
||||||
|
val updatedTrack = track.copy(lastChapterRead = chapterNumber)
|
||||||
async {
|
async {
|
||||||
runCatching {
|
runCatching {
|
||||||
try {
|
try {
|
||||||
@ -44,13 +45,10 @@ class TrackChapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.awaitAll()
|
.awaitAll()
|
||||||
.mapNotNull { it.exceptionOrNull() }
|
.mapNotNull { it.exceptionOrNull() }
|
||||||
.forEach { logcat(LogPriority.INFO, it) }
|
.forEach { logcat(LogPriority.WARN, it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
|
|||||||
.forEach { track ->
|
.forEach { track ->
|
||||||
try {
|
try {
|
||||||
val service = trackManager.getService(track.syncId)
|
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}" }
|
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" }
|
||||||
service.update(track.toDbTrack(), true)
|
service.update(track.toDbTrack(), true)
|
||||||
insertTrack.await(track)
|
insertTrack.await(track)
|
||||||
|
@ -76,7 +76,7 @@ fun ExtensionScreen(
|
|||||||
enabled = !state.isLoading,
|
enabled = !state.isLoading,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> {
|
state.isEmpty -> {
|
||||||
val msg = if (!searchQuery.isNullOrEmpty()) {
|
val msg = if (!searchQuery.isNullOrEmpty()) {
|
||||||
R.string.no_results_found
|
R.string.no_results_found
|
||||||
|
@ -51,7 +51,7 @@ fun MigrateSourceScreen(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> EmptyScreen(
|
state.isEmpty -> EmptyScreen(
|
||||||
textResource = R.string.information_empty_library,
|
textResource = R.string.information_empty_library,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
@ -47,7 +47,7 @@ fun SourcesScreen(
|
|||||||
onLongClickItem: (Source) -> Unit,
|
onLongClickItem: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> EmptyScreen(
|
state.isEmpty -> EmptyScreen(
|
||||||
textResource = R.string.source_empty_screen,
|
textResource = R.string.source_empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
@ -65,7 +65,7 @@ fun HistoryScreen(
|
|||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
state.list.let {
|
state.list.let {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
LoadingScreen(modifier = Modifier.padding(contentPadding))
|
LoadingScreen(Modifier.padding(contentPadding))
|
||||||
} else if (it.isEmpty()) {
|
} else if (it.isEmpty()) {
|
||||||
val msg = if (!state.searchQuery.isNullOrEmpty()) {
|
val msg = if (!state.searchQuery.isNullOrEmpty()) {
|
||||||
R.string.no_results_found
|
R.string.no_results_found
|
||||||
|
@ -180,7 +180,7 @@ private val displayModes = listOf(
|
|||||||
private fun ColumnScope.DisplayPage(
|
private fun ColumnScope.DisplayPage(
|
||||||
screenModel: LibrarySettingsScreenModel,
|
screenModel: LibrarySettingsScreenModel,
|
||||||
) {
|
) {
|
||||||
val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState()
|
val displayMode by screenModel.libraryPreferences.displayMode().collectAsState()
|
||||||
SettingsChipRow(R.string.action_display_mode) {
|
SettingsChipRow(R.string.action_display_mode) {
|
||||||
displayModes.map { (titleRes, mode) ->
|
displayModes.map { (titleRes, mode) ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
|
@ -164,7 +164,7 @@ internal fun PreferenceItem(
|
|||||||
TrackingPreferenceWidget(
|
TrackingPreferenceWidget(
|
||||||
service = this,
|
service = this,
|
||||||
checked = uName.isNotEmpty(),
|
checked = uName.isNotEmpty(),
|
||||||
onClick = { if (isLogged) item.logout() else item.login() },
|
onClick = { if (isLoggedIn) item.logout() else item.login() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ import tachiyomi.domain.category.interactor.GetCategories
|
|||||||
import tachiyomi.domain.category.interactor.ResetCategoryFlags
|
import tachiyomi.domain.category.interactor.ResetCategoryFlags
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
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_CHARGING
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_NETWORK_NOT_METERED
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_NETWORK_NOT_METERED
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
|
||||||
@ -123,14 +122,14 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
): Preference.PreferenceGroup {
|
): Preference.PreferenceGroup {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val libraryUpdateIntervalPref = libraryPreferences.libraryUpdateInterval()
|
val autoUpdateIntervalPref = libraryPreferences.autoUpdateInterval()
|
||||||
val libraryUpdateCategoriesPref = libraryPreferences.libraryUpdateCategories()
|
val autoUpdateCategoriesPref = libraryPreferences.updateCategories()
|
||||||
val libraryUpdateCategoriesExcludePref = libraryPreferences.libraryUpdateCategoriesExclude()
|
val autoUpdateCategoriesExcludePref = libraryPreferences.updateCategoriesExclude()
|
||||||
|
|
||||||
val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState()
|
val autoUpdateInterval by autoUpdateIntervalPref.collectAsState()
|
||||||
|
|
||||||
val included by libraryUpdateCategoriesPref.collectAsState()
|
val included by autoUpdateCategoriesPref.collectAsState()
|
||||||
val excluded by libraryUpdateCategoriesExcludePref.collectAsState()
|
val excluded by autoUpdateCategoriesExcludePref.collectAsState()
|
||||||
var showCategoriesDialog by rememberSaveable { mutableStateOf(false) }
|
var showCategoriesDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
if (showCategoriesDialog) {
|
if (showCategoriesDialog) {
|
||||||
TriStateListDialog(
|
TriStateListDialog(
|
||||||
@ -142,8 +141,8 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
itemLabel = { it.visualName },
|
itemLabel = { it.visualName },
|
||||||
onDismissRequest = { showCategoriesDialog = false },
|
onDismissRequest = { showCategoriesDialog = false },
|
||||||
onValueChanged = { newIncluded, newExcluded ->
|
onValueChanged = { newIncluded, newExcluded ->
|
||||||
libraryUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
|
autoUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
|
||||||
libraryUpdateCategoriesExcludePref.set(newExcluded.map { it.id.toString() }.toSet())
|
autoUpdateCategoriesExcludePref.set(newExcluded.map { it.id.toString() }.toSet())
|
||||||
showCategoriesDialog = false
|
showCategoriesDialog = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -153,7 +152,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
title = stringResource(R.string.pref_category_library_update),
|
title = stringResource(R.string.pref_category_library_update),
|
||||||
preferenceItems = listOf(
|
preferenceItems = listOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = libraryUpdateIntervalPref,
|
pref = autoUpdateIntervalPref,
|
||||||
title = stringResource(R.string.pref_library_update_interval),
|
title = stringResource(R.string.pref_library_update_interval),
|
||||||
entries = mapOf(
|
entries = mapOf(
|
||||||
0 to stringResource(R.string.update_never),
|
0 to stringResource(R.string.update_never),
|
||||||
@ -169,15 +168,14 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.MultiSelectListPreference(
|
Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
pref = libraryPreferences.libraryUpdateDeviceRestriction(),
|
pref = libraryPreferences.autoUpdateDeviceRestrictions(),
|
||||||
enabled = libraryUpdateInterval > 0,
|
enabled = autoUpdateInterval > 0,
|
||||||
title = stringResource(R.string.pref_library_update_restriction),
|
title = stringResource(R.string.pref_library_update_restriction),
|
||||||
subtitle = stringResource(R.string.restrictions),
|
subtitle = stringResource(R.string.restrictions),
|
||||||
entries = mapOf(
|
entries = mapOf(
|
||||||
DEVICE_ONLY_ON_WIFI to stringResource(R.string.connected_to_wifi),
|
DEVICE_ONLY_ON_WIFI to stringResource(R.string.connected_to_wifi),
|
||||||
DEVICE_NETWORK_NOT_METERED to stringResource(R.string.network_not_metered),
|
DEVICE_NETWORK_NOT_METERED to stringResource(R.string.network_not_metered),
|
||||||
DEVICE_CHARGING to stringResource(R.string.charging),
|
DEVICE_CHARGING to stringResource(R.string.charging),
|
||||||
DEVICE_BATTERY_NOT_LOW to stringResource(R.string.battery_not_low),
|
|
||||||
),
|
),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
// Post to event looper to allow the preference to be updated.
|
// 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),
|
subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.MultiSelectListPreference(
|
Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
pref = libraryPreferences.libraryUpdateMangaRestriction(),
|
pref = libraryPreferences.autoUpdateMangaRestrictions(),
|
||||||
title = stringResource(R.string.pref_library_update_manga_restriction),
|
title = stringResource(R.string.pref_library_update_manga_restriction),
|
||||||
entries = mapOf(
|
entries = mapOf(
|
||||||
MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
|
MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
|
||||||
|
@ -81,7 +81,7 @@ fun UpdateScreen(
|
|||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.items.isEmpty() -> EmptyScreen(
|
state.items.isEmpty() -> EmptyScreen(
|
||||||
textResource = R.string.information_no_recent,
|
textResource = R.string.information_no_recent,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
@ -4,16 +4,26 @@ import android.content.Context
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.network.HttpException
|
import eu.kanade.tachiyomi.network.HttpException
|
||||||
import eu.kanade.tachiyomi.source.online.LicensedMangaChaptersException
|
import eu.kanade.tachiyomi.source.online.LicensedMangaChaptersException
|
||||||
|
import eu.kanade.tachiyomi.util.system.isOnline
|
||||||
import tachiyomi.data.source.NoResultsException
|
import tachiyomi.data.source.NoResultsException
|
||||||
import tachiyomi.domain.source.model.SourceNotInstalledException
|
import tachiyomi.domain.source.model.SourceNotInstalledException
|
||||||
|
import java.net.UnknownHostException
|
||||||
|
|
||||||
context(Context)
|
context(Context)
|
||||||
val Throwable.formattedMessage: String
|
val Throwable.formattedMessage: String
|
||||||
get() {
|
get() {
|
||||||
when (this) {
|
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 NoResultsException -> return getString(R.string.no_results_found)
|
||||||
is SourceNotInstalledException -> return getString(R.string.loader_not_implemented_error)
|
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)
|
is LicensedMangaChaptersException -> return getString(R.string.licensed_manga_chapters_error)
|
||||||
}
|
}
|
||||||
return when (val className = this::class.simpleName) {
|
return when (val className = this::class.simpleName) {
|
||||||
|
@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.util.system.toast
|
|||||||
import eu.kanade.tachiyomi.util.system.workManager
|
import eu.kanade.tachiyomi.util.system.workManager
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.preference.TriState
|
import tachiyomi.core.preference.TriState
|
||||||
|
import tachiyomi.core.preference.getAndSet
|
||||||
import tachiyomi.core.preference.getEnum
|
import tachiyomi.core.preference.getEnum
|
||||||
import tachiyomi.core.preference.minusAssign
|
import tachiyomi.core.preference.minusAssign
|
||||||
import tachiyomi.core.preference.plusAssign
|
import tachiyomi.core.preference.plusAssign
|
||||||
@ -101,11 +102,11 @@ object Migrations {
|
|||||||
}
|
}
|
||||||
if (oldVersion < 44) {
|
if (oldVersion < 44) {
|
||||||
// Reset sorting preference if using removed sort by source
|
// 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
|
if (oldSortingMode == 5) { // SOURCE = 5
|
||||||
prefs.edit {
|
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
|
// Force MAL log out due to login flow change
|
||||||
// v52: switched from scraping to WebView
|
// v52: switched from scraping to WebView
|
||||||
// v53: switched from WebView to OAuth
|
// v53: switched from WebView to OAuth
|
||||||
if (trackManager.myAnimeList.isLogged) {
|
if (trackManager.myAnimeList.isLoggedIn) {
|
||||||
trackManager.myAnimeList.logout()
|
trackManager.myAnimeList.logout()
|
||||||
context.toast(R.string.myanimelist_relogin)
|
context.toast(R.string.myanimelist_relogin)
|
||||||
}
|
}
|
||||||
@ -180,14 +181,14 @@ object Migrations {
|
|||||||
}
|
}
|
||||||
if (oldVersion < 61) {
|
if (oldVersion < 61) {
|
||||||
// Handle removed every 1 or 2 hour library updates
|
// Handle removed every 1 or 2 hour library updates
|
||||||
val updateInterval = libraryPreferences.libraryUpdateInterval().get()
|
val updateInterval = libraryPreferences.autoUpdateInterval().get()
|
||||||
if (updateInterval == 1 || updateInterval == 2) {
|
if (updateInterval == 1 || updateInterval == 2) {
|
||||||
libraryPreferences.libraryUpdateInterval().set(3)
|
libraryPreferences.autoUpdateInterval().set(3)
|
||||||
LibraryUpdateJob.setupTask(context, 3)
|
LibraryUpdateJob.setupTask(context, 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion < 64) {
|
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 oldSortingDirection = prefs.getBoolean("library_sorting_ascending", true)
|
||||||
|
|
||||||
val newSortingMode = when (oldSortingMode) {
|
val newSortingMode = when (oldSortingMode) {
|
||||||
@ -208,12 +209,12 @@ object Migrations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prefs.edit(commit = true) {
|
prefs.edit(commit = true) {
|
||||||
remove(libraryPreferences.librarySortingMode().key())
|
remove(libraryPreferences.sortingMode().key())
|
||||||
remove("library_sorting_ascending")
|
remove("library_sorting_ascending")
|
||||||
}
|
}
|
||||||
|
|
||||||
prefs.edit {
|
prefs.edit {
|
||||||
putString(libraryPreferences.librarySortingMode().key(), newSortingMode)
|
putString(libraryPreferences.sortingMode().key(), newSortingMode)
|
||||||
putString("library_sorting_ascending", newSortingDirection)
|
putString("library_sorting_ascending", newSortingDirection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,16 +225,16 @@ object Migrations {
|
|||||||
}
|
}
|
||||||
if (oldVersion < 71) {
|
if (oldVersion < 71) {
|
||||||
// Handle removed every 3, 4, 6, and 8 hour library updates
|
// 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)) {
|
if (updateInterval in listOf(3, 4, 6, 8)) {
|
||||||
libraryPreferences.libraryUpdateInterval().set(12)
|
libraryPreferences.autoUpdateInterval().set(12)
|
||||||
LibraryUpdateJob.setupTask(context, 12)
|
LibraryUpdateJob.setupTask(context, 12)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion < 72) {
|
if (oldVersion < 72) {
|
||||||
val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true)
|
val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true)
|
||||||
if (!oldUpdateOngoingOnly) {
|
if (!oldUpdateOngoingOnly) {
|
||||||
libraryPreferences.libraryUpdateMangaRestriction() -= MANGA_NON_COMPLETED
|
libraryPreferences.autoUpdateMangaRestrictions() -= MANGA_NON_COMPLETED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion < 75) {
|
if (oldVersion < 75) {
|
||||||
@ -258,20 +259,20 @@ object Migrations {
|
|||||||
if (oldVersion < 81) {
|
if (oldVersion < 81) {
|
||||||
// Handle renamed enum values
|
// Handle renamed enum values
|
||||||
prefs.edit {
|
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"
|
"LAST_CHECKED" -> "LAST_MANGA_UPDATE"
|
||||||
"UNREAD" -> "UNREAD_COUNT"
|
"UNREAD" -> "UNREAD_COUNT"
|
||||||
"DATE_FETCHED" -> "CHAPTER_FETCH_DATE"
|
"DATE_FETCHED" -> "CHAPTER_FETCH_DATE"
|
||||||
else -> oldSortingMode
|
else -> oldSortingMode
|
||||||
}
|
}
|
||||||
putString(libraryPreferences.librarySortingMode().key(), newSortingMode)
|
putString(libraryPreferences.sortingMode().key(), newSortingMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion < 82) {
|
if (oldVersion < 82) {
|
||||||
prefs.edit {
|
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")!!
|
val direction = prefs.getString("library_sorting_ascending", "ASCENDING")!!
|
||||||
putString(libraryPreferences.librarySortingMode().key(), "$sort,$direction")
|
putString(libraryPreferences.sortingMode().key(), "$sort,$direction")
|
||||||
remove("library_sorting_ascending")
|
remove("library_sorting_ascending")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,6 +369,12 @@ object Migrations {
|
|||||||
readerPreferences.longStripSplitWebtoon().set(false)
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.work.BackoffPolicy
|
import androidx.work.BackoffPolicy
|
||||||
|
import androidx.work.Constraints
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
@ -76,6 +77,10 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
|
|||||||
val backupPreferences = Injekt.get<BackupPreferences>()
|
val backupPreferences = Injekt.get<BackupPreferences>()
|
||||||
val interval = prefInterval ?: backupPreferences.backupInterval().get()
|
val interval = prefInterval ?: backupPreferences.backupInterval().get()
|
||||||
if (interval > 0) {
|
if (interval > 0) {
|
||||||
|
val constraints = Constraints(
|
||||||
|
requiresBatteryNotLow = true,
|
||||||
|
)
|
||||||
|
|
||||||
val request = PeriodicWorkRequestBuilder<BackupCreateJob>(
|
val request = PeriodicWorkRequestBuilder<BackupCreateJob>(
|
||||||
interval.toLong(),
|
interval.toLong(),
|
||||||
TimeUnit.HOURS,
|
TimeUnit.HOURS,
|
||||||
@ -84,6 +89,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
|
|||||||
)
|
)
|
||||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10.minutes.toJavaDuration())
|
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10.minutes.toJavaDuration())
|
||||||
.addTag(TAG_AUTO)
|
.addTag(TAG_AUTO)
|
||||||
|
.setConstraints(constraints)
|
||||||
.setInputData(workDataOf(IS_AUTO_BACKUP_KEY to true))
|
.setInputData(workDataOf(IS_AUTO_BACKUP_KEY to true))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ class BackupFileValidator(
|
|||||||
.distinct()
|
.distinct()
|
||||||
val missingTrackers = trackers
|
val missingTrackers = trackers
|
||||||
.mapNotNull { trackManager.getService(it.toLong()) }
|
.mapNotNull { trackManager.getService(it.toLong()) }
|
||||||
.filter { !it.isLogged }
|
.filter { !it.isLoggedIn }
|
||||||
.map { context.getString(it.nameRes()) }
|
.map { context.getString(it.nameRes()) }
|
||||||
.sorted()
|
.sorted()
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.core.util.mapNotNullKeys
|
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
@ -327,14 +326,16 @@ class DownloadCache(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val sourceMap = sources.associate { provider.getSourceDirName(it).lowercase() to it.id }
|
||||||
|
|
||||||
rootDownloadsDirLock.withLock {
|
rootDownloadsDirLock.withLock {
|
||||||
val sourceDirs = rootDownloadsDir.dir.listFiles().orEmpty()
|
val sourceDirs = rootDownloadsDir.dir.listFiles().orEmpty()
|
||||||
.associate { it.name to SourceDirectory(it) }
|
.filter { it.isDirectory && !it.name.isNullOrBlank() }
|
||||||
.mapNotNullKeys { entry ->
|
.mapNotNull { dir ->
|
||||||
sources.find {
|
val sourceId = sourceMap[dir.name!!.lowercase()]
|
||||||
provider.getSourceDirName(it).equals(entry.key, ignoreCase = true)
|
sourceId?.let { it to SourceDirectory(dir) }
|
||||||
}?.id
|
|
||||||
}
|
}
|
||||||
|
.toMap()
|
||||||
|
|
||||||
rootDownloadsDir.sourceDirs = sourceDirs
|
rootDownloadsDir.sourceDirs = sourceDirs
|
||||||
|
|
||||||
@ -342,7 +343,7 @@ class DownloadCache(
|
|||||||
.map { sourceDir ->
|
.map { sourceDir ->
|
||||||
async {
|
async {
|
||||||
sourceDir.mangaDirs = sourceDir.dir.listFiles().orEmpty()
|
sourceDir.mangaDirs = sourceDir.dir.listFiles().orEmpty()
|
||||||
.filterNot { it.name.isNullOrBlank() }
|
.filter { it.isDirectory && !it.name.isNullOrBlank() }
|
||||||
.associate { it.name!! to MangaDirectory(it) }
|
.associate { it.name!! to MangaDirectory(it) }
|
||||||
|
|
||||||
sourceDir.mangaDirs.values.forEach { mangaDir ->
|
sourceDir.mangaDirs.values.forEach { mangaDir ->
|
||||||
|
@ -15,19 +15,14 @@ import androidx.work.WorkQuery
|
|||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import androidx.work.workDataOf
|
import androidx.work.workDataOf
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
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.interactor.UpdateManga
|
||||||
import eu.kanade.domain.manga.model.copyFrom
|
import eu.kanade.domain.manga.model.copyFrom
|
||||||
import eu.kanade.domain.manga.model.toSManga
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
import eu.kanade.domain.track.interactor.RefreshTracks
|
||||||
import eu.kanade.domain.track.model.toDomainTrack
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
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.UnmeteredSource
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
@ -44,7 +39,6 @@ import kotlinx.coroutines.awaitAll
|
|||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.supervisorScope
|
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.sync.withPermit
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
@ -53,13 +47,11 @@ import tachiyomi.core.util.lang.withIOContext
|
|||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
|
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.chapter.model.NoChaptersException
|
import tachiyomi.domain.chapter.model.NoChaptersException
|
||||||
import tachiyomi.domain.download.service.DownloadPreferences
|
import tachiyomi.domain.download.service.DownloadPreferences
|
||||||
import tachiyomi.domain.library.model.LibraryManga
|
import tachiyomi.domain.library.model.LibraryManga
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
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_CHARGING
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_NETWORK_NOT_METERED
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_NETWORK_NOT_METERED
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
|
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.manga.model.toMangaUpdate
|
||||||
import tachiyomi.domain.source.model.SourceNotInstalledException
|
import tachiyomi.domain.source.model.SourceNotInstalledException
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
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.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -93,17 +83,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
private val downloadPreferences: DownloadPreferences = Injekt.get()
|
private val downloadPreferences: DownloadPreferences = Injekt.get()
|
||||||
private val libraryPreferences: LibraryPreferences = Injekt.get()
|
private val libraryPreferences: LibraryPreferences = Injekt.get()
|
||||||
private val downloadManager: DownloadManager = Injekt.get()
|
private val downloadManager: DownloadManager = Injekt.get()
|
||||||
private val trackManager: TrackManager = Injekt.get()
|
|
||||||
private val coverCache: CoverCache = Injekt.get()
|
private val coverCache: CoverCache = Injekt.get()
|
||||||
private val getLibraryManga: GetLibraryManga = Injekt.get()
|
private val getLibraryManga: GetLibraryManga = Injekt.get()
|
||||||
private val getManga: GetManga = Injekt.get()
|
private val getManga: GetManga = Injekt.get()
|
||||||
private val updateManga: UpdateManga = Injekt.get()
|
private val updateManga: UpdateManga = Injekt.get()
|
||||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get()
|
|
||||||
private val getCategories: GetCategories = Injekt.get()
|
private val getCategories: GetCategories = Injekt.get()
|
||||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get()
|
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get()
|
||||||
private val getTracks: GetTracks = Injekt.get()
|
private val refreshTracks: RefreshTracks = Injekt.get()
|
||||||
private val insertTrack: InsertTrack = Injekt.get()
|
|
||||||
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get()
|
|
||||||
private val setFetchInterval: SetFetchInterval = Injekt.get()
|
private val setFetchInterval: SetFetchInterval = Injekt.get()
|
||||||
|
|
||||||
private val notifier = LibraryUpdateNotifier(context)
|
private val notifier = LibraryUpdateNotifier(context)
|
||||||
@ -113,7 +99,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
if (tags.contains(WORK_NAME_AUTO)) {
|
if (tags.contains(WORK_NAME_AUTO)) {
|
||||||
val preferences = Injekt.get<LibraryPreferences>()
|
val preferences = Injekt.get<LibraryPreferences>()
|
||||||
val restrictions = preferences.libraryUpdateDeviceRestriction().get()
|
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
|
||||||
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
|
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
|
||||||
return Result.retry()
|
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 this is a chapter update, set the last update time to now
|
||||||
if (target == Target.CHAPTERS) {
|
if (target == Target.CHAPTERS) {
|
||||||
libraryPreferences.libraryUpdateLastTimestamp().set(Date().time)
|
libraryPreferences.lastUpdatedTimestamp().set(Date().time)
|
||||||
}
|
}
|
||||||
|
|
||||||
val categoryId = inputData.getLong(KEY_CATEGORY, -1L)
|
val categoryId = inputData.getLong(KEY_CATEGORY, -1L)
|
||||||
@ -181,14 +167,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
val listToUpdate = if (categoryId != -1L) {
|
val listToUpdate = if (categoryId != -1L) {
|
||||||
libraryManga.filter { it.category == categoryId }
|
libraryManga.filter { it.category == categoryId }
|
||||||
} else {
|
} else {
|
||||||
val categoriesToUpdate = libraryPreferences.libraryUpdateCategories().get().map { it.toLong() }
|
val categoriesToUpdate = libraryPreferences.updateCategories().get().map { it.toLong() }
|
||||||
val includedManga = if (categoriesToUpdate.isNotEmpty()) {
|
val includedManga = if (categoriesToUpdate.isNotEmpty()) {
|
||||||
libraryManga.filter { it.category in categoriesToUpdate }
|
libraryManga.filter { it.category in categoriesToUpdate }
|
||||||
} else {
|
} else {
|
||||||
libraryManga
|
libraryManga
|
||||||
}
|
}
|
||||||
|
|
||||||
val categoriesToExclude = libraryPreferences.libraryUpdateCategoriesExclude().get().map { it.toLong() }
|
val categoriesToExclude = libraryPreferences.updateCategoriesExclude().get().map { it.toLong() }
|
||||||
val excludedMangaIds = if (categoriesToExclude.isNotEmpty()) {
|
val excludedMangaIds = if (categoriesToExclude.isNotEmpty()) {
|
||||||
libraryManga.filter { it.category in categoriesToExclude }.map { it.manga.id }
|
libraryManga.filter { it.category in categoriesToExclude }.map { it.manga.id }
|
||||||
} else {
|
} else {
|
||||||
@ -229,7 +215,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
val skippedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
val skippedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
||||||
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
||||||
val hasDownloads = AtomicBoolean(false)
|
val hasDownloads = AtomicBoolean(false)
|
||||||
val restrictions = libraryPreferences.libraryUpdateMangaRestriction().get()
|
val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get()
|
||||||
val fetchWindow = setFetchInterval.getWindow(ZonedDateTime.now())
|
val fetchWindow = setFetchInterval.getWindow(ZonedDateTime.now())
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
@ -297,8 +283,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (libraryPreferences.autoUpdateTrackers().get()) {
|
if (libraryPreferences.autoUpdateTrackers().get()) {
|
||||||
val loggedServices = trackManager.services.filter { it.isLogged }
|
refreshTracks.await(manga.id)
|
||||||
updateTrackings(manga, loggedServices)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -418,49 +403,19 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
private suspend fun updateTrackings() {
|
private suspend fun updateTrackings() {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
var progressCount = 0
|
var progressCount = 0
|
||||||
val loggedServices = trackManager.services.filter { it.isLogged }
|
|
||||||
|
|
||||||
mangaToUpdate.forEach { libraryManga ->
|
mangaToUpdate.forEach { libraryManga ->
|
||||||
val manga = libraryManga.manga
|
|
||||||
|
|
||||||
ensureActive()
|
ensureActive()
|
||||||
|
|
||||||
|
val manga = libraryManga.manga
|
||||||
notifier.showProgressNotification(listOf(manga), progressCount++, mangaToUpdate.size)
|
notifier.showProgressNotification(listOf(manga), progressCount++, mangaToUpdate.size)
|
||||||
|
refreshTracks.await(manga.id)
|
||||||
// Update the tracking details.
|
|
||||||
updateTrackings(manga, loggedServices)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
notifier.cancelProgressNotification()
|
notifier.cancelProgressNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateTrackings(manga: Manga, loggedServices: List<TrackService>) {
|
|
||||||
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(
|
private suspend fun withUpdateNotification(
|
||||||
updatingManga: CopyOnWriteArrayList<Manga>,
|
updatingManga: CopyOnWriteArrayList<Manga>,
|
||||||
completed: AtomicInteger,
|
completed: AtomicInteger,
|
||||||
@ -558,13 +513,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
prefInterval: Int? = null,
|
prefInterval: Int? = null,
|
||||||
) {
|
) {
|
||||||
val preferences = Injekt.get<LibraryPreferences>()
|
val preferences = Injekt.get<LibraryPreferences>()
|
||||||
val interval = prefInterval ?: preferences.libraryUpdateInterval().get()
|
val interval = prefInterval ?: preferences.autoUpdateInterval().get()
|
||||||
if (interval > 0) {
|
if (interval > 0) {
|
||||||
val restrictions = preferences.libraryUpdateDeviceRestriction().get()
|
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
|
||||||
val constraints = Constraints(
|
val constraints = Constraints(
|
||||||
requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { NetworkType.UNMETERED } else { NetworkType.CONNECTED },
|
requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { NetworkType.UNMETERED } else { NetworkType.CONNECTED },
|
||||||
requiresCharging = DEVICE_CHARGING in restrictions,
|
requiresCharging = DEVICE_CHARGING in restrictions,
|
||||||
requiresBatteryNotLow = DEVICE_BATTERY_NOT_LOW in restrictions,
|
requiresBatteryNotLow = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
|
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
|
||||||
|
@ -39,5 +39,5 @@ class TrackManager(context: Context) {
|
|||||||
|
|
||||||
fun getService(id: Long) = services.find { it.id == id }
|
fun getService(id: Long) = services.find { it.id == id }
|
||||||
|
|
||||||
fun hasLoggedServices() = services.any { it.isLogged }
|
fun hasLoggedServices() = services.any { it.isLoggedIn }
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ abstract class TrackService(val id: Long) {
|
|||||||
val trackPreferences: TrackPreferences by injectLazy()
|
val trackPreferences: TrackPreferences by injectLazy()
|
||||||
val networkService: NetworkHelper by injectLazy()
|
val networkService: NetworkHelper by injectLazy()
|
||||||
private val insertTrack: InsertTrack by injectLazy()
|
private val insertTrack: InsertTrack by injectLazy()
|
||||||
|
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay by injectLazy()
|
||||||
|
|
||||||
open val client: OkHttpClient
|
open val client: OkHttpClient
|
||||||
get() = networkService.client
|
get() = networkService.client
|
||||||
@ -89,7 +90,7 @@ abstract class TrackService(val id: Long) {
|
|||||||
trackPreferences.setTrackCredentials(this, "", "")
|
trackPreferences.setTrackCredentials(this, "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
open val isLogged: Boolean
|
open val isLoggedIn: Boolean
|
||||||
get() = getUsername().isNotEmpty() &&
|
get() = getUsername().isNotEmpty() &&
|
||||||
getPassword().isNotEmpty()
|
getPassword().isNotEmpty()
|
||||||
|
|
||||||
@ -101,6 +102,7 @@ abstract class TrackService(val id: Long) {
|
|||||||
trackPreferences.setTrackCredentials(this, username, password)
|
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) {
|
suspend fun registerTracking(item: Track, mangaId: Long) {
|
||||||
item.manga_id = mangaId
|
item.manga_id = mangaId
|
||||||
try {
|
try {
|
||||||
@ -113,6 +115,7 @@ abstract class TrackService(val id: Long) {
|
|||||||
|
|
||||||
insertTrack.await(track)
|
insertTrack.await(track)
|
||||||
|
|
||||||
|
// TODO: merge into SyncChaptersWithTrackServiceTwoWay?
|
||||||
// Update chapter progress if newer chapters marked read locally
|
// Update chapter progress if newer chapters marked read locally
|
||||||
if (hasReadChapters) {
|
if (hasReadChapters) {
|
||||||
val latestLocalReadChapterNumber = allChapters
|
val latestLocalReadChapterNumber = allChapters
|
||||||
@ -144,9 +147,7 @@ abstract class TrackService(val id: Long) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is EnhancedTrackService) {
|
syncChaptersWithTrackServiceTwoWay.await(mangaId, track, this@TrackService)
|
||||||
Injekt.get<SyncChaptersWithTrackServiceTwoWay>().await(allChapters, track, this@TrackService)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
withUIContext { Injekt.get<Application>().toast(e.message) }
|
withUIContext { Injekt.get<Application>().toast(e.message) }
|
||||||
|
@ -45,7 +45,6 @@ import tachiyomi.core.util.system.logcat
|
|||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
import tachiyomi.domain.category.interactor.SetMangaCategories
|
import tachiyomi.domain.category.interactor.SetMangaCategories
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
|
|
||||||
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
||||||
@ -72,7 +71,6 @@ class BrowseSourceScreenModel(
|
|||||||
private val getRemoteManga: GetRemoteManga = Injekt.get(),
|
private val getRemoteManga: GetRemoteManga = Injekt.get(),
|
||||||
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
|
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
|
||||||
private val getCategories: GetCategories = Injekt.get(),
|
private val getCategories: GetCategories = Injekt.get(),
|
||||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
|
||||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||||
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
||||||
private val getManga: GetManga = Injekt.get(),
|
private val getManga: GetManga = Injekt.get(),
|
||||||
@ -82,7 +80,7 @@ class BrowseSourceScreenModel(
|
|||||||
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
|
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
|
||||||
) : StateScreenModel<BrowseSourceScreenModel.State>(State(Listing.valueOf(listingQuery))) {
|
) : StateScreenModel<BrowseSourceScreenModel.State>(State(Listing.valueOf(listingQuery))) {
|
||||||
|
|
||||||
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
|
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLoggedIn } }
|
||||||
|
|
||||||
var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
|
var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
|
||||||
|
|
||||||
@ -299,8 +297,7 @@ class BrowseSourceScreenModel(
|
|||||||
(service as TrackService).bind(track)
|
(service as TrackService).bind(track)
|
||||||
insertTrack.await(track.toDomainTrack()!!)
|
insertTrack.await(track.toDomainTrack()!!)
|
||||||
|
|
||||||
val chapters = getChapterByMangaId.await(manga.id)
|
syncChaptersWithTrackServiceTwoWay.await(manga.id, track.toDomainTrack()!!, service)
|
||||||
syncChaptersWithTrackServiceTwoWay.await(chapters, track.toDomainTrack()!!, service)
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.WARN, e) { "Could not match manga: ${manga.title} with service $service" }
|
logcat(LogPriority.WARN, e) { "Could not match manga: ${manga.title} with service $service" }
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.ui.main
|
package eu.kanade.tachiyomi.ui.deeplink
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
|
|
||||||
class DeepLinkActivity : Activity() {
|
class DeepLinkActivity : Activity() {
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<DeepLinkScreenModel.State>(State.Loading) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
coroutineScope.launchIO {
|
||||||
|
val manga = sourceManager.getCatalogueSources()
|
||||||
|
.filterIsInstance<ResolvableSource>()
|
||||||
|
.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
|
||||||
|
}
|
||||||
|
}
|
@ -366,7 +366,7 @@ class LibraryScreenModel(
|
|||||||
* @return map of track id with the filter value
|
* @return map of track id with the filter value
|
||||||
*/
|
*/
|
||||||
private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
|
private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
|
||||||
val loggedServices = trackManager.services.filter { it.isLogged }
|
val loggedServices = trackManager.services.filter { it.isLoggedIn }
|
||||||
return if (loggedServices.isNotEmpty()) {
|
return if (loggedServices.isNotEmpty()) {
|
||||||
val prefFlows = loggedServices
|
val prefFlows = loggedServices
|
||||||
.map { libraryPreferences.filterTracking(it.id.toInt()).changes() }
|
.map { libraryPreferences.filterTracking(it.id.toInt()).changes() }
|
||||||
@ -519,7 +519,7 @@ class LibraryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getDisplayMode(): PreferenceMutableState<LibraryDisplayMode> {
|
fun getDisplayMode(): PreferenceMutableState<LibraryDisplayMode> {
|
||||||
return libraryPreferences.libraryDisplayMode().asState(coroutineScope)
|
return libraryPreferences.displayMode().asState(coroutineScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getColumnsPreferenceForCurrentOrientation(isLandscape: Boolean): PreferenceMutableState<Int> {
|
fun getColumnsPreferenceForCurrentOrientation(isLandscape: Boolean): PreferenceMutableState<Int> {
|
||||||
|
@ -26,7 +26,7 @@ class LibrarySettingsScreenModel(
|
|||||||
) : ScreenModel {
|
) : ScreenModel {
|
||||||
|
|
||||||
val trackServices
|
val trackServices
|
||||||
get() = trackManager.services.filter { it.isLogged }
|
get() = trackManager.services.filter { it.isLoggedIn }
|
||||||
|
|
||||||
fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) {
|
fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) {
|
||||||
preference(libraryPreferences).getAndSet {
|
preference(libraryPreferences).getAndSet {
|
||||||
|
@ -148,7 +148,7 @@ object LibraryTab : Tab {
|
|||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.searchQuery.isNullOrEmpty() && !state.hasActiveFilters && state.isLibraryEmpty -> {
|
state.searchQuery.isNullOrEmpty() && !state.hasActiveFilters && state.isLibraryEmpty -> {
|
||||||
val handler = LocalUriHandler.current
|
val handler = LocalUriHandler.current
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
|
@ -71,6 +71,7 @@ import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
|||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
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.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
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)
|
val query = intent.getStringExtra(SearchManager.QUERY) ?: intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
if (!query.isNullOrEmpty()) {
|
if (!query.isNullOrEmpty()) {
|
||||||
navigator.popUntilRoot()
|
navigator.popUntilRoot()
|
||||||
navigator.push(GlobalSearchScreen(query))
|
navigator.push(DeepLinkScreen(query))
|
||||||
}
|
}
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ class MangaScreenModel(
|
|||||||
private val successState: State.Success?
|
private val successState: State.Success?
|
||||||
get() = state.value as? 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?
|
val manga: Manga?
|
||||||
get() = successState?.manga
|
get() = successState?.manga
|
||||||
@ -128,7 +128,7 @@ class MangaScreenModel(
|
|||||||
val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get()))
|
val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get()))
|
||||||
private val skipFiltered by readerPreferences.skipFiltered().asState(coroutineScope)
|
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<Int> = arrayOf(-1, -1) // first and last selected index in list
|
private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list
|
||||||
private val selectedChapterIds: HashSet<Long> = HashSet()
|
private val selectedChapterIds: HashSet<Long> = HashSet()
|
||||||
|
@ -71,7 +71,6 @@ import tachiyomi.core.util.lang.withIOContext
|
|||||||
import tachiyomi.core.util.lang.withUIContext
|
import tachiyomi.core.util.lang.withUIContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.manga.interactor.GetManga
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import tachiyomi.domain.track.interactor.DeleteTrack
|
import tachiyomi.domain.track.interactor.DeleteTrack
|
||||||
import tachiyomi.domain.track.interactor.GetTracks
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
@ -218,8 +217,7 @@ data class TrackInfoDialogHomeScreen(
|
|||||||
|
|
||||||
private suspend fun refreshTrackers() {
|
private suspend fun refreshTrackers() {
|
||||||
val insertTrack = Injekt.get<InsertTrack>()
|
val insertTrack = Injekt.get<InsertTrack>()
|
||||||
val getMangaWithChapters = Injekt.get<GetMangaWithChapters>()
|
val syncChaptersWithTrackServiceTwoWay = Injekt.get<SyncChaptersWithTrackServiceTwoWay>()
|
||||||
val syncTwoWayService = Injekt.get<SyncChaptersWithTrackServiceTwoWay>()
|
|
||||||
val context = Injekt.get<Application>()
|
val context = Injekt.get<Application>()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -229,11 +227,7 @@ data class TrackInfoDialogHomeScreen(
|
|||||||
val track = trackItem.track ?: continue
|
val track = trackItem.track ?: continue
|
||||||
val domainTrack = trackItem.service.refresh(track.toDbTrack()).toDomainTrack() ?: continue
|
val domainTrack = trackItem.service.refresh(track.toDbTrack()).toDomainTrack() ?: continue
|
||||||
insertTrack.await(domainTrack)
|
insertTrack.await(domainTrack)
|
||||||
|
syncChaptersWithTrackServiceTwoWay.await(mangaId, domainTrack, trackItem.service)
|
||||||
if (trackItem.service is EnhancedTrackService) {
|
|
||||||
val allChapters = getMangaWithChapters.awaitChapters(mangaId)
|
|
||||||
syncTwoWayService.await(allChapters, domainTrack, trackItem.service)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(
|
logcat(
|
||||||
LogPriority.ERROR,
|
LogPriority.ERROR,
|
||||||
@ -257,7 +251,7 @@ data class TrackInfoDialogHomeScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Track>.mapToTrackItem(): List<TrackItem> {
|
private fun List<Track>.mapToTrackItem(): List<TrackItem> {
|
||||||
val loggedServices = Injekt.get<TrackManager>().services.filter { it.isLogged }
|
val loggedServices = Injekt.get<TrackManager>().services.filter { it.isLoggedIn }
|
||||||
val source = Injekt.get<SourceManager>().getOrStub(sourceId)
|
val source = Injekt.get<SourceManager>().getOrStub(sourceId)
|
||||||
return loggedServices
|
return loggedServices
|
||||||
// Map to TrackItem
|
// Map to TrackItem
|
||||||
|
@ -36,7 +36,7 @@ class StatsScreenModel(
|
|||||||
private val trackManager: TrackManager = Injekt.get(),
|
private val trackManager: TrackManager = Injekt.get(),
|
||||||
) : StateScreenModel<StatsScreenState>(StatsScreenState.Loading) {
|
) : StateScreenModel<StatsScreenState>(StatsScreenState.Loading) {
|
||||||
|
|
||||||
private val loggedServices by lazy { trackManager.services.fastFilter { it.isLogged } }
|
private val loggedServices by lazy { trackManager.services.fastFilter { it.isLoggedIn } }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launchIO {
|
coroutineScope.launchIO {
|
||||||
@ -87,14 +87,14 @@ class StatsScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getGlobalUpdateItemCount(libraryManga: List<LibraryManga>): Int {
|
private fun getGlobalUpdateItemCount(libraryManga: List<LibraryManga>): Int {
|
||||||
val includedCategories = preferences.libraryUpdateCategories().get().map { it.toLong() }
|
val includedCategories = preferences.updateCategories().get().map { it.toLong() }
|
||||||
val includedManga = if (includedCategories.isNotEmpty()) {
|
val includedManga = if (includedCategories.isNotEmpty()) {
|
||||||
libraryManga.filter { it.category in includedCategories }
|
libraryManga.filter { it.category in includedCategories }
|
||||||
} else {
|
} else {
|
||||||
libraryManga
|
libraryManga
|
||||||
}
|
}
|
||||||
|
|
||||||
val excludedCategories = preferences.libraryUpdateCategoriesExclude().get().map { it.toLong() }
|
val excludedCategories = preferences.updateCategoriesExclude().get().map { it.toLong() }
|
||||||
val excludedMangaIds = if (excludedCategories.isNotEmpty()) {
|
val excludedMangaIds = if (excludedCategories.isNotEmpty()) {
|
||||||
libraryManga.fastMapNotNull { manga ->
|
libraryManga.fastMapNotNull { manga ->
|
||||||
manga.id.takeIf { manga.category in excludedCategories }
|
manga.id.takeIf { manga.category in excludedCategories }
|
||||||
@ -103,7 +103,7 @@ class StatsScreenModel(
|
|||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val updateRestrictions = preferences.libraryUpdateMangaRestriction().get()
|
val updateRestrictions = preferences.autoUpdateMangaRestrictions().get()
|
||||||
return includedManga
|
return includedManga
|
||||||
.fastFilterNot { it.manga.id in excludedMangaIds }
|
.fastFilterNot { it.manga.id in excludedMangaIds }
|
||||||
.fastDistinctBy { it.manga.id }
|
.fastDistinctBy { it.manga.id }
|
||||||
|
@ -64,7 +64,7 @@ class UpdatesScreenModel(
|
|||||||
private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
|
private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
|
||||||
val events: Flow<Event> = _events.receiveAsFlow()
|
val events: Flow<Event> = _events.receiveAsFlow()
|
||||||
|
|
||||||
val lastUpdated by libraryPreferences.libraryUpdateLastTimestamp().asState(coroutineScope)
|
val lastUpdated by libraryPreferences.lastUpdatedTimestamp().asState(coroutineScope)
|
||||||
|
|
||||||
// First and last selected index in list
|
// First and last selected index in list
|
||||||
private val selectedPositions: Array<Int> = arrayOf(-1, -1)
|
private val selectedPositions: Array<Int> = arrayOf(-1, -1)
|
||||||
|
@ -21,10 +21,25 @@ data class GithubRelease(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class GitHubAssets(@SerialName("browser_download_url") val downloadLink: String)
|
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 = {
|
val releaseMapper: (GithubRelease) -> Release = {
|
||||||
Release(
|
Release(
|
||||||
it.version,
|
it.version,
|
||||||
it.info,
|
it.info.replace(gitHubUsernameMentionRegex) { mention ->
|
||||||
|
"[${mention.value}](https://github.com/${mention.value.substring(1)})"
|
||||||
|
},
|
||||||
it.releaseLink,
|
it.releaseLink,
|
||||||
it.assets.map(GitHubAssets::downloadLink),
|
it.assets.map(GitHubAssets::downloadLink),
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,7 @@ class CreateCategoryWithName(
|
|||||||
|
|
||||||
private val initialFlags: Long
|
private val initialFlags: Long
|
||||||
get() {
|
get() {
|
||||||
val sort = preferences.librarySortingMode().get()
|
val sort = preferences.sortingMode().get()
|
||||||
return sort.type.flag or sort.direction.flag
|
return sort.type.flag or sort.direction.flag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ class ResetCategoryFlags(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await() {
|
suspend fun await() {
|
||||||
val sort = preferences.librarySortingMode().get()
|
val sort = preferences.sortingMode().get()
|
||||||
categoryRepository.updateAllFlags(sort.type + sort.direction)
|
categoryRepository.updateAllFlags(sort.type + sort.direction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,6 @@ class SetDisplayMode(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
fun await(display: LibraryDisplayMode) {
|
fun await(display: LibraryDisplayMode) {
|
||||||
preferences.libraryDisplayMode().set(display)
|
preferences.displayMode().set(display)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ class SetSortModeForCategory(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
preferences.librarySortingMode().set(LibrarySort(type, direction))
|
preferences.sortingMode().set(LibrarySort(type, direction))
|
||||||
categoryRepository.updateAllFlags(flags)
|
categoryRepository.updateAllFlags(flags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,24 +11,24 @@ class LibraryPreferences(
|
|||||||
private val preferenceStore: PreferenceStore,
|
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 portraitColumns() = preferenceStore.getInt("pref_library_columns_portrait_key", 0)
|
||||||
|
|
||||||
fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0)
|
fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0)
|
||||||
|
|
||||||
fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
|
fun lastUpdatedTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L)
|
||||||
fun libraryUpdateLastTimestamp() = 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",
|
"library_update_restriction",
|
||||||
setOf(
|
setOf(
|
||||||
DEVICE_ONLY_ON_WIFI,
|
DEVICE_ONLY_ON_WIFI,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
fun libraryUpdateMangaRestriction() = preferenceStore.getStringSet(
|
fun autoUpdateMangaRestrictions() = preferenceStore.getStringSet(
|
||||||
"library_update_manga_restriction",
|
"library_update_manga_restriction",
|
||||||
setOf(
|
setOf(
|
||||||
MANGA_HAS_UNREAD,
|
MANGA_HAS_UNREAD,
|
||||||
@ -95,9 +95,9 @@ class LibraryPreferences(
|
|||||||
|
|
||||||
fun categorizedDisplaySettings() = preferenceStore.getBoolean("categorized_display", false)
|
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
|
// endregion
|
||||||
|
|
||||||
@ -148,7 +148,6 @@ class LibraryPreferences(
|
|||||||
const val DEVICE_ONLY_ON_WIFI = "wifi"
|
const val DEVICE_ONLY_ON_WIFI = "wifi"
|
||||||
const val DEVICE_NETWORK_NOT_METERED = "network_not_metered"
|
const val DEVICE_NETWORK_NOT_METERED = "network_not_metered"
|
||||||
const val DEVICE_CHARGING = "ac"
|
const val DEVICE_CHARGING = "ac"
|
||||||
const val DEVICE_BATTERY_NOT_LOW = "battery_not_low"
|
|
||||||
|
|
||||||
const val MANGA_NON_COMPLETED = "manga_ongoing"
|
const val MANGA_NON_COMPLETED = "manga_ongoing"
|
||||||
const val MANGA_HAS_UNREAD = "manga_fully_read"
|
const val MANGA_HAS_UNREAD = "manga_fully_read"
|
||||||
|
@ -6,20 +6,19 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
@Suppress("OverridingDeprecatedMember")
|
|
||||||
class StubSource(
|
class StubSource(
|
||||||
override val id: Long,
|
override val id: Long,
|
||||||
override val lang: String,
|
override val lang: String,
|
||||||
override val name: String,
|
override val name: String,
|
||||||
) : Source {
|
) : Source {
|
||||||
|
|
||||||
val isInvalid: Boolean = name.isBlank() || lang.isBlank()
|
private val isInvalid: Boolean = name.isBlank() || lang.isBlank()
|
||||||
|
|
||||||
override suspend fun getMangaDetails(manga: SManga): SManga {
|
override suspend fun getMangaDetails(manga: SManga): SManga {
|
||||||
throw SourceNotInstalledException()
|
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<SManga> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
return Observable.error(SourceNotInstalledException())
|
return Observable.error(SourceNotInstalledException())
|
||||||
}
|
}
|
||||||
@ -28,7 +27,7 @@ class StubSource(
|
|||||||
throw SourceNotInstalledException()
|
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<List<SChapter>> {
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
return Observable.error(SourceNotInstalledException())
|
return Observable.error(SourceNotInstalledException())
|
||||||
}
|
}
|
||||||
@ -37,7 +36,7 @@ class StubSource(
|
|||||||
throw SourceNotInstalledException()
|
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<List<Page>> {
|
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||||
return Observable.error(SourceNotInstalledException())
|
return Observable.error(SourceNotInstalledException())
|
||||||
}
|
}
|
||||||
|
@ -25,4 +25,4 @@ kotlin.mpp.androidSourceSetLayoutVersion=2
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.defaults.buildfeatures.buildconfig=true
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
android.nonTransitiveRClass=false
|
android.nonTransitiveRClass=false
|
||||||
android.nonFinalResIds=false
|
#android.nonFinalResIds=false
|
@ -1,12 +1,12 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp_version = "8.1.0"
|
agp_version = "8.1.1"
|
||||||
lifecycle_version = "2.6.1"
|
lifecycle_version = "2.6.1"
|
||||||
paging_version = "3.2.0"
|
paging_version = "3.2.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" }
|
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"
|
appcompat = "androidx.appcompat:appcompat:1.6.1"
|
||||||
biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
||||||
constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
|
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-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
|
||||||
paging-compose = { module = "androidx.paging:paging-compose", 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-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha01"
|
||||||
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha01"
|
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha01"
|
||||||
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0-alpha04"
|
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0-alpha04"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[versions]
|
[versions]
|
||||||
compiler = "1.5.1"
|
compiler = "1.5.2"
|
||||||
compose-bom = "2023.09.00-alpha02"
|
compose-bom = "2023.09.00-alpha02"
|
||||||
accompanist = "0.33.0-alpha"
|
accompanist = "0.33.1-alpha"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
activity = "androidx.activity:activity-compose:1.7.2"
|
activity = "androidx.activity:activity-compose:1.7.2"
|
||||||
|
@ -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"
|
android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2"
|
||||||
google-services-gradle = "com.google.gms:google-services:4.3.15"
|
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"
|
rxjava = "io.reactivex:rxjava:1.3.8"
|
||||||
flowreactivenetwork = "ru.beryukhov:flowreactivenetwork:1.0.4"
|
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"
|
kotlinter = "org.jmailen.gradle:kotlinter-gradle:3.13.0"
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
reactivex = ["rxandroid", "rxjava"]
|
|
||||||
okhttp = ["okhttp-core", "okhttp-logging", "okhttp-dnsoverhttps"]
|
okhttp = ["okhttp-core", "okhttp-logging", "okhttp-dnsoverhttps"]
|
||||||
js-engine = ["quickjs-android"]
|
js-engine = ["quickjs-android"]
|
||||||
sqlite = ["sqlite-framework", "sqlite-ktx", "sqlite-android"]
|
sqlite = ["sqlite-framework", "sqlite-ktx", "sqlite-android"]
|
||||||
|
@ -254,7 +254,6 @@
|
|||||||
<string name="connected_to_wifi">Only on Wi-Fi</string>
|
<string name="connected_to_wifi">Only on Wi-Fi</string>
|
||||||
<string name="network_not_metered">Only on unmetered network</string>
|
<string name="network_not_metered">Only on unmetered network</string>
|
||||||
<string name="charging">When charging</string>
|
<string name="charging">When charging</string>
|
||||||
<string name="battery_not_low">When battery not low</string>
|
|
||||||
<string name="restrictions">Restrictions: %s</string>
|
<string name="restrictions">Restrictions: %s</string>
|
||||||
|
|
||||||
<string name="pref_library_update_manga_restriction">Skip updating entries</string>
|
<string name="pref_library_update_manga_restriction">Skip updating entries</string>
|
||||||
@ -646,8 +645,6 @@
|
|||||||
<!-- missing prompt after Compose rewrite #7901 -->
|
<!-- missing prompt after Compose rewrite #7901 -->
|
||||||
<string name="no_more_results">No more results</string>
|
<string name="no_more_results">No more results</string>
|
||||||
<string name="no_results_found">No results found</string>
|
<string name="no_results_found">No results found</string>
|
||||||
<!-- Do not translate "WebView" -->
|
|
||||||
<string name="http_error_hint">Check website in WebView</string>
|
|
||||||
<string name="licensed_manga_chapters_error">Licensed - No chapters to show</string>
|
<string name="licensed_manga_chapters_error">Licensed - No chapters to show</string>
|
||||||
<string name="local_source">Local source</string>
|
<string name="local_source">Local source</string>
|
||||||
<string name="other_source">Other</string>
|
<string name="other_source">Other</string>
|
||||||
@ -987,4 +984,10 @@
|
|||||||
<string name="appwidget_updates_description">See your recently updated library entries</string>
|
<string name="appwidget_updates_description">See your recently updated library entries</string>
|
||||||
<string name="appwidget_unavailable_locked">Widget not available when app lock is enabled</string>
|
<string name="appwidget_unavailable_locked">Widget not available when app lock is enabled</string>
|
||||||
<string name="remove_manga">You are about to remove \"%s\" from your library</string>
|
<string name="remove_manga">You are about to remove \"%s\" from your library</string>
|
||||||
|
|
||||||
|
<!-- Common exceptions -->
|
||||||
|
<!-- Do not translate "WebView" -->
|
||||||
|
<string name="exception_http">HTTP %d, check website in WebView</string>
|
||||||
|
<string name="exception_offline">No Internet connection</string>
|
||||||
|
<string name="exception_unknown_host">Couldn\'t reach %s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -24,13 +24,44 @@ interface Source {
|
|||||||
val lang: String
|
val lang: String
|
||||||
get() = ""
|
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<SChapter> {
|
||||||
|
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<Page> {
|
||||||
|
return fetchPageList(chapter).awaitSingle()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable with the updated details for a manga.
|
* Returns an observable with the updated details for a manga.
|
||||||
*
|
*
|
||||||
* @param manga the manga to update.
|
* @param manga the manga to update.
|
||||||
*/
|
*/
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
"Use the 1.x API instead",
|
"Use the non-RxJava API instead",
|
||||||
ReplaceWith("getMangaDetails"),
|
ReplaceWith("getMangaDetails"),
|
||||||
)
|
)
|
||||||
fun fetchMangaDetails(manga: SManga): Observable<SManga> = throw IllegalStateException("Not used")
|
fun fetchMangaDetails(manga: SManga): Observable<SManga> = throw IllegalStateException("Not used")
|
||||||
@ -41,7 +72,7 @@ interface Source {
|
|||||||
* @param manga the manga to update.
|
* @param manga the manga to update.
|
||||||
*/
|
*/
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
"Use the 1.x API instead",
|
"Use the non-RxJava API instead",
|
||||||
ReplaceWith("getChapterList"),
|
ReplaceWith("getChapterList"),
|
||||||
)
|
)
|
||||||
fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = throw IllegalStateException("Not used")
|
fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = throw IllegalStateException("Not used")
|
||||||
@ -53,33 +84,8 @@ interface Source {
|
|||||||
* @param chapter the chapter.
|
* @param chapter the chapter.
|
||||||
*/
|
*/
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
"Use the 1.x API instead",
|
"Use the non-RxJava API instead",
|
||||||
ReplaceWith("getPageList"),
|
ReplaceWith("getPageList"),
|
||||||
)
|
)
|
||||||
fun fetchPageList(chapter: SChapter): Observable<List<Page>> = Observable.empty()
|
fun fetchPageList(chapter: SChapter): Observable<List<Page>> = 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<SChapter> {
|
|
||||||
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<Page> {
|
|
||||||
return fetchPageList(chapter).awaitSingle()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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?
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user