Last commit merged: 772db51593
This commit is contained in:
LuftVerbot 2023-11-19 14:39:59 +01:00
parent 2c4230376c
commit f1cd8f69d3
67 changed files with 355 additions and 414 deletions

View file

@ -1,5 +1,4 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jmailen.gradle.kotlinter.tasks.LintTask
import java.io.FileInputStream
import java.util.Properties
@ -293,10 +292,6 @@ androidComponents {
}
tasks {
withType<LintTask>().configureEach {
exclude { it.file.path.contains("generated[\\\\/]".toRegex()) }
}
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs += listOf(

View file

@ -13,11 +13,11 @@ import eu.kanade.domain.extension.manga.interactor.GetExtensionSources
import eu.kanade.domain.extension.manga.interactor.GetMangaExtensionLanguages
import eu.kanade.domain.extension.manga.interactor.GetMangaExtensionsByType
import eu.kanade.domain.items.chapter.interactor.SetReadStatus
import eu.kanade.domain.items.chapter.interactor.SyncChapterProgressWithTrack
import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.items.episode.interactor.SetSeenStatus
import eu.kanade.domain.items.episode.interactor.SyncEpisodeProgressWithTrack
import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithSource
import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithTrackServiceTwoWay
import eu.kanade.domain.source.anime.interactor.GetAnimeSourcesWithFavoriteCount
import eu.kanade.domain.source.anime.interactor.GetEnabledAnimeSources
import eu.kanade.domain.source.anime.interactor.GetLanguagesWithAnimeSources
@ -235,7 +235,7 @@ class DomainModule : InjektModule {
addFactory { SetSeenStatus(get(), get(), get(), get()) }
addFactory { ShouldUpdateDbEpisode() }
addFactory { SyncEpisodesWithSource(get(), get(), get(), get(), get(), get(), get()) }
addFactory { SyncEpisodesWithTrackServiceTwoWay(get(), get()) }
addFactory { SyncEpisodeProgressWithTrack(get(), get(), get()) }
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
addFactory { GetChapter(get()) }
@ -244,7 +244,7 @@ class DomainModule : InjektModule {
addFactory { SetReadStatus(get(), get(), get(), get()) }
addFactory { ShouldUpdateDbChapter() }
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get()) }
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
addFactory { SyncChapterProgressWithTrack(get(), get(), get()) }
addSingletonFactory<AnimeHistoryRepository> { AnimeHistoryRepositoryImpl(get()) }
addFactory { GetAnimeHistory(get()) }

View file

@ -1,27 +1,35 @@
package eu.kanade.domain.items.chapter.interactor
import eu.kanade.domain.track.manga.model.toDbTrack
import eu.kanade.tachiyomi.data.track.EnhancedMangaTrackService
import eu.kanade.tachiyomi.data.track.MangaTrackService
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
import tachiyomi.domain.items.chapter.model.Chapter
import tachiyomi.domain.items.chapter.model.toChapterUpdate
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
import tachiyomi.domain.track.manga.model.MangaTrack
import uy.kohesive.injekt.api.get
class SyncChaptersWithTrackServiceTwoWay(
class SyncChapterProgressWithTrack(
private val updateChapter: UpdateChapter,
private val insertTrack: InsertMangaTrack,
private val getChapterByMangaId: GetChapterByMangaId,
) {
suspend fun await(
chapters: List<Chapter>,
mangaId: Long,
remoteTrack: MangaTrack,
service: MangaTrackService,
) {
val sortedChapters = chapters.sortedBy { it.chapterNumber }
if (service !is EnhancedMangaTrackService) {
return
}
val sortedChapters = getChapterByMangaId.await(mangaId)
.sortedBy { it.chapterNumber }
.filter { it.isRecognizedNumber }
val chapterUpdates = sortedChapters
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
.map { it.copy(read = true).toChapterUpdate() }

View file

@ -2,26 +2,35 @@ package eu.kanade.domain.items.episode.interactor
import eu.kanade.domain.track.anime.model.toDbTrack
import eu.kanade.tachiyomi.data.track.AnimeTrackService
import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
import tachiyomi.domain.items.episode.model.Episode
import tachiyomi.domain.items.episode.model.toEpisodeUpdate
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
import tachiyomi.domain.track.anime.model.AnimeTrack
import uy.kohesive.injekt.api.get
class SyncEpisodesWithTrackServiceTwoWay(
class SyncEpisodeProgressWithTrack(
private val updateEpisode: UpdateEpisode,
private val insertTrack: InsertAnimeTrack,
private val getEpisodeByAnimeId: GetEpisodeByAnimeId,
) {
suspend fun await(
episodes: List<Episode>,
animeId: Long,
remoteTrack: AnimeTrack,
service: AnimeTrackService,
) {
val sortedEpisodes = episodes.sortedBy { it.episodeNumber }
if (service !is EnhancedAnimeTrackService) {
return
}
val sortedEpisodes = getEpisodeByAnimeId.await(animeId)
.sortedBy { it.episodeNumber }
.filter { it.isRecognizedNumber }
val episodeUpdates = sortedEpisodes
.filter { episode -> episode.episodeNumber <= remoteTrack.lastEpisodeSeen && !episode.seen }
.map { it.copy(seen = true).toEpisodeUpdate() }

View file

@ -0,0 +1,48 @@
package eu.kanade.domain.track.anime.interactor
import eu.kanade.domain.items.episode.interactor.SyncEpisodeProgressWithTrack
import eu.kanade.domain.track.anime.model.toDbTrack
import eu.kanade.domain.track.anime.model.toDomainTrack
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.supervisorScope
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
class RefreshAnimeTracks(
private val getTracks: GetAnimeTracks,
private val trackManager: TrackManager,
private val insertTrack: InsertAnimeTrack,
private val syncEpisodeProgressWithTrack: SyncEpisodeProgressWithTrack,
) {
/**
* Fetches updated tracking data from all logged in trackers.
*
* @return Failed updates.
*/
suspend fun await(animeId: Long): List<Pair<TrackService?, Throwable>> {
return supervisorScope {
return@supervisorScope getTracks.await(animeId)
.map { track ->
async {
val service = trackManager.getService(track.syncId)
return@async try {
if (service?.isLoggedIn == true) {
val updatedTrack = service.animeService.refresh(track.toDbTrack())
insertTrack.await(updatedTrack.toDomainTrack()!!)
syncEpisodeProgressWithTrack.await(animeId, track, service.animeService)
}
null
} catch (e: Throwable) {
service to e
}
}
}
.awaitAll()
.filterNotNull()
}
}
}

View file

@ -48,7 +48,7 @@ class DelayedAnimeTrackingUpdateJob(context: Context, workerParams: WorkerParame
.forEach { animeTrack ->
try {
val service = trackManager.getService(animeTrack.syncId)
if (service != null && service.isLogged) {
if (service != null && service.isLoggedIn) {
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${animeTrack.id}, last episode seen: ${animeTrack.lastEpisodeSeen}" }
service.animeService.update(animeTrack.toDbTrack(), true)
insertTrack.await(animeTrack)

View file

@ -0,0 +1,52 @@
package eu.kanade.domain.track.manga.interactor
import eu.kanade.domain.items.chapter.interactor.SyncChapterProgressWithTrack
import eu.kanade.domain.track.manga.model.toDbTrack
import eu.kanade.domain.track.manga.model.toDomainTrack
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.supervisorScope
import tachiyomi.domain.track.manga.interactor.GetMangaTracks
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
class RefreshMangaTracks(
private val getTracks: GetMangaTracks,
private val trackManager: TrackManager,
private val insertTrack: InsertMangaTrack,
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
) {
/**
* Fetches updated tracking data from all logged in trackers.
*
* @return Failed updates.
*/
suspend fun await(mangaId: Long): List<Pair<TrackService?, Throwable>> {
return supervisorScope {
return@supervisorScope getTracks.await(mangaId)
.map { track ->
async {
val service = trackManager.getService(track.syncId)
return@async try {
if (service?.isLoggedIn == true) {
val updatedTrack = service.mangaService.refresh(track.toDbTrack())
insertTrack.await(updatedTrack.toDomainTrack()!!)
syncChapterProgressWithTrack.await(
mangaId,
track,
service.mangaService,
)
}
null
} catch (e: Throwable) {
service to e
}
}
}
.awaitAll()
.filterNotNull()
}
}
}

View file

@ -48,7 +48,7 @@ class DelayedMangaTrackingUpdateJob(context: Context, workerParams: WorkerParame
.forEach { track ->
try {
val service = trackManager.getService(track.syncId)
if (service != null && service.isLogged) {
if (service != null && service.isLoggedIn) {
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" }
service.mangaService.update(track.toDbTrack(), true)
insertTrack.await(track)

View file

@ -127,7 +127,7 @@ private fun ColumnScope.FilterPage(
trackServices.map { service ->
val filterTracker by screenModel.libraryPreferences.filterTrackedAnime(service.id.toInt()).collectAsState()
TriStateItem(
label = stringResource(service.nameRes()),
label = service.name,
state = filterTracker,
onClick = { screenModel.toggleTracker(service.id.toInt()) },
)

View file

@ -127,7 +127,7 @@ private fun ColumnScope.FilterPage(
trackServices.map { service ->
val filterTracker by screenModel.libraryPreferences.filterTrackedManga(service.id.toInt()).collectAsState()
TriStateItem(
label = stringResource(service.nameRes()),
label = service.name,
state = filterTracker,
onClick = { screenModel.toggleTracker(service.id.toInt()) },
)

View file

@ -179,7 +179,7 @@ internal fun PreferenceItem(
TrackingPreferenceWidget(
service = this,
checked = uName.isNotEmpty(),
onClick = { if (isLogged) item.logout() else item.login() },
onClick = { if (isLoggedIn) item.logout() else item.login() },
)
}
}

View file

@ -115,9 +115,7 @@ object SettingsTrackingScreen : SearchableSettings {
if (enhancedMangaTrackers.second.isNotEmpty()) {
val missingMangaSourcesInfo = stringResource(
R.string.enhanced_services_not_installed,
enhancedMangaTrackers.second
.map { stringResource(it.nameRes()) }
.joinToString(),
enhancedMangaTrackers.second.joinToString { it.name },
)
enhancedMangaTrackerInfo += "\n\n$missingMangaSourcesInfo"
}
@ -139,43 +137,43 @@ object SettingsTrackingScreen : SearchableSettings {
title = stringResource(R.string.services),
preferenceItems = listOf(
Preference.PreferenceItem.TrackingPreference(
title = stringResource(trackManager.myAnimeList.nameRes()),
title = trackManager.myAnimeList.name,
service = trackManager.myAnimeList,
login = { context.openInBrowser(MyAnimeListApi.authUrl(), forceDefaultBrowser = true) },
logout = { dialog = LogoutDialog(trackManager.myAnimeList) },
),
Preference.PreferenceItem.TrackingPreference(
title = stringResource(trackManager.aniList.nameRes()),
title = trackManager.aniList.name,
service = trackManager.aniList,
login = { context.openInBrowser(AnilistApi.authUrl(), forceDefaultBrowser = true) },
logout = { dialog = LogoutDialog(trackManager.aniList) },
),
Preference.PreferenceItem.TrackingPreference(
title = stringResource(trackManager.kitsu.nameRes()),
title = trackManager.kitsu.name,
service = trackManager.kitsu,
login = { dialog = LoginDialog(trackManager.kitsu, R.string.email) },
logout = { dialog = LogoutDialog(trackManager.kitsu) },
),
Preference.PreferenceItem.TrackingPreference(
title = stringResource(trackManager.mangaUpdates.nameRes()),
title = trackManager.mangaUpdates.name,
service = trackManager.mangaUpdates,
login = { dialog = LoginDialog(trackManager.mangaUpdates, R.string.username) },
logout = { dialog = LogoutDialog(trackManager.mangaUpdates) },
),
Preference.PreferenceItem.TrackingPreference(
title = stringResource(trackManager.shikimori.nameRes()),
title = trackManager.shikimori.name,
service = trackManager.shikimori,
login = { context.openInBrowser(ShikimoriApi.authUrl(), forceDefaultBrowser = true) },
logout = { dialog = LogoutDialog(trackManager.shikimori) },
),
Preference.PreferenceItem.TrackingPreference(
title = stringResource(trackManager.simkl.nameRes()),
title = trackManager.simkl.name,
service = trackManager.simkl,
login = { context.openInBrowser(SimklApi.authUrl(), forceDefaultBrowser = true) },
logout = { dialog = LogoutDialog(trackManager.simkl) },
),
Preference.PreferenceItem.TrackingPreference(
title = stringResource(trackManager.bangumi.nameRes()),
title = trackManager.bangumi.name,
service = trackManager.bangumi,
login = { context.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true) },
logout = { dialog = LogoutDialog(trackManager.bangumi) },
@ -188,7 +186,7 @@ object SettingsTrackingScreen : SearchableSettings {
preferenceItems = enhancedMangaTrackers.first
.map { service ->
Preference.PreferenceItem.TrackingPreference(
title = stringResource(service.nameRes()),
title = service.name,
service = service,
login = { (service as EnhancedMangaTrackService).loginNoop() },
logout = service::logout,
@ -218,7 +216,7 @@ object SettingsTrackingScreen : SearchableSettings {
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResource(R.string.login_title, stringResource(service.nameRes())),
text = stringResource(R.string.login_title, service.name),
modifier = Modifier.weight(1f),
)
IconButton(onClick = onDismissRequest) {
@ -326,7 +324,7 @@ object SettingsTrackingScreen : SearchableSettings {
onDismissRequest = onDismissRequest,
title = {
Text(
text = stringResource(R.string.logout_title, stringResource(service.nameRes())),
text = stringResource(R.string.logout_title, service.name),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
)

View file

@ -40,7 +40,7 @@ fun TrackingPreferenceWidget(
) {
TrackLogoIcon(service)
Text(
text = stringResource(service.nameRes()),
text = service.name,
modifier = Modifier
.weight(1f)
.padding(horizontal = 16.dp),

View file

@ -11,7 +11,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.data.track.TrackService
import tachiyomi.presentation.core.util.clickableNoIndication
@ -36,7 +35,7 @@ fun TrackLogoIcon(
) {
Image(
painter = painterResource(service.getLogo()),
contentDescription = stringResource(service.nameRes()),
contentDescription = service.name,
)
}
}

View file

@ -149,7 +149,7 @@ object Migrations {
// Force MAL log out due to login flow change
// v52: switched from scraping to WebView
// v53: switched from WebView to OAuth
if (trackManager.myAnimeList.isLogged) {
if (trackManager.myAnimeList.isLoggedIn) {
trackManager.myAnimeList.logout()
context.toast(R.string.myanimelist_relogin)
}

View file

@ -69,8 +69,8 @@ class BackupFileValidator(
.distinct()
val missingTrackers = trackers
.mapNotNull { trackManager.getService(it.toLong()) }
.filter { !it.isLogged }
.map { context.getString(it.nameRes()) }
.filter { !it.isLoggedIn }
.map { it.name }
.sorted()
return Results(missingSources, missingTrackers)

View file

@ -18,18 +18,13 @@ import eu.kanade.domain.entries.anime.interactor.UpdateAnime
import eu.kanade.domain.entries.anime.model.copyFrom
import eu.kanade.domain.entries.anime.model.toSAnime
import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithSource
import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithTrackServiceTwoWay
import eu.kanade.domain.track.anime.model.toDbTrack
import eu.kanade.domain.track.anime.model.toDomainTrack
import eu.kanade.domain.track.anime.interactor.RefreshAnimeTracks
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.UnmeteredSource
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.model.UpdateStrategy
import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.shouldDownloadNewEpisodes
@ -44,7 +39,6 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import logcat.LogPriority
@ -59,7 +53,6 @@ import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime
import tachiyomi.domain.entries.anime.interactor.SetAnimeFetchInterval
import tachiyomi.domain.entries.anime.model.Anime
import tachiyomi.domain.entries.anime.model.toAnimeUpdate
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
import tachiyomi.domain.items.episode.model.Episode
import tachiyomi.domain.items.episode.model.NoEpisodesException
import tachiyomi.domain.library.anime.LibraryAnime
@ -73,8 +66,6 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_V
import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_OUTSIDE_RELEASE_PERIOD
import tachiyomi.domain.source.anime.model.AnimeSourceNotInstalledException
import tachiyomi.domain.source.anime.service.AnimeSourceManager
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@ -92,17 +83,13 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa
private val downloadPreferences: DownloadPreferences = Injekt.get()
private val libraryPreferences: LibraryPreferences = Injekt.get()
private val downloadManager: AnimeDownloadManager = Injekt.get()
private val trackManager: TrackManager = Injekt.get()
private val coverCache: AnimeCoverCache = Injekt.get()
private val getLibraryAnime: GetLibraryAnime = Injekt.get()
private val getAnime: GetAnime = Injekt.get()
private val updateAnime: UpdateAnime = Injekt.get()
private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get()
private val getCategories: GetAnimeCategories = Injekt.get()
private val syncEpisodesWithSource: SyncEpisodesWithSource = Injekt.get()
private val getTracks: GetAnimeTracks = Injekt.get()
private val insertTrack: InsertAnimeTrack = Injekt.get()
private val syncEpisodesWithTrackServiceTwoWay: SyncEpisodesWithTrackServiceTwoWay = Injekt.get()
private val refreshAnimeTracks: RefreshAnimeTracks = Injekt.get()
private val setAnimeFetchInterval: SetAnimeFetchInterval = Injekt.get()
private val notifier = AnimeLibraryUpdateNotifier(context)
@ -296,8 +283,7 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa
}
if (libraryPreferences.autoUpdateTrackers().get()) {
val loggedServices = trackManager.services.filter { it.isLogged }
updateTrackings(anime, loggedServices)
refreshAnimeTracks(anime.id)
}
}
}
@ -417,47 +403,24 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa
private suspend fun updateTrackings() {
coroutineScope {
var progressCount = 0
val loggedServices = trackManager.services.filter { it.isLogged }
animeToUpdate.forEach { libraryAnime ->
val anime = libraryAnime.anime
ensureActive()
val anime = libraryAnime.anime
notifier.showProgressNotification(listOf(anime), progressCount++, animeToUpdate.size)
// Update the tracking details.
updateTrackings(anime, loggedServices)
refreshAnimeTracks(anime.id)
}
notifier.cancelProgressNotification()
}
}
private suspend fun updateTrackings(anime: Anime, loggedServices: List<TrackService>) {
getTracks.await(anime.id)
.map { track ->
supervisorScope {
async {
val service = trackManager.getService(track.syncId)
if (service != null && service in loggedServices) {
try {
val updatedTrack = service.animeService.refresh(track.toDbTrack())
insertTrack.await(updatedTrack.toDomainTrack()!!)
if (service is EnhancedAnimeTrackService) {
val episodes = getEpisodeByAnimeId.await(anime.id)
syncEpisodesWithTrackServiceTwoWay.await(episodes, track, service.animeService)
}
} catch (e: Throwable) {
// Ignore errors and continue
logcat(LogPriority.ERROR, e)
}
}
}
}
}
.awaitAll()
private suspend fun refreshAnimeTracks(animeId: Long) {
refreshAnimeTracks.await(animeId).forEach { (_, e) ->
// Ignore errors and continue
logcat(LogPriority.ERROR, e)
}
}
private suspend fun withUpdateNotification(

View file

@ -18,16 +18,11 @@ import eu.kanade.domain.entries.manga.interactor.UpdateManga
import eu.kanade.domain.entries.manga.model.copyFrom
import eu.kanade.domain.entries.manga.model.toSManga
import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.track.manga.model.toDbTrack
import eu.kanade.domain.track.manga.model.toDomainTrack
import eu.kanade.domain.track.manga.interactor.RefreshMangaTracks
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.MangaCoverCache
import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.track.EnhancedMangaTrackService
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.model.UpdateStrategy
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.source.model.SManga
@ -44,7 +39,6 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import logcat.LogPriority
@ -59,7 +53,6 @@ import tachiyomi.domain.entries.manga.interactor.GetManga
import tachiyomi.domain.entries.manga.interactor.SetMangaFetchInterval
import tachiyomi.domain.entries.manga.model.Manga
import tachiyomi.domain.entries.manga.model.toMangaUpdate
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
import tachiyomi.domain.items.chapter.model.Chapter
import tachiyomi.domain.items.chapter.model.NoChaptersException
import tachiyomi.domain.library.manga.LibraryManga
@ -73,8 +66,6 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_V
import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_OUTSIDE_RELEASE_PERIOD
import tachiyomi.domain.source.manga.model.SourceNotInstalledException
import tachiyomi.domain.source.manga.service.MangaSourceManager
import tachiyomi.domain.track.manga.interactor.GetMangaTracks
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@ -92,17 +83,13 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa
private val downloadPreferences: DownloadPreferences = Injekt.get()
private val libraryPreferences: LibraryPreferences = Injekt.get()
private val downloadManager: MangaDownloadManager = Injekt.get()
private val trackManager: TrackManager = Injekt.get()
private val coverCache: MangaCoverCache = Injekt.get()
private val getLibraryManga: GetLibraryManga = Injekt.get()
private val getManga: GetManga = Injekt.get()
private val updateManga: UpdateManga = Injekt.get()
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get()
private val getCategories: GetMangaCategories = Injekt.get()
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get()
private val getTracks: GetMangaTracks = Injekt.get()
private val insertTrack: InsertMangaTrack = Injekt.get()
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get()
private val refreshMangaTracks: RefreshMangaTracks = Injekt.get()
private val setMangaFetchInterval: SetMangaFetchInterval = Injekt.get()
private val notifier = MangaLibraryUpdateNotifier(context)
@ -296,8 +283,7 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa
}
if (libraryPreferences.autoUpdateTrackers().get()) {
val loggedServices = trackManager.services.filter { it.isLogged }
updateTrackings(manga, loggedServices)
refreshMangaTracks(manga.id)
}
}
}
@ -417,47 +403,24 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa
private suspend fun updateTrackings() {
coroutineScope {
var progressCount = 0
val loggedServices = trackManager.services.filter { it.isLogged }
mangaToUpdate.forEach { libraryManga ->
val manga = libraryManga.manga
ensureActive()
val manga = libraryManga.manga
notifier.showProgressNotification(listOf(manga), progressCount++, mangaToUpdate.size)
// Update the tracking details.
updateTrackings(manga, loggedServices)
refreshMangaTracks(manga.id)
}
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.mangaService.refresh(track.toDbTrack())
insertTrack.await(updatedTrack.toDomainTrack()!!)
if (service is EnhancedMangaTrackService) {
val chapters = getChapterByMangaId.await(manga.id)
syncChaptersWithTrackServiceTwoWay.await(chapters, track, service.mangaService)
}
} catch (e: Throwable) {
// Ignore errors and continue
logcat(LogPriority.ERROR, e)
}
}
}
}
}
.awaitAll()
private suspend fun refreshMangaTracks(mangaId: Long) {
refreshMangaTracks.await(mangaId).forEach { (_, e) ->
// Ignore errors and continue
logcat(LogPriority.ERROR, e)
}
}
private suspend fun withUpdateNotification(

View file

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.track
import android.app.Application
import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithTrackServiceTwoWay
import eu.kanade.domain.items.episode.interactor.SyncEpisodeProgressWithTrack
import eu.kanade.domain.track.anime.model.toDbTrack
import eu.kanade.domain.track.anime.model.toDomainTrack
import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack
@ -22,6 +22,7 @@ import java.time.ZoneOffset
import tachiyomi.domain.track.anime.model.AnimeTrack as DomainAnimeTrack
private val insertTrack: InsertAnimeTrack by injectLazy()
private val syncEpisodeProgressWithTrack: SyncEpisodeProgressWithTrack by injectLazy()
interface AnimeTrackService {
@ -56,7 +57,8 @@ interface AnimeTrackService {
suspend fun refresh(track: AnimeTrack): AnimeTrack
suspend fun registerTracking(item: AnimeTrack, animeId: Long) {
// TODO: move this to an interactor, and update all trackers based on common data
suspend fun register(item: AnimeTrack, animeId: Long) {
item.anime_id = animeId
try {
withIOContext {
@ -68,6 +70,7 @@ interface AnimeTrackService {
insertTrack.await(track)
// TODO: merge into SyncChaptersWithTrackServiceTwoWay?
// Update episode progress if newer episodes marked seen locally
if (hasSeenEpisodes) {
val latestLocalSeenEpisodeNumber = allEpisodes
@ -102,9 +105,7 @@ interface AnimeTrackService {
}
}
if (this is EnhancedAnimeTrackService) {
Injekt.get<SyncEpisodesWithTrackServiceTwoWay>().await(allEpisodes, track, this@AnimeTrackService)
}
syncEpisodeProgressWithTrack.await(animeId, track, this@AnimeTrackService)
}
} catch (e: Throwable) {
withUIContext { Injekt.get<Application>().toast(e.message) }

View file

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.track
import android.app.Application
import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.items.chapter.interactor.SyncChapterProgressWithTrack
import eu.kanade.domain.track.manga.model.toDbTrack
import eu.kanade.domain.track.manga.model.toDomainTrack
import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack
@ -22,6 +22,7 @@ import java.time.ZoneOffset
import tachiyomi.domain.track.manga.model.MangaTrack as DomainTrack
private val insertTrack: InsertMangaTrack by injectLazy()
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack by injectLazy()
interface MangaTrackService {
@ -56,7 +57,8 @@ interface MangaTrackService {
suspend fun refresh(track: MangaTrack): MangaTrack
suspend fun registerTracking(item: MangaTrack, mangaId: Long) {
// TODO: move this to an interactor, and update all trackers based on common data
suspend fun register(item: MangaTrack, mangaId: Long) {
item.manga_id = mangaId
try {
withIOContext {
@ -68,6 +70,7 @@ interface MangaTrackService {
insertTrack.await(track)
// TODO: merge into SyncChaptersWithTrackServiceTwoWay?
// Update chapter progress if newer chapters marked read locally
if (hasReadChapters) {
val latestLocalReadChapterNumber = allChapters
@ -102,9 +105,7 @@ interface MangaTrackService {
}
}
if (this is EnhancedMangaTrackService) {
Injekt.get<SyncChaptersWithTrackServiceTwoWay>().await(allChapters, track, this@MangaTrackService)
}
syncChapterProgressWithTrack.await(mangaId, track, this@MangaTrackService)
}
} catch (e: Throwable) {
withUIContext { Injekt.get<Application>().toast(e.message) }

View file

@ -32,7 +32,7 @@ class TrackManager(context: Context) {
val kitsu = Kitsu(KITSU)
val shikimori = Shikimori(SHIKIMORI)
val bangumi = Bangumi(BANGUMI)
val komga = Komga(context, KOMGA)
val komga = Komga(KOMGA)
val mangaUpdates = MangaUpdates(MANGA_UPDATES)
val kavita = Kavita(context, KAVITA)
val suwayomi = Suwayomi(SUWAYOMI)
@ -42,5 +42,5 @@ class TrackManager(context: Context) {
fun getService(id: Long) = services.find { it.id == id }
fun hasLoggedServices() = services.any { it.isLogged }
fun hasLoggedServices() = services.any { it.isLoggedIn }
}

View file

@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy
abstract class TrackService(val id: Long) {
abstract class TrackService(val id: Long, val name: String) {
val trackPreferences: TrackPreferences by injectLazy()
val networkService: NetworkHelper by injectLazy()
@ -17,10 +17,6 @@ abstract class TrackService(val id: Long) {
open val client: OkHttpClient
get() = networkService.client
// Name of the manga sync service to display
@StringRes
abstract fun nameRes(): Int
// Application and remote support for reading dates
open val supportsReadingDates: Boolean = false
@ -40,7 +36,7 @@ abstract class TrackService(val id: Long) {
trackPreferences.setTrackCredentials(this, "", "")
}
open val isLogged: Boolean
open val isLoggedIn: Boolean
get() = getUsername().isNotEmpty() &&
getPassword().isNotEmpty()

View file

@ -19,7 +19,7 @@ import uy.kohesive.injekt.injectLazy
import tachiyomi.domain.track.anime.model.AnimeTrack as DomainAnimeTrack
import tachiyomi.domain.track.manga.model.MangaTrack as DomainTrack
class Anilist(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService, DeletableMangaTrackService, DeletableAnimeTrackService {
class Anilist(id: Long) : TrackService(id, "AniList"), MangaTrackService, AnimeTrackService, DeletableMangaTrackService, DeletableAnimeTrackService {
companion object {
const val READING = 1
@ -59,9 +59,6 @@ class Anilist(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService
}
}
@StringRes
override fun nameRes() = R.string.tracker_anilist
override fun getLogo() = R.drawable.ic_tracker_anilist
override fun getLogoColor() = Color.rgb(18, 25, 35)

View file

@ -15,7 +15,7 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy
class Bangumi(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService {
class Bangumi(id: Long) : TrackService(id, "Bangumi"), MangaTrackService, AnimeTrackService {
private val json: Json by injectLazy()
@ -23,9 +23,6 @@ class Bangumi(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService
private val api by lazy { BangumiApi(client, interceptor) }
@StringRes
override fun nameRes() = R.string.tracker_bangumi
override fun getScoreList(): List<String> {
return IntRange(0, 10).map(Int::toString)
}

View file

@ -15,7 +15,7 @@ import tachiyomi.domain.entries.manga.model.Manga
import java.security.MessageDigest
import tachiyomi.domain.track.manga.model.MangaTrack as DomainTrack
class Kavita(private val context: Context, id: Long) : TrackService(id), EnhancedMangaTrackService, MangaTrackService {
class Kavita(private val context: Context, id: Long) : TrackService(id, "Kavita"), EnhancedMangaTrackService, MangaTrackService {
companion object {
const val UNREAD = 1
@ -28,9 +28,6 @@ class Kavita(private val context: Context, id: Long) : TrackService(id), Enhance
private val interceptor by lazy { KavitaInterceptor(this) }
val api by lazy { KavitaApi(client, interceptor) }
@StringRes
override fun nameRes() = R.string.tracker_kavita
override fun getLogo(): Int = R.drawable.ic_tracker_kavita
override fun getLogoColor() = Color.rgb(74, 198, 148)

View file

@ -115,8 +115,8 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
}
private fun getLatestChapterRead(url: String): Float {
val serieId = getIdFromUrl(url)
val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$serieId"
val seriesId = getIdFromUrl(url)
val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$seriesId"
try {
with(json) {
authClient.newCall(GET(requestUrl)).execute().use {
@ -137,21 +137,21 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
suspend fun getTrackSearch(url: String): MangaTrackSearch = withIOContext {
try {
val serieDto: SeriesDto = with(json) {
val seriesDto: SeriesDto = with(json) {
authClient.newCall(GET(url))
.awaitSuccess()
.parseAs()
}
val track = serieDto.toTrack()
val track = seriesDto.toTrack()
track.apply {
cover_url = serieDto.thumbnail_url.toString()
cover_url = seriesDto.thumbnail_url.toString()
tracking_url = url
total_chapters = getTotalChapters(url)
title = serieDto.name
status = when (serieDto.pagesRead) {
serieDto.pages -> Kavita.COMPLETED
title = seriesDto.name
status = when (seriesDto.pagesRead) {
seriesDto.pages -> Kavita.COMPLETED
0 -> Kavita.UNREAD
else -> Kavita.READING
}

View file

@ -18,7 +18,7 @@ import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy
import java.text.DecimalFormat
class Kitsu(id: Long) : TrackService(id), AnimeTrackService, MangaTrackService, DeletableMangaTrackService, DeletableAnimeTrackService {
class Kitsu(id: Long) : TrackService(id, "Kitsu"), AnimeTrackService, MangaTrackService, DeletableMangaTrackService, DeletableAnimeTrackService {
companion object {
const val READING = 1
@ -30,9 +30,6 @@ class Kitsu(id: Long) : TrackService(id), AnimeTrackService, MangaTrackService,
const val PLAN_TO_WATCH = 15
}
@StringRes
override fun nameRes() = R.string.tracker_kitsu
override val supportsReadingDates: Boolean = true
private val json: Json by injectLazy()

View file

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.komga
import android.content.Context
import android.graphics.Color
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
@ -15,7 +14,7 @@ import okhttp3.OkHttpClient
import tachiyomi.domain.entries.manga.model.Manga
import tachiyomi.domain.track.manga.model.MangaTrack as DomainTrack
class Komga(private val context: Context, id: Long) : TrackService(id), EnhancedMangaTrackService, MangaTrackService {
class Komga(id: Long) : TrackService(id, "Komga"), EnhancedMangaTrackService, MangaTrackService {
companion object {
const val UNREAD = 1
@ -30,9 +29,6 @@ class Komga(private val context: Context, id: Long) : TrackService(id), Enhanced
val api by lazy { KomgaApi(client) }
@StringRes
override fun nameRes() = R.string.tracker_komga
override fun getLogo() = R.drawable.ic_tracker_komga
override fun getLogoColor() = Color.rgb(51, 37, 50)

View file

@ -17,7 +17,7 @@ import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.injectLazy
const val READLIST_API = "/api/v1/readlists"
private const val READLIST_API = "/api/v1/readlists"
class KomgaApi(private val client: OkHttpClient) {

View file

@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
import eu.kanade.tachiyomi.data.track.model.MangaTrackSearch
class MangaUpdates(id: Long) : TrackService(id), MangaTrackService, DeletableMangaTrackService {
class MangaUpdates(id: Long) : TrackService(id, "MangaUpdates"), MangaTrackService, DeletableMangaTrackService {
companion object {
const val READING_LIST = 0
@ -25,9 +25,6 @@ class MangaUpdates(id: Long) : TrackService(id), MangaTrackService, DeletableMan
private val api by lazy { MangaUpdatesApi(interceptor, client) }
@StringRes
override fun nameRes(): Int = R.string.tracker_manga_updates
override fun getLogo(): Int = R.drawable.ic_manga_updates
override fun getLogoColor(): Int = Color.rgb(146, 160, 173)

View file

@ -17,7 +17,7 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy
class MyAnimeList(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService, DeletableMangaTrackService, DeletableAnimeTrackService {
class MyAnimeList(id: Long) : TrackService(id, "MyAnimeList"), MangaTrackService, AnimeTrackService, DeletableMangaTrackService, DeletableAnimeTrackService {
companion object {
const val READING = 1
@ -39,9 +39,6 @@ class MyAnimeList(id: Long) : TrackService(id), MangaTrackService, AnimeTrackSer
private val interceptor by lazy { MyAnimeListInterceptor(this, getPassword()) }
private val api by lazy { MyAnimeListApi(client, interceptor) }
@StringRes
override fun nameRes() = R.string.tracker_myanimelist
override val supportsReadingDates: Boolean = true
override fun getLogo() = R.drawable.ic_tracker_mal

View file

@ -17,7 +17,7 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy
class Shikimori(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService, DeletableMangaTrackService, DeletableAnimeTrackService {
class Shikimori(id: Long) : TrackService(id, "Shikimori"), MangaTrackService, AnimeTrackService, DeletableMangaTrackService, DeletableAnimeTrackService {
companion object {
const val READING = 1
@ -34,9 +34,6 @@ class Shikimori(id: Long) : TrackService(id), MangaTrackService, AnimeTrackServi
private val api by lazy { ShikimoriApi(client, interceptor) }
@StringRes
override fun nameRes() = R.string.tracker_shikimori
override fun getScoreList(): List<String> {
return IntRange(0, 10).map(Int::toString)
}

View file

@ -212,7 +212,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
}
}
suspend fun findLibManga(track: MangaTrack, user_id: String): MangaTrack? {
suspend fun findLibManga(track: MangaTrack, userId: String): MangaTrack? {
return withIOContext {
val urlMangas = "$apiUrl/mangas".toUri().buildUpon()
.appendPath(track.media_id.toString())
@ -224,7 +224,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
}
val url = "$apiUrl/v2/user_rates".toUri().buildUpon()
.appendQueryParameter("user_id", user_id)
.appendQueryParameter("user_id", userId)
.appendQueryParameter("target_id", track.media_id.toString())
.appendQueryParameter("target_type", "Manga")
.build()

View file

@ -12,7 +12,7 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy
class Simkl(id: Long) : TrackService(id), AnimeTrackService {
class Simkl(id: Long) : TrackService(id, "Simkl"), AnimeTrackService {
companion object {
const val WATCHING = 1
@ -28,9 +28,6 @@ class Simkl(id: Long) : TrackService(id), AnimeTrackService {
private val api by lazy { SimklApi(client, interceptor) }
@StringRes
override fun nameRes() = R.string.tracker_simkl
override fun getScoreList(): List<String> {
return IntRange(0, 10).map(Int::toString)
}

View file

@ -12,12 +12,9 @@ import eu.kanade.tachiyomi.source.MangaSource
import tachiyomi.domain.entries.manga.model.Manga as DomainManga
import tachiyomi.domain.track.manga.model.MangaTrack as DomainTrack
class Suwayomi(id: Long) : TrackService(id), EnhancedMangaTrackService, MangaTrackService {
class Suwayomi(id: Long) : TrackService(id, "Suwayomi"), EnhancedMangaTrackService, MangaTrackService {
val api by lazy { TachideskApi() }
@StringRes
override fun nameRes() = R.string.tracker_suwayomi
override fun getLogo() = R.drawable.ic_tracker_suwayomi
override fun getLogoColor() = Color.rgb(255, 35, 35) // TODO

View file

@ -28,12 +28,12 @@ class TachideskApi {
private val network by injectLazy<NetworkHelper>()
private val json: Json by injectLazy()
val client: OkHttpClient =
private val client: OkHttpClient =
network.client.newBuilder()
.dns(Dns.SYSTEM) // don't use DNS over HTTPS as it breaks IP addressing
.build()
fun headersBuilder(): Headers.Builder = Headers.Builder().apply {
private fun headersBuilder(): Headers.Builder = Headers.Builder().apply {
add("User-Agent", network.defaultUserAgentProvider())
if (basePassword.isNotEmpty() && baseLogin.isNotEmpty()) {
val credentials = Credentials.basic(baseLogin, basePassword)
@ -41,7 +41,7 @@ class TachideskApi {
}
}
val headers: Headers by lazy { headersBuilder().build() }
private val headers: Headers by lazy { headersBuilder().build() }
private val baseUrl by lazy { getPrefBaseUrl() }
private val baseLogin by lazy { getPrefBaseLogin() }
@ -101,7 +101,7 @@ class TachideskApi {
return getTrackSearch(track.tracking_url)
}
val tachideskExtensionId by lazy {
private val tachideskExtensionId by lazy {
val key = "tachidesk/en/1"
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
@ -111,6 +111,10 @@ class TachideskApi {
Injekt.get<Application>().getSharedPreferences("source_$tachideskExtensionId", 0x0000)
}
private fun getPrefBaseUrl(): String = preferences.getString(ADDRESS_TITLE, ADDRESS_DEFAULT)!!
private fun getPrefBaseLogin(): String = preferences.getString(LOGIN_TITLE, LOGIN_DEFAULT)!!
private fun getPrefBasePassword(): String = preferences.getString(PASSWORD_TITLE, PASSWORD_DEFAULT)!!
companion object {
private const val ADDRESS_TITLE = "Server URL Address"
private const val ADDRESS_DEFAULT = ""
@ -119,8 +123,4 @@ class TachideskApi {
private const val PASSWORD_TITLE = "Password (Basic Auth)"
private const val PASSWORD_DEFAULT = ""
}
private fun getPrefBaseUrl(): String = preferences.getString(ADDRESS_TITLE, ADDRESS_DEFAULT)!!
private fun getPrefBaseLogin(): String = preferences.getString(LOGIN_TITLE, LOGIN_DEFAULT)!!
private fun getPrefBasePassword(): String = preferences.getString(PASSWORD_TITLE, PASSWORD_DEFAULT)!!
}

View file

@ -17,7 +17,7 @@ import eu.kanade.core.preference.asState
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.entries.anime.interactor.UpdateAnime
import eu.kanade.domain.entries.anime.model.toDomainAnime
import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithTrackServiceTwoWay
import eu.kanade.domain.items.episode.interactor.SyncEpisodeProgressWithTrack
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.anime.model.toDomainTrack
import eu.kanade.presentation.util.ioCoroutineScope
@ -52,7 +52,6 @@ import tachiyomi.domain.entries.anime.interactor.GetDuplicateLibraryAnime
import tachiyomi.domain.entries.anime.interactor.NetworkToLocalAnime
import tachiyomi.domain.entries.anime.model.Anime
import tachiyomi.domain.entries.anime.model.toAnimeUpdate
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
import tachiyomi.domain.items.episode.interactor.SetAnimeDefaultEpisodeFlags
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.source.anime.interactor.GetRemoteAnime
@ -74,17 +73,16 @@ class BrowseAnimeSourceScreenModel(
private val getRemoteAnime: GetRemoteAnime = Injekt.get(),
private val getDuplicateAnimelibAnime: GetDuplicateLibraryAnime = Injekt.get(),
private val getCategories: GetAnimeCategories = Injekt.get(),
private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get(),
private val setAnimeCategories: SetAnimeCategories = Injekt.get(),
private val setAnimeDefaultEpisodeFlags: SetAnimeDefaultEpisodeFlags = Injekt.get(),
private val getAnime: GetAnime = Injekt.get(),
private val networkToLocalAnime: NetworkToLocalAnime = Injekt.get(),
private val updateAnime: UpdateAnime = Injekt.get(),
private val insertTrack: InsertAnimeTrack = Injekt.get(),
private val syncEpisodesWithTrackServiceTwoWay: SyncEpisodesWithTrackServiceTwoWay = Injekt.get(),
private val syncEpisodeProgressWithTrack: SyncEpisodeProgressWithTrack = Injekt.get(),
) : StateScreenModel<BrowseAnimeSourceScreenModel.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)
@ -304,8 +302,7 @@ class BrowseAnimeSourceScreenModel(
(service as TrackService).animeService.bind(track)
insertTrack.await(track.toDomainTrack()!!)
val chapters = getEpisodeByAnimeId.await(anime.id)
syncEpisodesWithTrackServiceTwoWay.await(chapters, track.toDomainTrack()!!, service.animeService)
syncEpisodeProgressWithTrack.await(anime.id, track.toDomainTrack()!!, service.animeService)
}
} catch (e: Exception) {
logcat(

View file

@ -9,7 +9,6 @@ import androidx.compose.ui.unit.dp
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import androidx.paging.filter
import androidx.paging.map
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
@ -17,7 +16,7 @@ import eu.kanade.core.preference.asState
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.entries.manga.interactor.UpdateManga
import eu.kanade.domain.entries.manga.model.toDomainManga
import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.items.chapter.interactor.SyncChapterProgressWithTrack
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.manga.model.toDomainTrack
import eu.kanade.presentation.util.ioCoroutineScope
@ -34,7 +33,6 @@ import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
@ -52,7 +50,6 @@ import tachiyomi.domain.entries.manga.interactor.GetManga
import tachiyomi.domain.entries.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.entries.manga.model.Manga
import tachiyomi.domain.entries.manga.model.toMangaUpdate
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
import tachiyomi.domain.items.chapter.interactor.SetMangaDefaultChapterFlags
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.source.manga.interactor.GetRemoteManga
@ -74,17 +71,16 @@ class BrowseMangaSourceScreenModel(
private val getRemoteManga: GetRemoteManga = Injekt.get(),
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
private val getCategories: GetMangaCategories = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val setMangaCategories: SetMangaCategories = Injekt.get(),
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val insertTrack: InsertMangaTrack = Injekt.get(),
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack = Injekt.get(),
) : StateScreenModel<BrowseMangaSourceScreenModel.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)
@ -301,8 +297,7 @@ class BrowseMangaSourceScreenModel(
(service as TrackService).mangaService.bind(track)
insertTrack.await(track.toDomainTrack()!!)
val chapters = getChapterByMangaId.await(manga.id)
syncChaptersWithTrackServiceTwoWay.await(chapters, track.toDomainTrack()!!, service.mangaService)
syncChapterProgressWithTrack.await(manga.id, track.toDomainTrack()!!, service.mangaService)
}
} catch (e: Exception) {
logcat(LogPriority.WARN, e) { "Could not match manga: ${manga.title} with service $service" }

View file

@ -111,7 +111,7 @@ class AnimeScreenModel(
private val successState: State.Success?
get() = state.value as? State.Success
private val loggedServices by lazy { trackManager.services.filter { it.isLogged && it is AnimeTrackService } }
private val loggedServices by lazy { trackManager.services.filter { it.isLoggedIn && it is AnimeTrackService } }
val anime: Anime?
get() = successState?.anime
@ -336,7 +336,7 @@ class AnimeScreenModel(
launchIO {
try {
service.match(anime)?.let { track ->
(service as AnimeTrackService).registerTracking(track, animeId)
(service as AnimeTrackService).register(track, animeId)
}
} catch (e: Exception) {
logcat(LogPriority.WARN, e) {

View file

@ -39,9 +39,8 @@ import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithTrackServiceTwoWay
import eu.kanade.domain.track.anime.interactor.RefreshAnimeTracks
import eu.kanade.domain.track.anime.model.toDbTrack
import eu.kanade.domain.track.anime.model.toDomainTrack
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.track.TrackDateSelector
import eu.kanade.presentation.track.TrackItemSelector
@ -72,11 +71,9 @@ import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.entries.anime.interactor.GetAnime
import tachiyomi.domain.entries.anime.interactor.GetAnimeWithEpisodes
import tachiyomi.domain.source.anime.service.AnimeSourceManager
import tachiyomi.domain.track.anime.interactor.DeleteAnimeTrack
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
import tachiyomi.domain.track.anime.model.AnimeTrack
import tachiyomi.presentation.core.components.material.AlertDialogContent
import tachiyomi.presentation.core.components.material.padding
@ -210,7 +207,7 @@ data class AnimeTrackInfoDialogHomeScreen(
val anime = Injekt.get<GetAnime>().await(animeId) ?: return@launchNonCancellable
try {
val matchResult = item.service.match(anime) ?: throw Exception()
item.service.animeService.registerTracking(matchResult, animeId)
item.service.animeService.register(matchResult, animeId)
} catch (e: Exception) {
withUIContext { Injekt.get<Application>().toast(R.string.error_no_match) }
}
@ -218,49 +215,30 @@ data class AnimeTrackInfoDialogHomeScreen(
}
private suspend fun refreshTrackers() {
val insertAnimeTrack = Injekt.get<InsertAnimeTrack>()
val getAnimeWithEpisodes = Injekt.get<GetAnimeWithEpisodes>()
val syncTwoWayService = Injekt.get<SyncEpisodesWithTrackServiceTwoWay>()
val refreshTracks = Injekt.get<RefreshAnimeTracks>()
val context = Injekt.get<Application>()
try {
val trackItems = getTracks.await(animeId).mapToTrackItem()
for (trackItem in trackItems) {
try {
val track = trackItem.track ?: continue
val domainAnimeTrack = trackItem.service.animeService.refresh(track.toDbTrack()).toDomainTrack() ?: continue
insertAnimeTrack.await(domainAnimeTrack)
if (trackItem.service is EnhancedAnimeTrackService) {
val allEpisodes = getAnimeWithEpisodes.awaitEpisodes(animeId)
syncTwoWayService.await(allEpisodes, domainAnimeTrack, trackItem.service.animeService)
}
} catch (e: Exception) {
logcat(
LogPriority.ERROR,
e,
) { "Failed to refresh track data mangaId=$animeId for service ${trackItem.service.id}" }
withUIContext {
context.toast(
context.getString(
R.string.track_error,
context.getString(trackItem.service.nameRes()),
e.message,
),
)
}
refreshTracks.await(animeId)
.filter { it.first != null }
.forEach { (track, e) ->
logcat(LogPriority.ERROR, e) {
"Failed to refresh track data mangaId=$animeId for service ${track!!.id}"
}
withUIContext {
context.toast(
context.getString(
R.string.track_error,
track!!.name,
e.message,
),
)
}
}
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to refresh track data animeId=$animeId" }
withUIContext { context.toast(e.message) }
}
}
private fun List<AnimeTrack>.mapToTrackItem(): List<AnimeTrackItem> {
val dbTracks = map { it.toDbTrack() }
val loggedServices = Injekt.get<TrackManager>().services.filter {
it.isLogged && it is AnimeTrackService
it.isLoggedIn && it is AnimeTrackService
}
val source = Injekt.get<AnimeSourceManager>().getOrStub(sourceId)
return loggedServices
@ -594,7 +572,7 @@ private data class TrackDateRemoverScreen(
)
},
text = {
val serviceName = stringResource(sm.getServiceNameRes())
val serviceName = sm.getServiceName()
Text(
text = if (start) {
stringResource(R.string.track_remove_start_date_conf_text, serviceName)
@ -631,7 +609,7 @@ private data class TrackDateRemoverScreen(
private val start: Boolean,
) : ScreenModel {
fun getServiceNameRes() = service.nameRes()
fun getServiceName() = service.name
fun removeDate() {
coroutineScope.launchNonCancellable {
@ -716,7 +694,7 @@ data class TrackServiceSearchScreen(
}
fun registerTracking(item: AnimeTrackSearch) {
coroutineScope.launchNonCancellable { service.animeService.registerTracking(item, animeId) }
coroutineScope.launchNonCancellable { service.animeService.register(item, animeId) }
}
fun updateSelection(selected: AnimeTrackSearch) {
@ -747,7 +725,7 @@ private data class TrackAnimeServiceRemoveScreen(
service = Injekt.get<TrackManager>().getService(serviceId)!!,
)
}
val serviceName = stringResource(sm.getServiceNameRes())
val serviceName = sm.getServiceName()
var removeRemoteTrack by remember { mutableStateOf(false) }
AlertDialogContent(
modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars),
@ -812,7 +790,7 @@ private data class TrackAnimeServiceRemoveScreen(
private val deleteTrack: DeleteAnimeTrack = Injekt.get(),
) : ScreenModel {
fun getServiceNameRes() = service.nameRes()
fun getServiceName() = service.name
fun isServiceDeletable() = service is DeletableAnimeTrackService

View file

@ -107,7 +107,7 @@ class MangaScreenModel(
private val successState: State.Success?
get() = state.value as? State.Success
private val loggedServices by lazy { trackManager.services.filter { it.isLogged && it is MangaTrackService } }
private val loggedServices by lazy { trackManager.services.filter { it.isLoggedIn && it is MangaTrackService } }
val manga: Manga?
get() = successState?.manga
@ -333,7 +333,7 @@ class MangaScreenModel(
launchIO {
try {
service.match(manga)?.let { track ->
(service as MangaTrackService).registerTracking(track, mangaId)
(service as MangaTrackService).register(track, mangaId)
}
} catch (e: Exception) {
logcat(LogPriority.WARN, e) {

View file

@ -39,9 +39,8 @@ import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.track.manga.interactor.RefreshMangaTracks
import eu.kanade.domain.track.manga.model.toDbTrack
import eu.kanade.domain.track.manga.model.toDomainTrack
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.track.TrackDateSelector
import eu.kanade.presentation.track.TrackItemSelector
@ -72,11 +71,9 @@ import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.entries.manga.interactor.GetManga
import tachiyomi.domain.entries.manga.interactor.GetMangaWithChapters
import tachiyomi.domain.source.manga.service.MangaSourceManager
import tachiyomi.domain.track.manga.interactor.DeleteMangaTrack
import tachiyomi.domain.track.manga.interactor.GetMangaTracks
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
import tachiyomi.domain.track.manga.model.MangaTrack
import tachiyomi.presentation.core.components.material.AlertDialogContent
import tachiyomi.presentation.core.components.material.padding
@ -210,7 +207,7 @@ data class MangaTrackInfoDialogHomeScreen(
val manga = Injekt.get<GetManga>().await(mangaId) ?: return@launchNonCancellable
try {
val matchResult = item.service.match(manga) ?: throw Exception()
item.service.mangaService.registerTracking(matchResult, mangaId)
item.service.mangaService.register(matchResult, mangaId)
} catch (e: Exception) {
withUIContext { Injekt.get<Application>().toast(R.string.error_no_match) }
}
@ -218,48 +215,30 @@ data class MangaTrackInfoDialogHomeScreen(
}
private suspend fun refreshTrackers() {
val insertTrack = Injekt.get<InsertMangaTrack>()
val getMangaWithChapters = Injekt.get<GetMangaWithChapters>()
val syncTwoWayService = Injekt.get<SyncChaptersWithTrackServiceTwoWay>()
val refreshTracks = Injekt.get<RefreshMangaTracks>()
val context = Injekt.get<Application>()
try {
val trackItems = getTracks.await(mangaId).mapToTrackItem()
for (trackItem in trackItems) {
try {
val track = trackItem.track ?: continue
val domainMangaTrack = trackItem.service.mangaService.refresh(track.toDbTrack()).toDomainTrack() ?: continue
insertTrack.await(domainMangaTrack)
if (trackItem.service is EnhancedMangaTrackService) {
val allChapters = getMangaWithChapters.awaitChapters(mangaId)
syncTwoWayService.await(allChapters, domainMangaTrack, trackItem.service.mangaService)
}
} catch (e: Exception) {
logcat(
LogPriority.ERROR,
e,
) { "Failed to refresh track data mangaId=$mangaId for service ${trackItem.service.id}" }
withUIContext {
context.toast(
context.getString(
R.string.track_error,
context.getString(trackItem.service.nameRes()),
e.message,
),
)
}
refreshTracks.await(mangaId)
.filter { it.first != null }
.forEach { (track, e) ->
logcat(LogPriority.ERROR, e) {
"Failed to refresh track data mangaId=$mangaId for service ${track!!.name}"
}
withUIContext {
context.toast(
context.getString(
R.string.track_error,
track!!.name,
e.message,
),
)
}
}
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to refresh track data mangaId=$mangaId" }
withUIContext { context.toast(e.message) }
}
}
private fun List<MangaTrack>.mapToTrackItem(): List<MangaTrackItem> {
val loggedServices = Injekt.get<TrackManager>().services.filter {
it.isLogged && it is MangaTrackService
it.isLoggedIn && it is MangaTrackService
}
val source = Injekt.get<MangaSourceManager>().getOrStub(sourceId)
return loggedServices
@ -593,7 +572,7 @@ private data class TrackDateRemoverScreen(
)
},
text = {
val serviceName = stringResource(sm.getServiceNameRes())
val serviceName = sm.getServiceName()
Text(
text = if (start) {
stringResource(R.string.track_remove_start_date_conf_text, serviceName)
@ -630,7 +609,7 @@ private data class TrackDateRemoverScreen(
private val start: Boolean,
) : ScreenModel {
fun getServiceNameRes() = service.nameRes()
fun getServiceName() = service.name
fun removeDate() {
coroutineScope.launchNonCancellable {
@ -715,7 +694,7 @@ data class TrackServiceSearchScreen(
}
fun registerTracking(item: MangaTrackSearch) {
coroutineScope.launchNonCancellable { service.mangaService.registerTracking(item, mangaId) }
coroutineScope.launchNonCancellable { service.mangaService.register(item, mangaId) }
}
fun updateSelection(selected: MangaTrackSearch) {
@ -746,7 +725,7 @@ private data class TrackMangaServiceRemoveScreen(
service = Injekt.get<TrackManager>().getService(serviceId)!!,
)
}
val serviceName = stringResource(sm.getServiceNameRes())
val serviceName = sm.getServiceName()
var removeRemoteTrack by remember { mutableStateOf(false) }
AlertDialogContent(
modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars),
@ -811,7 +790,7 @@ private data class TrackMangaServiceRemoveScreen(
private val deleteTrack: DeleteMangaTrack = Injekt.get(),
) : ScreenModel {
fun getServiceNameRes() = service.nameRes()
fun getServiceName() = service.name
fun isServiceDeletable() = service is DeletableMangaTrackService

View file

@ -373,7 +373,7 @@ class AnimeLibraryScreenModel(
* @return map of track id with the filter value
*/
private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
val loggedServices = trackManager.services.filter { it.isLogged && it is AnimeTrackService }
val loggedServices = trackManager.services.filter { it.isLoggedIn && it is AnimeTrackService }
return if (loggedServices.isNotEmpty()) {
val prefFlows = loggedServices
.map { libraryPreferences.filterTrackedAnime(it.id.toInt()).changes() }

View file

@ -26,7 +26,7 @@ class AnimeLibrarySettingsScreenModel(
) : ScreenModel {
val trackServices
get() = trackManager.services.filter { it.isLogged }
get() = trackManager.services.filter { it.isLoggedIn }
fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) {
preference(libraryPreferences).getAndSet {

View file

@ -367,7 +367,7 @@ class MangaLibraryScreenModel(
* @return map of track id with the filter value
*/
private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
val loggedServices = trackManager.services.filter { it.isLogged && it is MangaTrackService }
val loggedServices = trackManager.services.filter { it.isLoggedIn && it is MangaTrackService }
return if (loggedServices.isNotEmpty()) {
val prefFlows = loggedServices
.map { libraryPreferences.filterTrackedManga(it.id.toInt()).changes() }

View file

@ -26,7 +26,7 @@ class MangaLibrarySettingsScreenModel(
) : ScreenModel {
val trackServices
get() = trackManager.services.filter { it.isLogged }
get() = trackManager.services.filter { it.isLoggedIn }
fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) {
preference(libraryPreferences).getAndSet {

View file

@ -481,7 +481,7 @@ class ExternalIntents {
getTracks.await(anime.id)
.mapNotNull { track ->
val service = trackManager.getService(track.syncId)
if (service != null && service.isLogged &&
if (service != null && service.isLoggedIn &&
service is AnimeTrackService && episodeNumber > track.lastEpisodeSeen
) {
val updatedTrack = track.copy(lastEpisodeSeen = episodeNumber)

View file

@ -575,7 +575,7 @@ class PlayerViewModel @JvmOverloads constructor(
getTracks.await(anime.id)
.mapNotNull { track ->
val service = trackManager.getService(track.syncId)
if (service != null && service.isLogged && episodeSeen > track.lastEpisodeSeen) {
if (service != null && service.isLoggedIn && episodeSeen > track.lastEpisodeSeen) {
val updatedTrack = track.copy(lastEpisodeSeen = episodeSeen)
// We want these to execute even if the presenter is destroyed and leaks

View file

@ -869,7 +869,7 @@ class ReaderViewModel @JvmOverloads constructor(
getTracks.await(manga.id)
.mapNotNull { track ->
val service = trackManager.getService(track.syncId)
if (service != null && service.isLogged && chapterRead > track.lastChapterRead) {
if (service != null && service.isLoggedIn && chapterRead > track.lastChapterRead) {
val updatedTrack = track.copy(lastChapterRead = chapterRead)
// We want these to execute even if the presenter is destroyed and leaks

View file

@ -37,7 +37,7 @@ class AnimeStatsScreenModel(
private val trackManager: TrackManager = Injekt.get(),
) : StateScreenModel<StatsScreenState>(StatsScreenState.Loading) {
private val loggedServices by lazy { trackManager.services.fastFilter { it.isLogged && it is AnimeTrackService } }
private val loggedServices by lazy { trackManager.services.fastFilter { it.isLoggedIn && it is AnimeTrackService } }
init {
coroutineScope.launchIO {

View file

@ -37,7 +37,7 @@ class MangaStatsScreenModel(
private val trackManager: TrackManager = Injekt.get(),
) : StateScreenModel<StatsScreenState>(StatsScreenState.Loading) {
private val loggedServices by lazy { trackManager.services.fastFilter { it.isLogged && it is MangaTrackService } }
private val loggedServices by lazy { trackManager.services.fastFilter { it.isLoggedIn && it is MangaTrackService } }
init {
coroutineScope.launchIO {

View file

@ -6,7 +6,7 @@ dependencies {
implementation(androidxLibs.gradle)
implementation(kotlinLibs.gradle)
implementation(libs.kotlinter)
implementation(libs.ktlint)
implementation(gradleApi())
}

View file

@ -1,21 +1,15 @@
import org.jmailen.gradle.kotlinter.KotlinterExtension
import org.jmailen.gradle.kotlinter.KotlinterPlugin
import org.jlleitschuh.gradle.ktlint.KtlintExtension
import org.jlleitschuh.gradle.ktlint.KtlintPlugin
apply<KotlinterPlugin>()
apply<KtlintPlugin>()
extensions.configure<KotlinterExtension>("kotlinter") {
experimentalRules = true
extensions.configure<KtlintExtension>("ktlint") {
version.set("0.50.0")
android.set(true)
enableExperimentalRules.set(true)
disabledRules = arrayOf(
"experimental:argument-list-wrapping", // Doesn't play well with Android Studio
"filename", // Often broken to give a more general name
)
}
tasks {
named<DefaultTask>("preBuild").configure {
if (!System.getenv("CI").toBoolean())
dependsOn("formatKotlin")
filter {
exclude("**/generated/**")
}
}

View file

@ -9,11 +9,12 @@ class AnimeUpdatesRepositoryImpl(
private val databaseHandler: AnimeDatabaseHandler,
) : AnimeUpdatesRepository {
override suspend fun awaitWithSeen(seen: Boolean, after: Long): List<AnimeUpdatesWithRelations> {
override suspend fun awaitWithSeen(seen: Boolean, after: Long, limit: Long): List<AnimeUpdatesWithRelations> {
return databaseHandler.awaitList {
animeupdatesViewQueries.getUpdatesBySeenStatus(
seen = seen,
after = after,
limit = limit,
mapper = animeUpdateWithRelationMapper,
)
}
@ -25,11 +26,12 @@ class AnimeUpdatesRepositoryImpl(
}
}
override fun subscribeWithSeen(seen: Boolean, after: Long): Flow<List<AnimeUpdatesWithRelations>> {
override fun subscribeWithSeen(seen: Boolean, after: Long, limit: Long): Flow<List<AnimeUpdatesWithRelations>> {
return databaseHandler.subscribeToList {
animeupdatesViewQueries.getUpdatesBySeenStatus(
seen = seen,
after = after,
limit = limit,
mapper = animeUpdateWithRelationMapper,
)
}

View file

@ -9,11 +9,12 @@ class MangaUpdatesRepositoryImpl(
private val databaseHandler: MangaDatabaseHandler,
) : MangaUpdatesRepository {
override suspend fun awaitWithRead(read: Boolean, after: Long): List<MangaUpdatesWithRelations> {
override suspend fun awaitWithRead(read: Boolean, after: Long, limit: Long): List<MangaUpdatesWithRelations> {
return databaseHandler.awaitList {
updatesViewQueries.getUpdatesByReadStatus(
read = read,
after = after,
limit = limit,
mapper = mangaUpdateWithRelationMapper,
)
}
@ -25,11 +26,12 @@ class MangaUpdatesRepositoryImpl(
}
}
override fun subscribeWithRead(read: Boolean, after: Long): Flow<List<MangaUpdatesWithRelations>> {
override fun subscribeWithRead(read: Boolean, after: Long, limit: Long): Flow<List<MangaUpdatesWithRelations>> {
return databaseHandler.subscribeToList {
updatesViewQueries.getUpdatesByReadStatus(
read = read,
after = after,
limit = limit,
mapper = mangaUpdateWithRelationMapper,
)
}

View file

@ -30,4 +30,6 @@ getUpdatesByReadStatus:
SELECT *
FROM updatesView
WHERE read = :read
AND dateUpload > :after;
AND dateUpload > :after
LIMIT :limit;

View file

@ -31,4 +31,6 @@ getUpdatesBySeenStatus:
SELECT *
FROM animeupdatesView
WHERE seen = :seen
AND dateUpload > :after;
AND dateUpload > :after
LIMIT :limit;

View file

@ -10,7 +10,7 @@ class GetAnimeUpdates(
) {
suspend fun await(seen: Boolean, after: Long): List<AnimeUpdatesWithRelations> {
return repository.awaitWithSeen(seen, after)
return repository.awaitWithSeen(seen, after, limit = 500)
}
fun subscribe(calendar: Calendar): Flow<List<AnimeUpdatesWithRelations>> {
@ -18,6 +18,6 @@ class GetAnimeUpdates(
}
fun subscribe(seen: Boolean, after: Long): Flow<List<AnimeUpdatesWithRelations>> {
return repository.subscribeWithSeen(seen, after)
return repository.subscribeWithSeen(seen, after, limit = 500)
}
}

View file

@ -5,9 +5,9 @@ import tachiyomi.domain.updates.anime.model.AnimeUpdatesWithRelations
interface AnimeUpdatesRepository {
suspend fun awaitWithSeen(seen: Boolean, after: Long): List<AnimeUpdatesWithRelations>
suspend fun awaitWithSeen(seen: Boolean, after: Long, limit: Long): List<AnimeUpdatesWithRelations>
fun subscribeAllAnimeUpdates(after: Long, limit: Long): Flow<List<AnimeUpdatesWithRelations>>
fun subscribeWithSeen(seen: Boolean, after: Long): Flow<List<AnimeUpdatesWithRelations>>
fun subscribeWithSeen(seen: Boolean, after: Long, limit: Long): Flow<List<AnimeUpdatesWithRelations>>
}

View file

@ -10,7 +10,7 @@ class GetMangaUpdates(
) {
suspend fun await(read: Boolean, after: Long): List<MangaUpdatesWithRelations> {
return repository.awaitWithRead(read, after)
return repository.awaitWithRead(read, after, limit = 500)
}
fun subscribe(calendar: Calendar): Flow<List<MangaUpdatesWithRelations>> {
@ -18,6 +18,6 @@ class GetMangaUpdates(
}
fun subscribe(read: Boolean, after: Long): Flow<List<MangaUpdatesWithRelations>> {
return repository.subscribeWithRead(read, after)
return repository.subscribeWithRead(read, after, limit = 500)
}
}

View file

@ -5,9 +5,9 @@ import tachiyomi.domain.updates.manga.model.MangaUpdatesWithRelations
interface MangaUpdatesRepository {
suspend fun awaitWithRead(read: Boolean, after: Long): List<MangaUpdatesWithRelations>
suspend fun awaitWithRead(read: Boolean, after: Long, limit: Long): List<MangaUpdatesWithRelations>
fun subscribeAllMangaUpdates(after: Long, limit: Long): Flow<List<MangaUpdatesWithRelations>>
fun subscribeWithRead(read: Boolean, after: Long): Flow<List<MangaUpdatesWithRelations>>
fun subscribeWithRead(read: Boolean, after: Long, limit: Long): Flow<List<MangaUpdatesWithRelations>>
}

View file

@ -28,7 +28,7 @@ guava = "com.google.guava:guava:32.1.2-android"
paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" }
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.0-beta04"
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.0-beta05"
test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha01"
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha01"
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0-alpha04"

View file

@ -1,6 +1,6 @@
[versions]
kotlin_version = "1.9.0"
serialization_version = "1.5.1"
serialization_version = "1.6.0"
xml_serialization_version = "0.86.1"
[libraries]

View file

@ -32,7 +32,7 @@ junrar = "com.github.junrar:junrar:7.5.5"
sqlite-framework = { module = "androidx.sqlite:sqlite-framework", version.ref = "sqlite" }
sqlite-ktx = { module = "androidx.sqlite:sqlite-ktx", version.ref = "sqlite" }
sqlite-android = "com.github.requery:sqlite-android:3.42.0"
sqlite-android = "com.github.requery:sqlite-android:3.43.0"
preferencektx = "androidx.preference:preference-ktx:1.2.1"
@ -89,7 +89,7 @@ voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.
voyager-tab-navigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" }
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" }
kotlinter = "org.jmailen.gradle:kotlinter-gradle:3.13.0"
ktlint = "org.jlleitschuh.gradle:ktlint-gradle:11.5.1"
aniyomi-mpv = "com.github.aniyomiorg:aniyomi-mpv-lib:1.10.n"
ffmpeg-kit = "com.github.jmir1:ffmpeg-kit:1.10"

View file

@ -682,16 +682,6 @@
<string name="are_you_sure">Are you sure?</string>
<!-- Tracking Screen -->
<string name="tracker_anilist" translatable="false">AniList</string>
<string name="tracker_myanimelist" translatable="false">MyAnimeList</string>
<string name="tracker_kitsu" translatable="false">Kitsu</string>
<string name="tracker_komga" translatable="false">Komga</string>
<string name="tracker_bangumi" translatable="false">Bangumi</string>
<string name="tracker_shikimori" translatable="false">Shikimori</string>
<string name="tracker_simkl" translatable="false">Simkl</string>
<string name="tracker_manga_updates" translatable="false">MangaUpdates</string>
<string name="tracker_kavita" translatable="false">Kavita</string>
<string name="tracker_suwayomi" translatable="false">Suwayomi</string>
<string name="manga_tracking_tab">Tracking</string>
<plurals name="num_trackers">
<item quantity="one">%d tracker</item>

View file

@ -37,14 +37,14 @@ import tachiyomi.presentation.widget.util.appWidgetBackgroundRadius
import tachiyomi.presentation.widget.util.calculateRowAndColumnCount
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.util.Calendar
import java.util.Date
class AnimeUpdatesGridGlanceWidget : GlanceAppWidget() {
private val app: Application by injectLazy()
private val preferences: SecurityPreferences by injectLazy()
class AnimeUpdatesGridGlanceWidget(
private val context: Context = Injekt.get<Application>(),
private val getUpdates: GetAnimeUpdates = Injekt.get(),
private val preferences: SecurityPreferences = Injekt.get(),
) : GlanceAppWidget() {
private var data: List<Pair<Long, Bitmap?>>? = null
@ -64,23 +64,22 @@ class AnimeUpdatesGridGlanceWidget : GlanceAppWidget() {
}
}
private suspend fun loadData(list: List<AnimeUpdatesWithRelations>? = null) {
withIOContext {
val manager = GlanceAppWidgetManager(app)
val ids = manager.getGlanceIds(this@AnimeUpdatesGridGlanceWidget::class.java)
if (ids.isEmpty()) return@withIOContext
private suspend fun loadData() {
val manager = GlanceAppWidgetManager(context)
val ids = manager.getGlanceIds(this@AnimeUpdatesGridGlanceWidget::class.java)
if (ids.isEmpty()) return
val processList = list
?: Injekt.get<GetAnimeUpdates>().await(
seen = false,
after = DateLimit.timeInMillis,
)
withIOContext {
val updates = getUpdates.await(
seen = false,
after = DateLimit.timeInMillis,
)
val (rowCount, columnCount) = ids
.flatMap { manager.getAppWidgetSizes(it) }
.maxBy { it.height.value * it.width.value }
.calculateRowAndColumnCount()
data = prepareList(processList, rowCount * columnCount)
data = prepareList(updates, rowCount * columnCount)
}
}
@ -88,12 +87,12 @@ class AnimeUpdatesGridGlanceWidget : GlanceAppWidget() {
// Resize to cover size
val widthPx = CoverWidth.value.toInt().dpToPx
val heightPx = CoverHeight.value.toInt().dpToPx
val roundPx = app.resources.getDimension(R.dimen.appwidget_inner_radius)
val roundPx = context.resources.getDimension(R.dimen.appwidget_inner_radius)
return processList
.distinctBy { it.animeId }
.take(take)
.map { animeupdatesView ->
val request = ImageRequest.Builder(app)
val request = ImageRequest.Builder(context)
.data(
AnimeCover(
animeId = animeupdatesView.animeId,
@ -115,7 +114,7 @@ class AnimeUpdatesGridGlanceWidget : GlanceAppWidget() {
}
}
.build()
Pair(animeupdatesView.animeId, app.imageLoader.executeBlocking(request).drawable?.toBitmap())
Pair(animeupdatesView.animeId, context.imageLoader.executeBlocking(request).drawable?.toBitmap())
}
}

View file

@ -37,14 +37,14 @@ import tachiyomi.presentation.widget.util.appWidgetBackgroundRadius
import tachiyomi.presentation.widget.util.calculateRowAndColumnCount
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.util.Calendar
import java.util.Date
class MangaUpdatesGridGlanceWidget : GlanceAppWidget() {
private val app: Application by injectLazy()
private val preferences: SecurityPreferences by injectLazy()
class MangaUpdatesGridGlanceWidget(
private val context: Context = Injekt.get<Application>(),
private val getUpdates: GetMangaUpdates = Injekt.get(),
private val preferences: SecurityPreferences = Injekt.get(),
) : GlanceAppWidget() {
private var data: List<Pair<Long, Bitmap?>>? = null
@ -64,23 +64,22 @@ class MangaUpdatesGridGlanceWidget : GlanceAppWidget() {
}
}
private suspend fun loadData(list: List<MangaUpdatesWithRelations>? = null) {
withIOContext {
val manager = GlanceAppWidgetManager(app)
val ids = manager.getGlanceIds(this@MangaUpdatesGridGlanceWidget::class.java)
if (ids.isEmpty()) return@withIOContext
private suspend fun loadData() {
val manager = GlanceAppWidgetManager(context)
val ids = manager.getGlanceIds(this@MangaUpdatesGridGlanceWidget::class.java)
if (ids.isEmpty()) return
val processList = list
?: Injekt.get<GetMangaUpdates>().await(
read = false,
after = DateLimit.timeInMillis,
)
withIOContext {
val updates = getUpdates.await(
read = false,
after = DateLimit.timeInMillis,
)
val (rowCount, columnCount) = ids
.flatMap { manager.getAppWidgetSizes(it) }
.maxBy { it.height.value * it.width.value }
.calculateRowAndColumnCount()
data = prepareList(processList, rowCount * columnCount)
data = prepareList(updates, rowCount * columnCount)
}
}
@ -88,12 +87,12 @@ class MangaUpdatesGridGlanceWidget : GlanceAppWidget() {
// Resize to cover size
val widthPx = CoverWidth.value.toInt().dpToPx
val heightPx = CoverHeight.value.toInt().dpToPx
val roundPx = app.resources.getDimension(R.dimen.appwidget_inner_radius)
val roundPx = context.resources.getDimension(R.dimen.appwidget_inner_radius)
return processList
.distinctBy { it.mangaId }
.take(take)
.map { updatesView ->
val request = ImageRequest.Builder(app)
val request = ImageRequest.Builder(context)
.data(
MangaCover(
mangaId = updatesView.mangaId,
@ -115,7 +114,7 @@ class MangaUpdatesGridGlanceWidget : GlanceAppWidget() {
}
}
.build()
Pair(updatesView.mangaId, app.imageLoader.executeBlocking(request).drawable?.toBitmap())
Pair(updatesView.mangaId, context.imageLoader.executeBlocking(request).drawable?.toBitmap())
}
}