im going to merge after this

This commit is contained in:
jhmiramon 2021-04-22 15:14:43 +02:00
parent 1c98f9181d
commit 6e7c03ee5c
86 changed files with 1406 additions and 357 deletions

View file

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager

View file

@ -235,8 +235,8 @@ class AnimelibUpdateNotifier(private val context: Context) {
.centerCrop()
.circleCrop()
.override(
NOTIF_ICON_SIZE,
NOTIF_ICON_SIZE
NOTIF_ICON_SIZE,
NOTIF_ICON_SIZE
)
.submit()
.get()

View file

@ -7,25 +7,28 @@ import android.os.IBinder
import android.os.PowerManager
import androidx.core.content.ContextCompat
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateRanker.rankingScheme
import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateService.Companion.start
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.AnimelibAnime
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.AnimelibAnime
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.toAnimeInfo
import eu.kanade.tachiyomi.data.download.AnimeDownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateRanker.rankingScheme
import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateService.Companion.start
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.AnimeSourceManager
import eu.kanade.tachiyomi.source.model.SAnime
import eu.kanade.tachiyomi.source.model.toSAnime
import eu.kanade.tachiyomi.source.model.toSEpisode
import eu.kanade.tachiyomi.util.episode.NoEpisodesException
import eu.kanade.tachiyomi.util.episode.syncEpisodesWithSource
import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.shouldDownloadNewEpisodes
import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
@ -37,6 +40,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
@ -56,7 +60,7 @@ import java.util.concurrent.atomic.AtomicInteger
*/
class AnimelibUpdateService(
val db: AnimeDatabaseHelper = Injekt.get(),
val sourceManager: SourceManager = Injekt.get(),
val sourceManager: AnimeSourceManager = Injekt.get(),
val preferences: PreferencesHelper = Injekt.get(),
val downloadManager: AnimeDownloadManager = Injekt.get(),
val trackManager: TrackManager = Injekt.get(),
@ -205,7 +209,7 @@ class AnimelibUpdateService(
}
updateJob = ioScope.launch(handler) {
when (target) {
Target.CHAPTERS -> updateChapterList()
Target.CHAPTERS -> updateEpisodeList()
Target.COVERS -> updateCovers()
Target.TRACKING -> updateTrackings()
}
@ -262,7 +266,7 @@ class AnimelibUpdateService(
* @param animeToUpdate the list to update
* @return an observable delivering the progress of each update.
*/
suspend fun updateChapterList() {
suspend fun updateEpisodeList() {
val progressCount = AtomicInteger(0)
val newUpdates = mutableListOf<Pair<AnimelibAnime, Array<Episode>>>()
val failedUpdates = mutableListOf<Pair<Anime, String?>>()
@ -279,8 +283,8 @@ class AnimelibUpdateService(
val (newEpisodes, _) = updateAnime(anime)
if (newEpisodes.isNotEmpty()) {
if (anime.shouldDownloadNewChapters(db, preferences)) {
downloadChapters(anime, newEpisodes)
if (anime.shouldDownloadNewEpisodes(db, preferences)) {
downloadEpisodes(anime, newEpisodes)
hasDownloads = true
}
@ -315,7 +319,7 @@ class AnimelibUpdateService(
}
}
private fun downloadChapters(anime: Anime, episodes: List<Episode>) {
private fun downloadEpisodes(anime: Anime, episodes: List<Episode>) {
// We don't want to start downloading while the library is updating, because websites
// may don't like it and they could ban the user.
downloadManager.downloadEpisodes(anime, episodes, false)
@ -327,7 +331,7 @@ class AnimelibUpdateService(
* @param anime the anime to update.
* @return a pair of the inserted and removed chapters.
*/
suspend fun updateAnime(anime: Anime): Pair<List<Chapter>, List<Chapter>> {
suspend fun updateAnime(anime: Anime): Pair<List<Episode>, List<Episode>> {
val source = sourceManager.getOrStub(anime.source)
// Update anime details metadata in the background
@ -350,10 +354,10 @@ class AnimelibUpdateService(
}
}
val chapters = source.getChapterList(anime.toAnimeInfo())
.map { it.toSChapter() }
val chapters = source.getEpisodeList(anime.toAnimeInfo())
.map { it.toSEpisode() }
return syncChaptersWithSource(db, chapters, anime, source)
return syncEpisodesWithSource(db, chapters, anime, source)
}
private suspend fun updateCovers() {
@ -402,15 +406,15 @@ class AnimelibUpdateService(
notifier.showProgressNotification(anime, progressCount++, animeToUpdate.size)
// Update the tracking details.
db.getAnimeTracks(anime).executeAsBlocking()
db.getTracks(anime).executeAsBlocking()
.map { track ->
supervisorScope {
async {
val service = trackManager.getService(track.sync_id)
if (service != null && service in loggedServices) {
try {
val updatedTrack = service.refresh(track)
db.insertAnimeTrack(updatedTrack).executeAsBlocking()
val updatedTrack = service.refreshAnime(track)
db.insertTrack(updatedTrack).executeAsBlocking()
} catch (e: Throwable) {
// Ignore errors and continue
Timber.e(e)

View file

@ -3,24 +3,24 @@ package eu.kanade.tachiyomi.data.database
import android.content.Context
import androidx.sqlite.db.SupportSQLiteOpenHelper
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
import eu.kanade.tachiyomi.data.database.mappers.AnimeCategoryTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.AnimeTrackTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.AnimeTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.CategoryTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.EpisodeTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.HistoryTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.AnimeCategoryTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.AnimeTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.AnimeTrackTypeMapping
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.AnimeCategory
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.queries.CategoryQueries
import eu.kanade.tachiyomi.data.database.queries.EpisodeQueries
import eu.kanade.tachiyomi.data.database.queries.HistoryQueries
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.queries.AnimeCategoryQueries
import eu.kanade.tachiyomi.data.database.queries.AnimeQueries
import eu.kanade.tachiyomi.data.database.queries.AnimeTrackQueries
import eu.kanade.tachiyomi.data.database.queries.CategoryQueries
import eu.kanade.tachiyomi.data.database.queries.EpisodeQueries
import eu.kanade.tachiyomi.data.database.queries.HistoryQueries
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
/**

View file

@ -11,11 +11,11 @@ import com.pushtorefresh.storio.sqlite.queries.InsertQuery
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.AnimeTrackImpl
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_ANIME_ID
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_FINISH_DATE
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_LAST_EPISODE_SEEN
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_LIBRARY_ID
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_ANIME_ID
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_MEDIA_ID
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_SCORE
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_START_DATE

View file

@ -64,7 +64,7 @@ class AnimePutResolver : DefaultPutResolver<Anime>() {
COL_LAST_UPDATE to obj.last_update,
COL_INITIALIZED to obj.initialized,
COL_VIEWER to obj.viewer,
COL_CHAPTER_FLAGS to obj.chapter_flags,
COL_CHAPTER_FLAGS to obj.episode_flags,
COL_COVER_LAST_MODIFIED to obj.cover_last_modified,
COL_DATE_ADDED to obj.date_added
)
@ -86,7 +86,7 @@ interface BaseAnimeGetResolver {
last_update = cursor.getLong(cursor.getColumnIndex(COL_LAST_UPDATE))
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
episode_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
cover_last_modified = cursor.getLong(cursor.getColumnIndex(COL_COVER_LAST_MODIFIED))
date_added = cursor.getLong(cursor.getColumnIndex(COL_DATE_ADDED))
}

View file

@ -46,7 +46,7 @@ class EpisodePutResolver : DefaultPutResolver<Episode>() {
override fun mapToContentValues(obj: Episode) =
contentValuesOf(
COL_ID to obj.id,
COL_MANGA_ID to obj.manga_id,
COL_MANGA_ID to obj.anime_id,
COL_URL to obj.url,
COL_NAME to obj.name,
COL_READ to obj.read,
@ -64,7 +64,7 @@ class EpisodeGetResolver : DefaultGetResolver<Episode>() {
override fun mapFromCursor(cursor: Cursor): Episode = EpisodeImpl().apply {
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
anime_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
url = cursor.getString(cursor.getColumnIndex(COL_URL))
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
scanlator = cursor.getString(cursor.getColumnIndex(COL_SCANLATOR))

View file

@ -32,7 +32,7 @@ open class AnimeImpl : Anime {
override var viewer: Int = 0
override var chapter_flags: Int = 0
override var episode_flags: Int = 0
override var cover_last_modified: Long = 0

View file

@ -4,7 +4,7 @@ class EpisodeImpl : Episode {
override var id: Long? = null
override var manga_id: Long? = null
override var anime_id: Long? = null
override lateinit var url: String

View file

@ -4,19 +4,19 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.Query
import com.pushtorefresh.storio.sqlite.queries.RawQuery
import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.models.AnimelibAnime
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.resolvers.AnimelibAnimeGetResolver
import eu.kanade.tachiyomi.data.database.models.AnimelibAnime
import eu.kanade.tachiyomi.data.database.resolvers.AnimeCoverLastModifiedPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.AnimeFavoritePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.AnimeFlagsPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.AnimeLastUpdatedPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.AnimeTitlePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.AnimeViewerPutResolver
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.data.database.resolvers.AnimelibAnimeGetResolver
import eu.kanade.tachiyomi.data.database.tables.AnimeCategoryTable
import eu.kanade.tachiyomi.data.database.tables.AnimeTable
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
interface AnimeQueries : DbProvider {
@ -174,5 +174,4 @@ interface AnimeQueries : DbProvider {
.build()
)
.prepare()
}

View file

@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.data.database.queries
import com.pushtorefresh.storio.sqlite.queries.Query
import com.pushtorefresh.storio.sqlite.queries.RawQuery
import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
interface CategoryQueries : DbProvider {
@ -31,14 +31,14 @@ interface CategoryQueries : DbProvider {
.prepare()
fun getCategoriesForAnime(anime: Anime) = db.get()
.listOfObjects(Category::class.java)
.withQuery(
RawQuery.builder()
.query(getCategoriesForMangaQuery())
.args(anime.id)
.build()
)
.prepare()
.listOfObjects(Category::class.java)
.withQuery(
RawQuery.builder()
.query(getCategoriesForMangaQuery())
.args(anime.id)
.build()
)
.prepare()
fun insertCategory(category: Category) = db.put().`object`(category).prepare()

View file

@ -3,14 +3,14 @@ package eu.kanade.tachiyomi.data.database.queries
import com.pushtorefresh.storio.sqlite.queries.Query
import com.pushtorefresh.storio.sqlite.queries.RawQuery
import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.AnimeEpisode
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.resolvers.AnimeEpisodeGetResolver
import eu.kanade.tachiyomi.data.database.resolvers.EpisodeBackupPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.EpisodeKnownBackupPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.EpisodeProgressPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.EpisodeSourceOrderPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.AnimeEpisodeGetResolver
import eu.kanade.tachiyomi.data.database.tables.EpisodeTable
import java.util.Date

View file

@ -1,12 +1,12 @@
package eu.kanade.tachiyomi.data.database.queries
import eu.kanade.tachiyomi.data.database.tables.AnimeCategoryTable as AnimeCategory
import eu.kanade.tachiyomi.data.database.tables.AnimeTable as Anime
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
import eu.kanade.tachiyomi.data.database.tables.AnimeCategoryTable as AnimeCategory
import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
import eu.kanade.tachiyomi.data.database.tables.AnimeTable as Anime
/**
* Query to get the manga from the library, with their categories and unread count.
@ -105,7 +105,7 @@ fun getLastReadMangaQuery() =
"""
fun getLastReadAnimeQuery() =
"""
"""
SELECT ${Anime.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
FROM ${Anime.TABLE}
JOIN ${Chapter.TABLE}
@ -128,7 +128,7 @@ fun getTotalChapterMangaQuery() =
"""
fun getTotalChapterAnimeQuery() =
"""
"""
SELECT ${Anime.TABLE}.*
FROM ${Anime.TABLE}
JOIN ${Anime.TABLE}
@ -148,7 +148,7 @@ fun getLatestChapterMangaQuery() =
"""
fun getLatestChapterAnimeQuery() =
"""
"""
SELECT ${Anime.TABLE}.*, MAX(${Chapter.TABLE}.${Chapter.COL_DATE_UPLOAD}) AS max
FROM ${Anime.TABLE}
JOIN ${Chapter.TABLE}
@ -168,7 +168,7 @@ fun getChapterFetchDateMangaQuery() =
"""
fun getChapterFetchDateAnimeQuery() =
"""
"""
SELECT ${Anime.TABLE}.*, MAX(${Chapter.TABLE}.${Chapter.COL_DATE_FETCH}) AS max
FROM ${Anime.TABLE}
JOIN ${Chapter.TABLE}
@ -192,7 +192,7 @@ fun getCategoriesForMangaQuery() =
* Query to get the categories for an anime.
*/
fun getCategoriesForAnimeQuery() =
"""
"""
SELECT ${Category.TABLE}.* FROM ${Category.TABLE}
JOIN ${AnimeCategory.TABLE} ON ${Category.TABLE}.${Category.COL_ID} =
${AnimeCategory.TABLE}.${AnimeCategory.COL_CATEGORY_ID}

View file

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.data.database.resolvers
import android.database.Cursor
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import eu.kanade.tachiyomi.data.database.mappers.EpisodeGetResolver
import eu.kanade.tachiyomi.data.database.mappers.AnimeGetResolver
import eu.kanade.tachiyomi.data.database.mappers.EpisodeGetResolver
import eu.kanade.tachiyomi.data.database.models.AnimeEpisode
class AnimeEpisodeGetResolver : DefaultGetResolver<AnimeEpisode>() {

View file

@ -37,6 +37,6 @@ class AnimeFlagsPutResolver(private val updateAll: Boolean = false) : PutResolve
fun mapToContentValues(anime: Anime) =
contentValuesOf(
AnimeTable.COL_CHAPTER_FLAGS to anime.chapter_flags
AnimeTable.COL_CHAPTER_FLAGS to anime.episode_flags
)
}

View file

@ -22,7 +22,7 @@ class AnimeSourceOrderPutResolver : PutResolver<Episode>() {
fun mapToUpdateQuery(episode: Episode) = UpdateQuery.builder()
.table(EpisodeTable.TABLE)
.where("${EpisodeTable.COL_URL} = ? AND ${EpisodeTable.COL_MANGA_ID} = ?")
.whereArgs(episode.url, episode.manga_id)
.whereArgs(episode.url, episode.anime_id)
.build()
fun mapToContentValues(episode: Episode) =

View file

@ -22,7 +22,7 @@ class EpisodeSourceOrderPutResolver : PutResolver<Episode>() {
fun mapToUpdateQuery(episode: Episode) = UpdateQuery.builder()
.table(EpisodeTable.TABLE)
.where("${EpisodeTable.COL_URL} = ? AND ${EpisodeTable.COL_MANGA_ID} = ?")
.whereArgs(episode.url, episode.manga_id)
.whereArgs(episode.url, episode.anime_id)
.build()
fun mapToContentValues(episode: Episode) =

View file

@ -3,10 +3,10 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context
import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.AnimeSourceManager
import kotlinx.coroutines.flow.onEach
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit
class AnimeDownloadCache(
private val context: Context,
private val provider: AnimeDownloadProvider,
private val sourceManager: SourceManager,
private val sourceManager: AnimeSourceManager,
private val preferences: PreferencesHelper = Injekt.get()
) {

View file

@ -4,13 +4,13 @@ import android.content.Context
import com.hippo.unifile.UniFile
import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.download.model.AnimeDownload
import eu.kanade.tachiyomi.data.download.model.AnimeDownloadQueue
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.AnimeSource
import eu.kanade.tachiyomi.source.AnimeSourceManager
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.lang.launchIO
import rx.Observable
@ -26,7 +26,7 @@ import uy.kohesive.injekt.injectLazy
*/
class AnimeDownloadManager(private val context: Context) {
private val sourceManager: SourceManager by injectLazy()
private val sourceManager: AnimeSourceManager by injectLazy()
private val preferences: PreferencesHelper by injectLazy()
/**
@ -137,7 +137,7 @@ class AnimeDownloadManager(private val context: Context) {
* @param episode the downloaded episode.
* @return an observable containing the list of pages from the episode.
*/
fun buildPageList(source: Source, anime: Anime, episode: Episode): Observable<List<Page>> {
fun buildPageList(source: AnimeSource, anime: Anime, episode: Episode): Observable<List<Page>> {
return buildPageList(provider.findEpisodeDir(episode, anime, source))
}
@ -210,7 +210,7 @@ class AnimeDownloadManager(private val context: Context) {
* @param anime the anime of the episodes.
* @param source the source of the episodes.
*/
fun deleteEpisodes(episodes: List<Episode>, anime: Anime, source: Source): List<Episode> {
fun deleteEpisodes(episodes: List<Episode>, anime: Anime, source: AnimeSource): List<Episode> {
val filteredEpisodes = getEpisodesToDelete(episodes)
launchIO {
removeFromDownloadQueue(filteredEpisodes)
@ -249,7 +249,7 @@ class AnimeDownloadManager(private val context: Context) {
* @param anime the anime to delete.
* @param source the source of the anime.
*/
fun deleteAnime(anime: Anime, source: Source) {
fun deleteAnime(anime: Anime, source: AnimeSource) {
launchIO {
downloader.queue.remove(anime)
provider.findAnimeDir(anime, source)?.delete()
@ -286,7 +286,7 @@ class AnimeDownloadManager(private val context: Context) {
* @param oldEpisode the existing episode with the old name.
* @param newEpisode the target episode with the new name.
*/
fun renameEpisode(source: Source, anime: Anime, oldEpisode: Episode, newEpisode: Episode) {
fun renameEpisode(source: AnimeSource, anime: Anime, oldEpisode: Episode, newEpisode: Episode) {
val oldNames = provider.getValidEpisodeDirNames(oldEpisode)
val newName = provider.getEpisodeDirName(newEpisode)
val animeDir = provider.getAnimeDir(anime, source)

View file

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context
import androidx.core.content.edit
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString

View file

@ -4,10 +4,10 @@ import android.content.Context
import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.AnimeSource
import eu.kanade.tachiyomi.util.storage.DiskUtil
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.launchIn
@ -48,7 +48,7 @@ class AnimeDownloadProvider(private val context: Context) {
* @param anime the anime to query.
* @param source the source of the anime.
*/
internal fun getAnimeDir(anime: Anime, source: Source): UniFile {
internal fun getAnimeDir(anime: Anime, source: AnimeSource): UniFile {
try {
return downloadsDir
.createDirectory(getSourceDirName(source))
@ -64,7 +64,7 @@ class AnimeDownloadProvider(private val context: Context) {
*
* @param source the source to query.
*/
fun findSourceDir(source: Source): UniFile? {
fun findSourceDir(source: AnimeSource): UniFile? {
return downloadsDir.findFile(getSourceDirName(source), true)
}
@ -74,7 +74,7 @@ class AnimeDownloadProvider(private val context: Context) {
* @param anime the anime to query.
* @param source the source of the anime.
*/
fun findAnimeDir(anime: Anime, source: Source): UniFile? {
fun findAnimeDir(anime: Anime, source: AnimeSource): UniFile? {
val sourceDir = findSourceDir(source)
return sourceDir?.findFile(getAnimeDirName(anime))
}
@ -86,7 +86,7 @@ class AnimeDownloadProvider(private val context: Context) {
* @param anime the anime of the episode.
* @param source the source of the episode.
*/
fun findEpisodeDir(episode: Episode, anime: Anime, source: Source): UniFile? {
fun findEpisodeDir(episode: Episode, anime: Anime, source: AnimeSource): UniFile? {
val animeDir = findAnimeDir(anime, source)
return getValidEpisodeDirNames(episode).asSequence()
.mapNotNull { animeDir?.findFile(it) }
@ -100,7 +100,7 @@ class AnimeDownloadProvider(private val context: Context) {
* @param anime the anime of the episode.
* @param source the source of the episode.
*/
fun findEpisodeDirs(episodes: List<Episode>, anime: Anime, source: Source): List<UniFile> {
fun findEpisodeDirs(episodes: List<Episode>, anime: Anime, source: AnimeSource): List<UniFile> {
val animeDir = findAnimeDir(anime, source) ?: return emptyList()
return episodes.mapNotNull { episode ->
getValidEpisodeDirNames(episode).asSequence()
@ -114,7 +114,7 @@ class AnimeDownloadProvider(private val context: Context) {
*
* @param source the source to query.
*/
fun getSourceDirName(source: Source): String {
fun getSourceDirName(source: AnimeSource): String {
return source.toString()
}

View file

@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context
import androidx.core.content.edit
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.download.model.AnimeDownload
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.AnimeSourceManager
import eu.kanade.tachiyomi.source.online.AnimeHttpSource
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
@ -20,7 +20,7 @@ import uy.kohesive.injekt.injectLazy
*/
class AnimeDownloadStore(
context: Context,
private val sourceManager: SourceManager
private val sourceManager: AnimeSourceManager
) {
/**
@ -29,7 +29,7 @@ class AnimeDownloadStore(
private val preferences = context.getSharedPreferences("active_downloads", Context.MODE_PRIVATE)
private val json: Json by injectLazy()
private val db: DatabaseHelper by injectLazy()
private val db: AnimeDatabaseHelper by injectLazy()
/**
* Counter used to keep the queue order.
@ -73,7 +73,7 @@ class AnimeDownloadStore(
* @param download the download.
*/
private fun getKey(download: AnimeDownload): String {
return download.chapter.id!!.toString()
return download.episode.id!!.toString()
}
/**
@ -92,9 +92,9 @@ class AnimeDownloadStore(
val anime = cachedAnime.getOrPut(animeId) {
db.getAnime(animeId).executeAsBlocking()
} ?: continue
val source = sourceManager.get(anime.source) as? HttpSource ?: continue
val chapter = db.getChapter(chapterId).executeAsBlocking() ?: continue
downloads.add(AnimeDownload(source, anime, chapter))
val source = sourceManager.get(anime.source) as? AnimeHttpSource ?: continue
val episode = db.getEpisode(chapterId).executeAsBlocking() ?: continue
downloads.add(AnimeDownload(source, anime, episode))
}
}
@ -109,7 +109,7 @@ class AnimeDownloadStore(
* @param download the download to serialize.
*/
private fun serialize(download: AnimeDownload): String {
val obj = AnimeDownloadObject(download.anime.id!!, download.chapter.id!!, counter++)
val obj = AnimeDownloadObject(download.anime.id!!, download.episode.id!!, counter++)
return json.encodeToString(obj)
}

View file

@ -7,11 +7,11 @@ import com.jakewharton.rxrelay.BehaviorRelay
import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.EpisodeCache
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.download.model.AnimeDownload
import eu.kanade.tachiyomi.data.download.model.AnimeDownloadQueue
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.AnimeSourceManager
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.AnimeHttpSource
import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList
@ -50,7 +50,7 @@ class AnimeDownloader(
private val context: Context,
private val provider: AnimeDownloadProvider,
private val cache: AnimeDownloadCache,
private val sourceManager: SourceManager
private val sourceManager: AnimeSourceManager
) {
private val episodeCache: EpisodeCache by injectLazy()

View file

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.download.model
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.AnimeHttpSource
import rx.subjects.PublishSubject

View file

@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.data.download.model
import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.download.AnimeDownloadStore
import eu.kanade.tachiyomi.source.model.Page
import rx.Observable

View file

@ -9,18 +9,20 @@ import android.net.Uri
import android.os.Build
import android.os.Handler
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateService
import eu.kanade.tachiyomi.data.backup.BackupRestoreService
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.anime.AnimeController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.watcher.WatcherActivity
@ -34,8 +36,6 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.File
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateService
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
/**
* Global [BroadcastReceiver] that runs on UI thread
@ -445,11 +445,11 @@ class NotificationReceiver : BroadcastReceiver() {
*/
internal fun openEpisodePendingActivity(context: Context, anime: Anime, groupId: Int): PendingIntent {
val newIntent =
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(AnimeController.MANGA_EXTRA, anime.id)
.putExtra("notificationId", anime.id.hashCode())
.putExtra("groupId", groupId)
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(AnimeController.MANGA_EXTRA, anime.id)
.putExtra("notificationId", anime.id.hashCode())
.putExtra("groupId", groupId)
return PendingIntent.getActivity(context, anime.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
@ -460,10 +460,10 @@ class NotificationReceiver : BroadcastReceiver() {
* @param anime anime of episode
*/
internal fun markAsReadPendingBroadcast(
context: Context,
anime: Anime,
episodes: Array<Episode>,
groupId: Int
context: Context,
anime: Anime,
episodes: Array<Episode>,
groupId: Int
): PendingIntent {
val newIntent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_MARK_AS_READ
@ -474,7 +474,7 @@ class NotificationReceiver : BroadcastReceiver() {
}
return PendingIntent.getBroadcast(context, anime.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
/**
* Returns [PendingIntent] that starts a reader activity containing chapter.
*

View file

@ -8,8 +8,8 @@ import androidx.preference.PreferenceManager
import com.tfcporciuncula.flow.FlowSharedPreferences
import com.tfcporciuncula.flow.Preference
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.anilist.Anilist

View file

@ -4,8 +4,8 @@ import androidx.annotation.CallSuper
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.NetworkHelper
@ -51,6 +51,8 @@ abstract class TrackService(val id: Int) {
abstract suspend fun add(track: Track): Track
abstract suspend fun addAnime(track: AnimeTrack): AnimeTrack
abstract suspend fun update(track: Track): Track
abstract suspend fun updateAnime(track: AnimeTrack): AnimeTrack

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.graphics.Color
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
@ -130,10 +131,32 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
}
}
override fun displayScore(track: AnimeTrack): String {
val score = track.score
return when (scorePreference.get()) {
POINT_5 -> when (score) {
0f -> "0 ★"
else -> "${((score + 10) / 20).toInt()}"
}
POINT_3 -> when {
score == 0f -> "0"
score <= 35 -> "😦"
score <= 60 -> "😐"
else -> "😊"
}
else -> track.toAnilistScore()
}
}
override suspend fun add(track: Track): Track {
return api.addLibManga(track)
}
override suspend fun addAnime(track: AnimeTrack): AnimeTrack {
return api.addLibAnime(track)
}
override suspend fun update(track: Track): Track {
// If user was using API v1 fetch library_id
if (track.library_id == null || track.library_id!! == 0L) {
@ -145,6 +168,17 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
return api.updateLibManga(track)
}
override suspend fun updateAnime(track: AnimeTrack): AnimeTrack {
// If user was using API v1 fetch library_id
if (track.library_id == null || track.library_id!! == 0L) {
val libManga = api.findLibAnime(track, getUsername().toInt())
?: throw Exception("$track not found on user library")
track.library_id = libManga.library_id
}
return api.updateLibAnime(track)
}
override suspend fun bind(track: Track): Track {
val remoteTrack = api.findLibManga(track, getUsername().toInt())
return if (remoteTrack != null) {
@ -159,6 +193,20 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
}
}
override suspend fun bindAnime(track: AnimeTrack): AnimeTrack {
val remoteTrack = api.findLibAnime(track, getUsername().toInt())
return if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.library_id = remoteTrack.library_id
updateAnime(track)
} else {
// Set default fields if it's not found in the list
track.status = READING
track.score = 0F
addAnime(track)
}
}
override suspend fun search(query: String): List<TrackSearch> {
return api.search(query)
}
@ -170,6 +218,13 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
return track
}
override suspend fun refreshAnime(track: AnimeTrack): AnimeTrack {
val remoteTrack = api.getLibAnime(track, getUsername().toInt())
track.copyPersonalFrom(remoteTrack)
track.total_episodes = remoteTrack.total_episodes
return track
}
override suspend fun login(username: String, password: String) = login(password)
suspend fun login(token: String) {

View file

@ -5,6 +5,7 @@ import androidx.core.net.toUri
import com.afollestad.date.dayOfMonth
import com.afollestad.date.month
import com.afollestad.date.year
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST
@ -100,6 +101,74 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}
suspend fun addLibAnime(track: AnimeTrack): AnimeTrack {
return withIOContext {
val query = """
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
| id
| status
|}
|}
|""".trimMargin()
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("mangaId", track.media_id)
put("progress", track.last_episode_seen)
put("status", track.toAnilistStatus())
}
}
authClient.newCall(
POST(
apiUrl,
body = payload.toString().toRequestBody(jsonMime)
)
)
.await()
.parseAs<JsonObject>()
.let {
track.library_id =
it["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long
track
}
}
}
suspend fun updateLibAnime(track: AnimeTrack): AnimeTrack {
return withIOContext {
val query = """
|mutation UpdateManga(
|${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus,
|${'$'}score: Int, ${'$'}startedAt: FuzzyDateInput, ${'$'}completedAt: FuzzyDateInput
|) {
|SaveMediaListEntry(
|id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status,
|scoreRaw: ${'$'}score, startedAt: ${'$'}startedAt, completedAt: ${'$'}completedAt
|) {
|id
|status
|progress
|}
|}
|""".trimMargin()
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("listId", track.library_id)
put("progress", track.last_episode_seen)
put("status", track.toAnilistStatus())
put("score", track.score.toInt())
put("startedAt", createDate(track.started_watching_date))
put("completedAt", createDate(track.finished_watching_date))
}
}
authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime)))
.await()
track
}
}
suspend fun search(search: String): List<TrackSearch> {
return withIOContext {
val query = """
@ -267,10 +336,81 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}
suspend fun findLibAnime(track: AnimeTrack, userid: Int): AnimeTrack? {
return withIOContext {
val query = """
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|Page {
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
|id
|status
|scoreRaw: score(format: POINT_100)
|progress
|startedAt {
|year
|month
|day
|}
|completedAt {
|year
|month
|day
|}
|media {
|id
|title {
|romaji
|}
|coverImage {
|large
|}
|type
|status
|chapters
|description
|startDate {
|year
|month
|day
|}
|}
|}
|}
|}
|""".trimMargin()
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("id", userid)
put("manga_id", track.media_id)
}
}
authClient.newCall(
POST(
apiUrl,
body = payload.toString().toRequestBody(jsonMime)
)
)
.await()
.parseAs<JsonObject>()
.let { response ->
val data = response["data"]!!.jsonObject
val page = data["Page"]!!.jsonObject
val media = page["mediaList"]!!.jsonArray
val entries = media.map { jsonToALUserAnime(it.jsonObject) }
entries.firstOrNull()?.toTrack()
}
}
}
suspend fun getLibManga(track: Track, userid: Int): Track {
return findLibManga(track, userid) ?: throw Exception("Could not find manga")
}
suspend fun getLibAnime(track: AnimeTrack, userid: Int): AnimeTrack {
return findLibAnime(track, userid) ?: throw Exception("Could not find anime")
}
fun createOAuth(token: String): OAuth {
return OAuth(token, "Bearer", System.currentTimeMillis() + 31536000000, 31536000000)
}
@ -334,6 +474,18 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
)
}
private fun jsonToALUserAnime(struct: JsonObject): ALUserAnime {
return ALUserAnime(
struct["id"]!!.jsonPrimitive.long,
struct["status"]!!.jsonPrimitive.content,
struct["scoreRaw"]!!.jsonPrimitive.int,
struct["progress"]!!.jsonPrimitive.int,
parseDate(struct, "startedAt"),
parseDate(struct, "completedAt"),
jsonToALManga(struct["media"]!!.jsonObject)
)
}
private fun parseDate(struct: JsonObject, dateKey: String): Long {
return try {
val date = Calendar.getInstance()

View file

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.data.track.anilist
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
@ -102,6 +103,38 @@ data class ALUserManga(
}
}
data class ALUserAnime(
val library_id: Long,
val list_status: String,
val score_raw: Int,
val chapters_read: Int,
val start_date_fuzzy: Long,
val completed_date_fuzzy: Long,
val manga: ALManga
) {
fun toTrack() = AnimeTrack.create(TrackManager.ANILIST).apply {
media_id = manga.media_id
status = toTrackStatus()
score = score_raw.toFloat()
started_watching_date = start_date_fuzzy
finished_watching_date = completed_date_fuzzy
last_episode_seen = chapters_read
library_id = this@ALUserAnime.library_id
total_episodes = manga.total_chapters
}
fun toTrackStatus() = when (list_status) {
"CURRENT" -> Anilist.READING
"COMPLETED" -> Anilist.COMPLETED
"PAUSED" -> Anilist.PAUSED
"DROPPED" -> Anilist.DROPPED
"PLANNING" -> Anilist.PLANNING
"REPEATING" -> Anilist.REPEATING
else -> throw NotImplementedError("Unknown status: $list_status")
}
}
fun Track.toAnilistStatus() = when (status) {
Anilist.READING -> "CURRENT"
Anilist.COMPLETED -> "COMPLETED"
@ -112,6 +145,16 @@ fun Track.toAnilistStatus() = when (status) {
else -> throw NotImplementedError("Unknown status: $status")
}
fun AnimeTrack.toAnilistStatus() = when (status) {
Anilist.READING -> "CURRENT"
Anilist.COMPLETED -> "COMPLETED"
Anilist.PAUSED -> "PAUSED"
Anilist.DROPPED -> "DROPPED"
Anilist.PLANNING -> "PLANNING"
Anilist.REPEATING -> "REPEATING"
else -> throw NotImplementedError("Unknown status: $status")
}
private val preferences: PreferencesHelper by injectLazy()
fun Track.toAnilistScore(): String = when (preferences.anilistScoreType().get()) {
@ -139,3 +182,29 @@ fun Track.toAnilistScore(): String = when (preferences.anilistScoreType().get())
"POINT_10_DECIMAL" -> (score / 10).toString()
else -> throw NotImplementedError("Unknown score type")
}
fun AnimeTrack.toAnilistScore(): String = when (preferences.anilistScoreType().get()) {
// 10 point
"POINT_10" -> (score.toInt() / 10).toString()
// 100 point
"POINT_100" -> score.toInt().toString()
// 5 stars
"POINT_5" -> when {
score == 0f -> "0"
score < 30 -> "1"
score < 50 -> "2"
score < 70 -> "3"
score < 90 -> "4"
else -> "5"
}
// Smiley
"POINT_3" -> when {
score == 0f -> "0"
score <= 35 -> ":("
score <= 60 -> ":|"
else -> ":)"
}
// 10 point decimal
"POINT_10_DECIMAL" -> (score / 10).toString()
else -> throw NotImplementedError("Unknown score type")
}

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.graphics.Color
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
@ -31,14 +32,26 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
return track.score.toInt().toString()
}
override fun displayScore(track: AnimeTrack): String {
return track.score.toInt().toString()
}
override suspend fun add(track: Track): Track {
return api.addLibManga(track)
}
override suspend fun addAnime(track: AnimeTrack): AnimeTrack {
return api.addLibAnime(track)
}
override suspend fun update(track: Track): Track {
return api.updateLibManga(track)
}
override suspend fun updateAnime(track: AnimeTrack): AnimeTrack {
return api.updateLibAnime(track)
}
override suspend fun bind(track: Track): Track {
val statusTrack = api.statusLibManga(track)
val remoteTrack = api.findLibManga(track)
@ -59,6 +72,26 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
}
}
override suspend fun bindAnime(track: AnimeTrack): AnimeTrack {
val statusTrack = api.statusLibAnime(track)
val remoteTrack = api.findLibAnime(track)
return if (remoteTrack != null && statusTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.library_id = remoteTrack.library_id
track.status = statusTrack.status
track.score = statusTrack.score
track.last_episode_seen = statusTrack.last_episode_seen
track.total_episodes = remoteTrack.total_episodes
refreshAnime(track)
} else {
// Set default fields if it's not found in the list
track.status = READING
track.score = 0F
addAnime(track)
updateAnime(track)
}
}
override suspend fun search(query: String): List<TrackSearch> {
return api.search(query)
}
@ -72,6 +105,15 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
return track
}
override suspend fun refreshAnime(track: AnimeTrack): AnimeTrack {
val remoteStatusTrack = api.statusLibAnime(track)
track.copyPersonalFrom(remoteStatusTrack!!)
api.findLibAnime(track)?.let { remoteTrack ->
track.total_episodes = remoteTrack.total_episodes
}
return track
}
override fun getLogo() = R.drawable.ic_tracker_bangumi
override fun getLogoColor() = Color.rgb(240, 145, 153)

View file

@ -2,8 +2,10 @@ package eu.kanade.tachiyomi.data.track.bangumi
import android.net.Uri
import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
@ -43,6 +45,18 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
}
}
suspend fun addLibAnime(track: AnimeTrack): AnimeTrack {
return withIOContext {
val body = FormBody.Builder()
.add("rating", track.score.toInt().toString())
.add("status", track.toBangumiStatus())
.build()
authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = body))
.await()
track
}
}
suspend fun updateLibManga(track: Track): Track {
return withIOContext {
// read status update
@ -68,6 +82,31 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
}
}
suspend fun updateLibAnime(track: AnimeTrack): AnimeTrack {
return withIOContext {
// read status update
val sbody = FormBody.Builder()
.add("rating", track.score.toInt().toString())
.add("status", track.toBangumiStatus())
.build()
authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = sbody))
.await()
// chapter update
val body = FormBody.Builder()
.add("watched_eps", track.last_episode_seen.toString())
.build()
authClient.newCall(
POST(
"$apiUrl/subject/${track.media_id}/update/watched_eps",
body = body
)
).await()
track
}
}
suspend fun search(search: String): List<TrackSearch> {
return withIOContext {
val url = "$apiUrl/search/subject/${URLEncoder.encode(search, Charsets.UTF_8.name())}"
@ -114,6 +153,28 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
}
}
private fun jsonToSearchAnime(obj: JsonObject): AnimeTrackSearch {
val coverUrl = if (obj["images"] is JsonObject) {
obj["images"]?.jsonObject?.get("common")?.jsonPrimitive?.contentOrNull ?: ""
} else {
// Sometimes JsonNull
""
}
val totalChapters = if (obj["eps_count"] != null) {
obj["eps_count"]!!.jsonPrimitive.int
} else {
0
}
return AnimeTrackSearch.create(TrackManager.BANGUMI).apply {
media_id = obj["id"]!!.jsonPrimitive.int
title = obj["name_cn"]!!.jsonPrimitive.content
cover_url = coverUrl
summary = obj["name"]!!.jsonPrimitive.content
tracking_url = obj["url"]!!.jsonPrimitive.content
total_episodes = totalChapters
}
}
suspend fun findLibManga(track: Track): Track? {
return withIOContext {
authClient.newCall(GET("$apiUrl/subject/${track.media_id}"))
@ -123,6 +184,15 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
}
}
suspend fun findLibAnime(track: AnimeTrack): AnimeTrack? {
return withIOContext {
authClient.newCall(GET("$apiUrl/subject/${track.media_id}"))
.await()
.parseAs<JsonObject>()
.let { jsonToSearchAnime(it) }
}
}
suspend fun statusLibManga(track: Track): Track? {
return withIOContext {
val urlUserRead = "$apiUrl/collection/${track.media_id}"
@ -151,6 +221,34 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
}
}
suspend fun statusLibAnime(track: AnimeTrack): AnimeTrack? {
return withIOContext {
val urlUserRead = "$apiUrl/collection/${track.media_id}"
val requestUserRead = Request.Builder()
.url(urlUserRead)
.cacheControl(CacheControl.FORCE_NETWORK)
.get()
.build()
// TODO: get user readed chapter here
var response = authClient.newCall(requestUserRead).await()
var responseBody = response.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
if (responseBody.contains("\"code\":400")) {
null
} else {
json.decodeFromString<Collection>(responseBody).let {
track.status = it.status?.id!!
track.last_episode_seen = it.ep_status!!
track.score = it.rating!!
track
}
}
}
}
suspend fun accessToken(code: String): OAuth {
return withIOContext {
client.newCall(accessTokenRequest(code))

View file

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.data.track.bangumi
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Track
fun Track.toBangumiStatus() = when (status) {
@ -11,6 +12,15 @@ fun Track.toBangumiStatus() = when (status) {
else -> throw NotImplementedError("Unknown status: $status")
}
fun AnimeTrack.toBangumiStatus() = when (status) {
Bangumi.READING -> "do"
Bangumi.COMPLETED -> "collect"
Bangumi.ON_HOLD -> "on_hold"
Bangumi.DROPPED -> "dropped"
Bangumi.PLANNING -> "wish"
else -> throw NotImplementedError("Unknown status: $status")
}
fun toTrackStatus(status: String) = when (status) {
"do" -> Bangumi.READING
"collect" -> Bangumi.COMPLETED

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.graphics.Color
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
@ -67,14 +68,27 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
return df.format(track.score)
}
override fun displayScore(track: AnimeTrack): String {
val df = DecimalFormat("0.#")
return df.format(track.score)
}
override suspend fun add(track: Track): Track {
return api.addLibManga(track, getUserId())
}
override suspend fun addAnime(track: AnimeTrack): AnimeTrack {
return api.addLibAnime(track, getUserId())
}
override suspend fun update(track: Track): Track {
return api.updateLibManga(track)
}
override suspend fun updateAnime(track: AnimeTrack): AnimeTrack {
return api.updateLibAnime(track)
}
override suspend fun bind(track: Track): Track {
val remoteTrack = api.findLibManga(track, getUserId())
return if (remoteTrack != null) {
@ -88,6 +102,19 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
}
}
override suspend fun bindAnime(track: AnimeTrack): AnimeTrack {
val remoteTrack = api.findLibAnime(track, getUserId())
return if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.media_id = remoteTrack.media_id
updateAnime(track)
} else {
track.status = READING
track.score = 0F
addAnime(track)
}
}
override suspend fun search(query: String): List<TrackSearch> {
return api.search(query)
}
@ -99,6 +126,13 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
return track
}
override suspend fun refreshAnime(track: AnimeTrack): AnimeTrack {
val remoteTrack = api.getLibAnime(track)
track.copyPersonalFrom(remoteTrack)
track.total_episodes = remoteTrack.total_episodes
return track
}
override suspend fun login(username: String, password: String) {
val token = api.login(username, password)
interceptor.newAuth(token)

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.track.kitsu
import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET
@ -74,6 +75,51 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
suspend fun addLibAnime(track: AnimeTrack, userId: String): AnimeTrack {
return withIOContext {
val data = buildJsonObject {
putJsonObject("data") {
put("type", "libraryEntries")
putJsonObject("attributes") {
put("status", track.toKitsuStatus())
put("progress", track.last_episode_seen)
}
putJsonObject("relationships") {
putJsonObject("user") {
putJsonObject("data") {
put("id", userId)
put("type", "users")
}
}
putJsonObject("media") {
putJsonObject("data") {
put("id", track.media_id)
put("type", "manga")
}
}
}
}
}
authClient.newCall(
POST(
"${baseUrl}library-entries",
headers = headersOf(
"Content-Type",
"application/vnd.api+json"
),
body = data.toString().toRequestBody("application/vnd.api+json".toMediaType())
)
)
.await()
.parseAs<JsonObject>()
.let {
track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.int
track
}
}
}
suspend fun updateLibManga(track: Track): Track {
return withIOContext {
val data = buildJsonObject {
@ -108,6 +154,40 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
suspend fun updateLibAnime(track: AnimeTrack): AnimeTrack {
return withIOContext {
val data = buildJsonObject {
putJsonObject("data") {
put("type", "libraryEntries")
put("id", track.media_id)
putJsonObject("attributes") {
put("status", track.toKitsuStatus())
put("progress", track.last_episode_seen)
put("ratingTwenty", track.toKitsuScore())
}
}
}
authClient.newCall(
Request.Builder()
.url("${baseUrl}library-entries/${track.media_id}")
.headers(
headersOf(
"Content-Type",
"application/vnd.api+json"
)
)
.patch(data.toString().toRequestBody("application/vnd.api+json".toMediaType()))
.build()
)
.await()
.parseAs<JsonObject>()
.let {
track
}
}
}
suspend fun search(query: String): List<TrackSearch> {
return withIOContext {
authClient.newCall(GET(algoliaKeyUrl))
@ -170,6 +250,27 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
suspend fun findLibAnime(track: AnimeTrack, userId: String): AnimeTrack? {
return withIOContext {
val url = "${baseUrl}library-entries".toUri().buildUpon()
.encodedQuery("filter[manga_id]=${track.media_id}&filter[user_id]=$userId")
.appendQueryParameter("include", "manga")
.build()
authClient.newCall(GET(url.toString()))
.await()
.parseAs<JsonObject>()
.let {
val data = it["data"]!!.jsonArray
if (data.size > 0) {
val manga = it["included"]!!.jsonArray[0].jsonObject
KitsuLibAnime(data[0].jsonObject, manga).toTrack()
} else {
null
}
}
}
}
suspend fun getLibManga(track: Track): Track {
return withIOContext {
val url = "${baseUrl}library-entries".toUri().buildUpon()
@ -191,6 +292,27 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
suspend fun getLibAnime(track: AnimeTrack): AnimeTrack {
return withIOContext {
val url = "${baseUrl}library-entries".toUri().buildUpon()
.encodedQuery("filter[id]=${track.media_id}")
.appendQueryParameter("include", "manga")
.build()
authClient.newCall(GET(url.toString()))
.await()
.parseAs<JsonObject>()
.let {
val data = it["data"]!!.jsonArray
if (data.size > 0) {
val anime = it["included"]!!.jsonArray[0].jsonObject
KitsuLibAnime(data[0].jsonObject, anime).toTrack()
} else {
throw Exception("Could not find manga")
}
}
}
}
suspend fun login(username: String, password: String): OAuth {
return withIOContext {
val formBody: RequestBody = FormBody.Builder()

View file

@ -1,8 +1,10 @@
package eu.kanade.tachiyomi.data.track.kitsu
import androidx.annotation.CallSuper
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull
@ -88,6 +90,44 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
}
}
class KitsuLibAnime(obj: JsonObject, anime: JsonObject) {
val id = anime["id"]!!.jsonPrimitive.int
private val canonicalTitle = anime["attributes"]!!.jsonObject["canonicalTitle"]!!.jsonPrimitive.content
private val episodeCount = anime["attributes"]!!.jsonObject["episodeCount"]?.jsonPrimitive?.intOrNull
val type = anime["attributes"]!!.jsonObject["animeType"]?.jsonPrimitive?.contentOrNull.orEmpty()
val original = anime["attributes"]!!.jsonObject["posterImage"]!!.jsonObject["original"]!!.jsonPrimitive.content
private val synopsis = anime["attributes"]!!.jsonObject["synopsis"]!!.jsonPrimitive.content
private val startDate = anime["attributes"]!!.jsonObject["startDate"]?.jsonPrimitive?.contentOrNull.orEmpty()
private val libraryId = obj["id"]!!.jsonPrimitive.int
val status = obj["attributes"]!!.jsonObject["status"]!!.jsonPrimitive.content
private val ratingTwenty = obj["attributes"]!!.jsonObject["ratingTwenty"]?.jsonPrimitive?.contentOrNull
val progress = obj["attributes"]!!.jsonObject["progress"]!!.jsonPrimitive.int
fun toTrack() = AnimeTrackSearch.create(TrackManager.KITSU).apply {
media_id = libraryId
title = canonicalTitle
total_episodes = episodeCount ?: 0
cover_url = original
summary = synopsis
tracking_url = KitsuApi.mangaUrl(media_id)
publishing_status = this@KitsuLibAnime.status
publishing_type = type
start_date = startDate
status = toTrackStatus()
score = ratingTwenty?.let { it.toInt() / 2f } ?: 0f
last_episode_seen = progress
}
private fun toTrackStatus() = when (status) {
"current" -> Kitsu.READING
"completed" -> Kitsu.COMPLETED
"on_hold" -> Kitsu.ON_HOLD
"dropped" -> Kitsu.DROPPED
"planned" -> Kitsu.PLAN_TO_READ
else -> throw Exception("Unknown status")
}
}
fun Track.toKitsuStatus() = when (status) {
Kitsu.READING -> "current"
Kitsu.COMPLETED -> "completed"
@ -100,3 +140,16 @@ fun Track.toKitsuStatus() = when (status) {
fun Track.toKitsuScore(): String? {
return if (score > 0) (score * 2).toInt().toString() else null
}
fun AnimeTrack.toKitsuStatus() = when (status) {
Kitsu.READING -> "current"
Kitsu.COMPLETED -> "completed"
Kitsu.ON_HOLD -> "on_hold"
Kitsu.DROPPED -> "dropped"
Kitsu.PLAN_TO_READ -> "planned"
else -> throw Exception("Unknown status")
}
fun AnimeTrack.toKitsuScore(): String? {
return if (score > 0) (score * 2).toInt().toString() else null
}

View file

@ -0,0 +1,66 @@
package eu.kanade.tachiyomi.data.track.model
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
class AnimeTrackSearch : AnimeTrack {
override var id: Long? = null
override var anime_id: Long = 0
override var sync_id: Int = 0
override var media_id: Int = 0
override var library_id: Long? = null
override lateinit var title: String
override var last_episode_seen: Int = 0
override var total_episodes: Int = 0
override var score: Float = 0f
override var status: Int = 0
override var started_watching_date: Long = 0
override var finished_watching_date: Long = 0
override lateinit var tracking_url: String
var cover_url: String = ""
var summary: String = ""
var publishing_status: String = ""
var publishing_type: String = ""
var start_date: String = ""
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || javaClass != other.javaClass) return false
other as AnimeTrack
if (anime_id != other.anime_id) return false
if (sync_id != other.sync_id) return false
return media_id == other.media_id
}
override fun hashCode(): Int {
var result = (anime_id xor anime_id.ushr(32)).toInt()
result = 31 * result + sync_id
result = 31 * result + media_id
return result
}
companion object {
fun create(serviceId: Int): AnimeTrackSearch = AnimeTrackSearch().apply {
sync_id = serviceId
}
}
}

View file

@ -5,6 +5,7 @@ import android.graphics.Color
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
@ -66,6 +67,10 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
return track.score.toInt().toString()
}
override fun displayScore(track: AnimeTrack): String {
return track.score.toInt().toString()
}
override suspend fun add(track: Track): Track {
track.status = READING
track.score = 0F

View file

@ -5,6 +5,7 @@ import android.graphics.Color
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
@ -40,6 +41,10 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
return track.score.toInt().toString()
}
override fun displayScore(track: AnimeTrack): String {
return track.score.toInt().toString()
}
override suspend fun add(track: Track): Track {
return api.addLibManga(track, getUsername())
}

View file

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.source
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.AnimesPage
import eu.kanade.tachiyomi.source.model.FilterList
import rx.Observable
interface AnimeCatalogueSource : AnimeSource {

View file

@ -1,13 +1,12 @@
package eu.kanade.tachiyomi.source
import android.graphics.drawable.Drawable
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.util.lang.awaitSingle
import rx.Observable
import tachiyomi.source.model.EpisodeInfo
import tachiyomi.source.model.AnimeInfo
import tachiyomi.source.model.EpisodeInfo
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get

View file

@ -0,0 +1,77 @@
package eu.kanade.tachiyomi.source
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SAnime
import eu.kanade.tachiyomi.source.model.SEpisode
import eu.kanade.tachiyomi.source.online.AnimeHttpSource
import rx.Observable
open class AnimeSourceManager(private val context: Context) {
private val sourcesMap = mutableMapOf<Long, AnimeSource>()
private val stubSourcesMap = mutableMapOf<Long, StubSource>()
init {
createInternalSources().forEach { registerSource(it) }
}
open fun get(sourceKey: Long): AnimeSource? {
return sourcesMap[sourceKey]
}
fun getOrStub(sourceKey: Long): AnimeSource {
return sourcesMap[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) {
StubSource(sourceKey)
}
}
fun getOnlineSources() = sourcesMap.values.filterIsInstance<AnimeHttpSource>()
fun getCatalogueSources() = sourcesMap.values.filterIsInstance<AnimeCatalogueSource>()
internal fun registerSource(source: AnimeSource) {
if (!sourcesMap.containsKey(source.id)) {
sourcesMap[source.id] = source
}
if (!stubSourcesMap.containsKey(source.id)) {
stubSourcesMap[source.id] = StubSource(source.id)
}
}
internal fun unregisterSource(source: AnimeSource) {
sourcesMap.remove(source.id)
}
private fun createInternalSources(): List<AnimeSource> = listOf(
LocalAnimeSource(context)
)
inner class StubSource(override val id: Long) : AnimeSource {
override val name: String
get() = id.toString()
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
return Observable.error(getSourceNotInstalledException())
}
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
return Observable.error(getSourceNotInstalledException())
}
override fun fetchPageList(episode: SEpisode): Observable<List<Page>> {
return Observable.error(getSourceNotInstalledException())
}
override fun toString(): String {
return name
}
private fun getSourceNotInstalledException(): Exception {
return Exception(context.getString(R.string.source_not_installed, id.toString()))
}
}
}

View file

@ -43,4 +43,4 @@ interface CatalogueSource : Source {
* Returns the list of filters for the source.
*/
fun getFilterList(): FilterList
}
}

View file

@ -4,16 +4,16 @@ import android.content.Context
import com.github.junrar.Archive
import com.google.gson.JsonParser
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.AnimesPage
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.AnimesPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SEpisode
import eu.kanade.tachiyomi.source.model.SAnime
import eu.kanade.tachiyomi.source.model.SEpisode
import eu.kanade.tachiyomi.util.episode.EpisodeRecognition
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.AnimeFile
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.ImageUtil
import rx.Observable
import timber.log.Timber

View file

@ -74,13 +74,13 @@ class LocalSource(private val context: Context) : CatalogueSource {
val time = if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L
var mangaDirs = baseDirs
.asSequence()
.mapNotNull { it.listFiles()?.toList() }
.flatten()
.filter { it.isDirectory }
.filterNot { it.name.startsWith('.') }
.filter { if (time == 0L) it.name.contains(query, ignoreCase = true) else it.lastModified() >= time }
.distinctBy { it.name }
.asSequence()
.mapNotNull { it.listFiles()?.toList() }
.flatten()
.filter { it.isDirectory }
.filterNot { it.name.startsWith('.') }
.filter { if (time == 0L) it.name.contains(query, ignoreCase = true) else it.lastModified() >= time }
.distinctBy { it.name }
val state = ((if (filters.isEmpty()) POPULAR_FILTERS else filters)[0] as OrderBy).state
when (state?.index) {
@ -144,61 +144,61 @@ class LocalSource(private val context: Context) : CatalogueSource {
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
getBaseDirectories(context)
.asSequence()
.mapNotNull { File(it, manga.url).listFiles()?.toList() }
.flatten()
.firstOrNull { it.extension == "json" }
?.apply {
val reader = this.inputStream().bufferedReader()
val json = JsonParser.parseReader(reader).asJsonObject
.asSequence()
.mapNotNull { File(it, manga.url).listFiles()?.toList() }
.flatten()
.firstOrNull { it.extension == "json" }
?.apply {
val reader = this.inputStream().bufferedReader()
val json = JsonParser.parseReader(reader).asJsonObject
manga.title = json["title"]?.asString ?: manga.title
manga.author = json["author"]?.asString ?: manga.author
manga.artist = json["artist"]?.asString ?: manga.artist
manga.description = json["description"]?.asString ?: manga.description
manga.genre = json["genre"]?.asJsonArray?.joinToString(", ") { it.asString }
?: manga.genre
manga.status = json["status"]?.asInt ?: manga.status
}
manga.title = json["title"]?.asString ?: manga.title
manga.author = json["author"]?.asString ?: manga.author
manga.artist = json["artist"]?.asString ?: manga.artist
manga.description = json["description"]?.asString ?: manga.description
manga.genre = json["genre"]?.asJsonArray?.joinToString(", ") { it.asString }
?: manga.genre
manga.status = json["status"]?.asInt ?: manga.status
}
return Observable.just(manga)
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val chapters = getBaseDirectories(context)
.asSequence()
.mapNotNull { File(it, manga.url).listFiles()?.toList() }
.flatten()
.filter { it.isDirectory || isSupportedFile(it.extension) }
.map { chapterFile ->
SChapter.create().apply {
url = "${manga.url}/${chapterFile.name}"
name = if (chapterFile.isDirectory) {
chapterFile.name
} else {
chapterFile.nameWithoutExtension
}
date_upload = chapterFile.lastModified()
val format = getFormat(this)
if (format is Format.Epub) {
EpubFile(format.file).use { epub ->
epub.fillChapterMetadata(this)
}
}
val chapNameCut = stripMangaTitle(name, manga.title)
if (chapNameCut.isNotEmpty()) name = chapNameCut
ChapterRecognition.parseChapterNumber(this, manga)
.asSequence()
.mapNotNull { File(it, manga.url).listFiles()?.toList() }
.flatten()
.filter { it.isDirectory || isSupportedFile(it.extension) }
.map { chapterFile ->
SChapter.create().apply {
url = "${manga.url}/${chapterFile.name}"
name = if (chapterFile.isDirectory) {
chapterFile.name
} else {
chapterFile.nameWithoutExtension
}
}
.sortedWith(
Comparator { c1, c2 ->
val c = c2.chapter_number.compareTo(c1.chapter_number)
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
date_upload = chapterFile.lastModified()
val format = getFormat(this)
if (format is Format.Epub) {
EpubFile(format.file).use { epub ->
epub.fillChapterMetadata(this)
}
)
.toList()
}
val chapNameCut = stripMangaTitle(name, manga.title)
if (chapNameCut.isNotEmpty()) name = chapNameCut
ChapterRecognition.parseChapterNumber(this, manga)
}
}
.sortedWith(
Comparator { c1, c2 ->
val c = c2.chapter_number.compareTo(c1.chapter_number)
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
}
)
.toList()
return Observable.just(chapters)
}
@ -276,16 +276,16 @@ class LocalSource(private val context: Context) : CatalogueSource {
return when (val format = getFormat(chapter)) {
is Format.Directory -> {
val entry = format.file.listFiles()
?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
entry?.let { updateCover(context, manga, it.inputStream()) }
}
is Format.Zip -> {
ZipFile(format.file).use { zip ->
val entry = zip.entries().toList()
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
entry?.let { updateCover(context, manga, zip.getInputStream(it)) }
}
@ -293,8 +293,8 @@ class LocalSource(private val context: Context) : CatalogueSource {
is Format.Rar -> {
Archive(format.file).use { archive ->
val entry = archive.fileHeaders
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
.find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } }
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
.find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } }
entry?.let { updateCover(context, manga, archive.getInputStream(it)) }
}
@ -302,8 +302,8 @@ class LocalSource(private val context: Context) : CatalogueSource {
is Format.Epub -> {
EpubFile(format.file).use { epub ->
val entry = epub.getImagesFromPages()
.firstOrNull()
?.let { epub.getEntry(it) }
.firstOrNull()
?.let { epub.getEntry(it) }
entry?.let { updateCover(context, manga, epub.getInputStream(it)) }
}
@ -321,4 +321,4 @@ class LocalSource(private val context: Context) : CatalogueSource {
data class Rar(val file: File) : Format()
data class Epub(val file: File) : Format()
}
}
}

View file

@ -76,7 +76,7 @@ interface Source : tachiyomi.source.Source {
@Suppress("DEPRECATION")
override suspend fun getChapterList(manga: MangaInfo): List<ChapterInfo> {
return fetchChapterList(manga.toSManga()).awaitSingle()
.map { it.toChapterInfo() }
.map { it.toChapterInfo() }
}
/**
@ -85,10 +85,10 @@ interface Source : tachiyomi.source.Source {
@Suppress("DEPRECATION")
override suspend fun getPageList(chapter: ChapterInfo): List<tachiyomi.source.model.Page> {
return fetchPageList(chapter.toSChapter()).awaitSingle()
.map { it.toPageUrl() }
.map { it.toPageUrl() }
}
}
fun Source.icon(): Drawable? = Injekt.get<ExtensionManager>().getAppIconForSource(this)
fun Source.getPreferenceKey(): String = "source_$id"
fun Source.getPreferenceKey(): String = "source_$id"

View file

@ -4,12 +4,12 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.newCallWithProgress
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.AnimeCatalogueSource
import eu.kanade.tachiyomi.source.model.AnimesPage
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SEpisode
import eu.kanade.tachiyomi.source.model.SAnime
import eu.kanade.tachiyomi.source.model.SEpisode
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
@ -23,7 +23,7 @@ import java.security.MessageDigest
/**
* A simple implementation for sources from a website.
*/
abstract class AnimeHttpSource : CatalogueSource {
abstract class AnimeHttpSource : AnimeCatalogueSource {
/**
* Network service.
@ -147,7 +147,7 @@ abstract class AnimeHttpSource : CatalogueSource {
*
* @param page the page number to retrieve.
*/
override fun fetchLatestAnimeUpdates(page: Int): Observable<AnimesPage> {
override fun fetchLatestUpdates(page: Int): Observable<AnimesPage> {
return client.newCall(latestUpdatesRequest(page))
.asObservableSuccess()
.map { response ->
@ -240,7 +240,7 @@ abstract class AnimeHttpSource : CatalogueSource {
*
* @param episode the episode whose page list has to be fetched.
*/
override fun fetchAnimePageList(episode: SEpisode): Observable<List<Page>> {
override fun fetchPageList(episode: SEpisode): Observable<List<Page>> {
return client.newCall(pageListRequest(episode))
.asObservableSuccess()
.map { response ->

View file

@ -32,18 +32,32 @@ import eu.davidea.flexibleadapter.SelectableAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.AnimeDownload
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.databinding.AnimeControllerBinding
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.AnimeSource
import eu.kanade.tachiyomi.source.AnimeSourceManager
import eu.kanade.tachiyomi.source.online.AnimeHttpSource
import eu.kanade.tachiyomi.ui.anime.episode.AnimeEpisodesHeaderAdapter
import eu.kanade.tachiyomi.ui.anime.episode.DeleteEpisodesDialog
import eu.kanade.tachiyomi.ui.anime.episode.DownloadCustomEpisodesDialog
import eu.kanade.tachiyomi.ui.anime.episode.EpisodeItem
import eu.kanade.tachiyomi.ui.anime.episode.EpisodesAdapter
import eu.kanade.tachiyomi.ui.anime.episode.EpisodesSettingsSheet
import eu.kanade.tachiyomi.ui.anime.episode.base.BaseEpisodesAdapter
import eu.kanade.tachiyomi.ui.anime.info.AnimeInfoHeaderAdapter
import eu.kanade.tachiyomi.ui.anime.track.TrackItem
import eu.kanade.tachiyomi.ui.anime.track.TrackSearchDialog
import eu.kanade.tachiyomi.ui.anime.track.TrackSheet
import eu.kanade.tachiyomi.ui.animelib.AnimelibController
import eu.kanade.tachiyomi.ui.animelib.ChangeAnimeCategoriesDialog
import eu.kanade.tachiyomi.ui.animelib.ChangeAnimeCoverDialog
import eu.kanade.tachiyomi.ui.base.controller.FabController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.ToolbarLiftOnScrollController
@ -52,24 +66,10 @@ import eu.kanade.tachiyomi.ui.browse.migration.search.AnimeSearchController
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.animelib.ChangeAnimeCategoriesDialog
import eu.kanade.tachiyomi.ui.animelib.ChangeAnimeCoverDialog
import eu.kanade.tachiyomi.ui.animelib.AnimelibController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.anime.episode.EpisodeItem
import eu.kanade.tachiyomi.ui.anime.episode.EpisodesAdapter
import eu.kanade.tachiyomi.ui.anime.episode.EpisodesSettingsSheet
import eu.kanade.tachiyomi.ui.anime.episode.DeleteEpisodesDialog
import eu.kanade.tachiyomi.ui.anime.episode.DownloadCustomEpisodesDialog
import eu.kanade.tachiyomi.ui.anime.episode.AnimeEpisodesHeaderAdapter
import eu.kanade.tachiyomi.ui.anime.episode.base.BaseEpisodesAdapter
import eu.kanade.tachiyomi.ui.anime.info.AnimeInfoHeaderAdapter
import eu.kanade.tachiyomi.ui.anime.track.TrackItem
import eu.kanade.tachiyomi.ui.anime.track.TrackSearchDialog
import eu.kanade.tachiyomi.ui.anime.track.TrackSheet
import eu.kanade.tachiyomi.ui.watcher.WatcherActivity
import eu.kanade.tachiyomi.ui.recent.history.HistoryController
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
import eu.kanade.tachiyomi.ui.watcher.WatcherActivity
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.episode.NoEpisodesException
import eu.kanade.tachiyomi.util.hasCustomCover
@ -111,7 +111,7 @@ class AnimeController :
) {
this.anime = anime
if (anime != null) {
source = Injekt.get<SourceManager>().getOrStub(anime.source)
source = Injekt.get<AnimeSourceManager>().getOrStub(anime.source)
}
}
@ -125,7 +125,7 @@ class AnimeController :
var anime: Anime? = null
private set
var source: Source? = null
var source: AnimeSource? = null
private set
private val fromSource = args.getBoolean(FROM_SOURCE_EXTRA, false)
@ -401,7 +401,7 @@ class AnimeController :
* @param anime anime object containing information about anime.
* @param source the source of the anime.
*/
fun onNextAnimeInfo(anime: Anime, source: Source) {
fun onNextAnimeInfo(anime: Anime, source: AnimeSource) {
if (anime.initialized) {
// Update view.
animeInfoAdapter?.update(anime, source)
@ -943,7 +943,7 @@ class AnimeController :
}
private fun downloadEpisodes(episodes: List<EpisodeItem>) {
if (source is SourceManager.StubSource) {
if (source is AnimeSourceManager.StubSource) {
activity?.toast(R.string.loader_not_implemented_error)
return
}

View file

@ -6,24 +6,24 @@ import android.os.Bundle
import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.AnimeCategory
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.toAnimeInfo
import eu.kanade.tachiyomi.data.download.AnimeDownloadManager
import eu.kanade.tachiyomi.data.download.model.AnimeDownload
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.toSEpisode
import eu.kanade.tachiyomi.source.LocalAnimeSource
import eu.kanade.tachiyomi.source.AnimeSource
import eu.kanade.tachiyomi.source.model.toSAnime
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.source.model.toSEpisode
import eu.kanade.tachiyomi.ui.anime.episode.EpisodeItem
import eu.kanade.tachiyomi.ui.anime.track.TrackItem
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.*
import eu.kanade.tachiyomi.util.episode.EpisodeSettingsHelper
import eu.kanade.tachiyomi.util.episode.syncEpisodesWithSource
@ -46,7 +46,7 @@ import java.util.Date
class AnimePresenter(
val anime: Anime,
val source: Source,
val source: AnimeSource,
val preferences: PreferencesHelper = Injekt.get(),
private val db: AnimeDatabaseHelper = Injekt.get(),
private val trackManager: TrackManager = Injekt.get(),
@ -275,7 +275,7 @@ class AnimePresenter(
.fromCallable {
context.contentResolver.openInputStream(data)?.use {
if (anime.isLocal()) {
LocalSource.updateCover(context, anime, it)
LocalAnimeSource.updateCover(context, anime, it)
anime.updateCoverLastModified(db)
} else if (anime.favorite) {
coverCache.setCustomCoverToCache(anime, it)
@ -544,7 +544,7 @@ class AnimePresenter(
}
private fun downloadNewEpisodes(episodes: List<Episode>) {
if (episodes.isEmpty() || !anime.shouldDownloadNewChapters(db, preferences)) return
if (episodes.isEmpty() || !anime.shouldDownloadNewEpisodes(db, preferences)) return
downloadEpisodes(episodes)
}

View file

@ -7,8 +7,8 @@ import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.ui.anime.episode.base.BaseEpisodeItem
class EpisodeItem(episode: Episode, val anime: Anime) :
@ -23,10 +23,10 @@ class EpisodeItem(episode: Episode, val anime: Anime) :
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: EpisodeHolder,
position: Int,
payloads: List<Any?>?
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: EpisodeHolder,
position: Int,
payloads: List<Any?>?
) {
holder.bind(this, anime)
}

View file

@ -12,8 +12,8 @@ import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
class EpisodesAdapter(
controller: AnimeController,
context: Context
controller: AnimeController,
context: Context
) : BaseEpisodesAdapter<EpisodeItem>(controller) {
private val preferences: PreferencesHelper by injectLazy()

View file

@ -14,9 +14,9 @@ import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Stat
import eu.kanade.tachiyomi.widget.sheet.TabbedBottomSheetDialog
class EpisodesSettingsSheet(
private val router: Router,
private val presenter: AnimePresenter,
onGroupClickListener: (ExtendedNavigationView.Group) -> Unit
private val router: Router,
private val presenter: AnimePresenter,
onGroupClickListener: (ExtendedNavigationView.Group) -> Unit
) : TabbedBottomSheetDialog(router.activity!!) {
val filters: Filter

View file

@ -10,13 +10,13 @@ import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.AnimeThumbnail
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.toAnimeThumbnail
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.databinding.AnimeInfoHeaderBinding
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.AnimeSource
import eu.kanade.tachiyomi.source.AnimeSourceManager
import eu.kanade.tachiyomi.source.model.SAnime
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.anime.AnimeController
@ -33,15 +33,15 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class AnimeInfoHeaderAdapter(
private val controller: AnimeController,
private val fromSource: Boolean
private val controller: AnimeController,
private val fromSource: Boolean
) :
RecyclerView.Adapter<AnimeInfoHeaderAdapter.HeaderViewHolder>() {
private val trackManager: TrackManager by injectLazy()
private var anime: Anime = controller.presenter.anime
private var source: Source = controller.presenter.source
private var source: AnimeSource = controller.presenter.source
private var trackCount: Int = 0
private lateinit var binding: AnimeInfoHeaderBinding
@ -66,7 +66,7 @@ class AnimeInfoHeaderAdapter(
* @param anime anime object containing information about anime.
* @param source the source of the anime.
*/
fun update(anime: Anime, source: Source) {
fun update(anime: Anime, source: AnimeSource) {
this.anime = anime
this.source = source
@ -199,7 +199,7 @@ class AnimeInfoHeaderAdapter(
* @param anime anime object containing information about anime.
* @param source the source of the anime.
*/
private fun setAnimeInfo(anime: Anime, source: Source?) {
private fun setAnimeInfo(anime: Anime, source: AnimeSource?) {
// Update full title TextView.
binding.animeFullTitle.text = if (anime.title.isBlank()) {
view.context.getString(R.string.unknown)
@ -227,7 +227,7 @@ class AnimeInfoHeaderAdapter(
if (animeSource != null) {
text = animeSource
setOnClickListener {
val sourceManager = Injekt.get<SourceManager>()
val sourceManager = Injekt.get<AnimeSourceManager>()
controller.performSearch(sourceManager.getOrStub(source.id).name)
}
} else {

View file

@ -7,7 +7,7 @@ import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.datetime.datePicker
import com.bluelinelabs.conductor.Controller
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import uy.kohesive.injekt.Injekt
@ -34,7 +34,7 @@ class SetTrackWatchingDatesDialog<T> : DialogController
@Suppress("unused")
constructor(bundle: Bundle) : super(bundle) {
val track = bundle.getSerializable(KEY_ITEM_TRACK) as Track
val track = bundle.getSerializable(KEY_ITEM_TRACK) as AnimeTrack
val service = Injekt.get<TrackManager>().getService(track.sync_id)!!
item = TrackItem(track, service)
dateToUpdate = ReadingDate.Start
@ -61,8 +61,8 @@ class SetTrackWatchingDatesDialog<T> : DialogController
return Calendar.getInstance().apply {
item.track?.let {
val date = when (dateToUpdate) {
ReadingDate.Start -> it.started_reading_date
ReadingDate.Finish -> it.finished_reading_date
ReadingDate.Start -> it.started_watching_date
ReadingDate.Finish -> it.finished_watching_date
}
if (date != 0L) {
timeInMillis = date

View file

@ -27,7 +27,7 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
true
}
binding.trackStatus.setOnClickListener { listener.onStatusClick(bindingAdapterPosition) }
binding.trackChapters.setOnClickListener { listener.onChaptersClick(bindingAdapterPosition) }
binding.trackChapters.setOnClickListener { listener.onEpisodesClick(bindingAdapterPosition) }
binding.trackScore.setOnClickListener { listener.onScoreClick(bindingAdapterPosition) }
binding.trackStartDate.setOnClickListener { listener.onStartDateClick(bindingAdapterPosition) }
binding.trackFinishDate.setOnClickListener { listener.onFinishDateClick(bindingAdapterPosition) }
@ -45,16 +45,16 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
binding.trackDetails.isVisible = track != null
if (track != null) {
binding.trackTitle.text = track.title
binding.trackChapters.text = "${track.last_chapter_read}/" +
if (track.total_chapters > 0) track.total_chapters else "-"
binding.trackChapters.text = "${track.last_episode_seen}/" +
if (track.total_episodes > 0) track.total_episodes else "-"
binding.trackStatus.text = item.service.getStatus(track.status)
binding.trackScore.text = if (track.score == 0f) "-" else item.service.displayScore(track)
if (item.service.supportsReadingDates) {
binding.trackStartDate.text =
if (track.started_reading_date != 0L) dateFormat.format(track.started_reading_date) else "-"
if (track.started_watching_date != 0L) dateFormat.format(track.started_watching_date) else "-"
binding.trackFinishDate.text =
if (track.finished_reading_date != 0L) dateFormat.format(track.finished_reading_date) else "-"
if (track.finished_watching_date != 0L) dateFormat.format(track.finished_watching_date) else "-"
} else {
binding.bottomDivider.isVisible = false
binding.vertDivider3.isVisible = false

View file

@ -8,12 +8,12 @@ import androidx.core.view.isVisible
import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.databinding.TrackSearchItemBinding
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
import eu.kanade.tachiyomi.databinding.AnimeTrackSearchItemBinding
import eu.kanade.tachiyomi.util.view.inflate
class TrackSearchAdapter(context: Context) :
ArrayAdapter<TrackSearch>(context, R.layout.track_search_item, mutableListOf<TrackSearch>()) {
ArrayAdapter<AnimeTrackSearch>(context, R.layout.track_search_item, mutableListOf<AnimeTrackSearch>()) {
override fun getView(position: Int, view: View?, parent: ViewGroup): View {
var v = view
@ -32,7 +32,7 @@ class TrackSearchAdapter(context: Context) :
return v
}
fun setItems(syncs: List<TrackSearch>) {
fun setItems(syncs: List<AnimeTrackSearch>) {
setNotifyOnChange(false)
clear()
addAll(syncs)
@ -41,9 +41,9 @@ class TrackSearchAdapter(context: Context) :
class TrackSearchHolder(private val view: View) {
private val binding = TrackSearchItemBinding.bind(view)
private val binding = AnimeTrackSearchItemBinding.bind(view)
fun onSetValues(track: TrackSearch) {
fun onSetValues(track: AnimeTrackSearch) {
binding.trackSearchTitle.text = track.title
binding.trackSearchSummary.text = track.summary
GlideApp.with(view.context).clear(binding.trackSearchCover)

View file

@ -9,13 +9,13 @@ import androidx.core.view.isVisible
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.databinding.TrackSearchDialogBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
import eu.kanade.tachiyomi.databinding.AnimeTrackSearchDialogBinding
import eu.kanade.tachiyomi.ui.anime.AnimeController
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
@ -28,11 +28,11 @@ import java.util.concurrent.TimeUnit
class TrackSearchDialog : DialogController {
private var binding: TrackSearchDialogBinding? = null
private var binding: AnimeTrackSearchDialogBinding? = null
private var adapter: TrackSearchAdapter? = null
private var selectedItem: Track? = null
private var selectedItem: AnimeTrack? = null
private val service: TrackService
@ -52,7 +52,7 @@ class TrackSearchDialog : DialogController {
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
binding = TrackSearchDialogBinding.inflate(LayoutInflater.from(activity!!))
binding = AnimeTrackSearchDialogBinding.inflate(LayoutInflater.from(activity!!))
val dialog = MaterialDialog(activity!!)
.customView(view = binding!!.root)
.positiveButton(android.R.string.ok) { onPositiveButtonClick() }
@ -109,7 +109,7 @@ class TrackSearchDialog : DialogController {
trackController.presenter.trackingSearch(query, service)
}
fun onSearchResults(results: List<TrackSearch>) {
fun onSearchResults(results: List<AnimeTrackSearch>) {
selectedItem = null
val binding = binding ?: return
binding.progress.isVisible = false

View file

@ -6,14 +6,14 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.databinding.TrackControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
import eu.kanade.tachiyomi.ui.anime.AnimeController
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
class TrackSheet(
val controller: AnimeController,
val anime: Anime
val controller: AnimeController,
val anime: Anime
) : BaseBottomSheetDialog(controller.activity!!),
TrackAdapter.OnClickListener,
SetTrackStatusDialog.Listener,

View file

@ -7,8 +7,8 @@ import eu.kanade.tachiyomi.data.database.models.AnimeCategory
import eu.kanade.tachiyomi.data.database.models.toAnimeInfo
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SEpisode
import eu.kanade.tachiyomi.source.model.SAnime
import eu.kanade.tachiyomi.source.model.SEpisode
import eu.kanade.tachiyomi.source.model.toSEpisode
import eu.kanade.tachiyomi.ui.browse.migration.AnimeMigrationFlags
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalAnimeSearchCardItem
@ -80,15 +80,15 @@ class AnimeSearchPresenter(
) {
val flags = preferences.migrateFlags().get()
val migrateEpisodes =
AnimeMigrationFlags.hasEpisodes(
AnimeMigrationFlags.hasEpisodes(
flags
)
val migrateCategories =
AnimeMigrationFlags.hasCategories(
AnimeMigrationFlags.hasCategories(
flags
)
val migrateTracks =
AnimeMigrationFlags.hasTracks(
AnimeMigrationFlags.hasTracks(
flags
)

View file

@ -4,8 +4,8 @@ import android.os.Bundle
import android.view.View
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.browse.AnimeSourceItem
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
class AnimeSourceSearchController(
bundle: Bundle
@ -28,7 +28,7 @@ class AnimeSourceSearchController(
newAnime = item.anime
val searchController = router.backstack.findLast { it.controller().javaClass == AnimeSearchController::class.java }?.controller() as AnimeSearchController?
val dialog =
AnimeSearchController.MigrationDialog(oldAnime, newAnime, this)
AnimeSearchController.MigrationDialog(oldAnime, newAnime, this)
dialog.targetController = searchController
dialog.showDialog(router)
return true

View file

@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.toAnimeThumbnail
import eu.kanade.tachiyomi.databinding.AnimeSourceComfortableGridItemBinding
import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding
import eu.kanade.tachiyomi.widget.StateImageViewTarget
/**
@ -19,7 +18,7 @@ import eu.kanade.tachiyomi.widget.StateImageViewTarget
* @constructor creates a new catalogue holder.
*/
class AnimeSourceComfortableGridHolder(private val view: View, private val adapter: FlexibleAdapter<*>) :
AnimeSourceHolder<AnimeSourceComfortableGridItemBinding>(view, adapter) {
AnimeSourceHolder<AnimeSourceComfortableGridItemBinding>(view, adapter) {
override val binding = AnimeSourceComfortableGridItemBinding.bind(view)

View file

@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.toAnimeThumbnail
import eu.kanade.tachiyomi.databinding.AnimeSourceComfortableGridItemBinding
import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding
import eu.kanade.tachiyomi.widget.StateImageViewTarget
/**
@ -19,7 +18,7 @@ import eu.kanade.tachiyomi.widget.StateImageViewTarget
* @constructor creates a new catalogue holder.
*/
open class AnimeSourceGridHolder(private val view: View, private val adapter: FlexibleAdapter<*>) :
AnimeSourceHolder<AnimeSourceComfortableGridItemBinding>(view, adapter) {
AnimeSourceHolder<AnimeSourceComfortableGridItemBinding>(view, adapter) {
override val binding = AnimeSourceComfortableGridItemBinding.bind(view)

View file

@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.toAnimeThumbnail
import eu.kanade.tachiyomi.databinding.AnimeSourceListItemBinding
import eu.kanade.tachiyomi.databinding.SourceListItemBinding
import eu.kanade.tachiyomi.util.system.getResourceColor
/**
@ -23,7 +22,7 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
* @constructor creates a new catalogue holder.
*/
class AnimeSourceListHolder(private val view: View, adapter: FlexibleAdapter<*>) :
AnimeSourceHolder<AnimeSourceListItemBinding>(view, adapter) {
AnimeSourceHolder<AnimeSourceListItemBinding>(view, adapter) {
override val binding = AnimeSourceListItemBinding.bind(view)

View file

@ -15,12 +15,11 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.GlobalAnimeSearchControllerBinding
import eu.kanade.tachiyomi.databinding.GlobalSearchControllerBinding
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.anime.AnimeController
import eu.kanade.tachiyomi.ui.base.controller.SearchableNucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.anime.AnimeController
import uy.kohesive.injekt.injectLazy
/**

View file

@ -9,8 +9,8 @@ import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.AnimesPage
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SAnime
import eu.kanade.tachiyomi.source.model.toSAnime
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter

View file

@ -7,7 +7,6 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.graphics.drawable.DrawableCompat
@ -22,6 +21,7 @@ import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
import eu.kanade.tachiyomi.databinding.LibraryControllerBinding
@ -32,7 +32,7 @@ import eu.kanade.tachiyomi.ui.base.controller.TabbedController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.AnimeController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.drop
@ -45,14 +45,14 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class LibraryController(
bundle: Bundle? = null,
private val preferences: PreferencesHelper = Injekt.get()
bundle: Bundle? = null,
private val preferences: PreferencesHelper = Injekt.get()
) : SearchableNucleusController<LibraryControllerBinding, LibraryPresenter>(bundle),
RootController,
TabbedController,
ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener,
DeleteLibraryMangasDialog.Listener {
RootController,
TabbedController,
ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener,
DeleteLibraryMangasDialog.Listener {
/**
* Position of the active category.
@ -163,34 +163,32 @@ class LibraryController(
return LibraryPresenter()
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = LibraryControllerBinding.inflate(inflater)
override fun createBinding(inflater: LayoutInflater) = LibraryControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.actionToolbar.applyInsetter {
type(navigationBars = true) {
margin(bottom = true)
}
}
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
adapter = LibraryAdapter(this)
binding.libraryPager.adapter = adapter
binding.libraryPager.pageSelections()
.onEach {
preferences.lastUsedCategory().set(it)
activeCategory = it
updateTitle()
}
.launchIn(viewScope)
.onEach {
preferences.lastUsedCategory().set(it)
activeCategory = it
updateTitle()
}
.launchIn(viewScope)
getColumnsPreferenceForCurrentOrientation().asImmediateFlow { mangaPerRow = it }
.drop(1)
// Set again the adapter to recalculate the covers height
.onEach { reattachAdapter() }
.launchIn(viewScope)
.drop(1)
// Set again the adapter to recalculate the covers height
.onEach { reattachAdapter() }
.launchIn(viewScope)
if (selectedMangas.isNotEmpty()) {
createActionModeIfNeeded()
@ -207,12 +205,12 @@ class LibraryController(
}
binding.btnGlobalSearch.clicks()
.onEach {
router.pushController(
GlobalSearchController(presenter.query).withFadeTransaction()
)
}
.launchIn(viewScope)
.onEach {
router.pushController(
GlobalSearchController(presenter.query).withFadeTransaction()
)
}
.launchIn(viewScope)
(activity as? MainActivity)?.fixViewToBottom(binding.actionToolbar)
}
@ -287,8 +285,8 @@ class LibraryController(
// Set the categories
adapter.categories = categories
adapter.itemsPerCategory = adapter.categories
.map { (it.id ?: -1) to (mangaMap[it.id]?.size ?: 0) }
.toMap()
.map { (it.id ?: -1) to (mangaMap[it.id]?.size ?: 0) }
.toMap()
// Restore active category.
binding.libraryPager.setCurrentItem(activeCat, false)
@ -366,8 +364,8 @@ class LibraryController(
if (actionMode == null) {
actionMode = (activity as AppCompatActivity).startSupportActionMode(this)
binding.actionToolbar.show(
actionMode!!,
R.menu.library_selection
actionMode!!,
R.menu.library_selection
) { onActionItemClicked(it!!) }
(activity as? MainActivity)?.showBottomNav(visible = false, collapse = true)
}
@ -395,7 +393,7 @@ class LibraryController(
if (presenter.query.isNotEmpty()) {
binding.btnGlobalSearch.isVisible = true
binding.btnGlobalSearch.text =
resources?.getString(R.string.action_global_search_query, presenter.query)
resources?.getString(R.string.action_global_search_query, presenter.query)
} else {
binding.btnGlobalSearch.isVisible = false
}
@ -417,7 +415,7 @@ class LibraryController(
when (item.itemId) {
R.id.action_search -> expandActionViewFromInteraction = true
R.id.action_filter -> showSettingsSheet()
R.id.action_update_animelib -> {
R.id.action_update_library -> {
activity?.let {
if (LibraryUpdateService.start(it)) {
it.toast(R.string.updating_library)
@ -487,7 +485,7 @@ class LibraryController(
// Notify the presenter a manga is being opened.
presenter.onOpenManga()
router.pushController(AnimeController(manga).withFadeTransaction())
router.pushController(MangaController(manga).withFadeTransaction())
}
/**
@ -533,11 +531,11 @@ class LibraryController(
// Get indexes of the common categories to preselect.
val commonCategoriesIndexes = presenter.getCommonCategories(mangas)
.map { categories.indexOf(it) }
.toTypedArray()
.map { categories.indexOf(it) }
.toTypedArray()
ChangeMangaCategoriesDialog(this, mangas, categories, commonCategoriesIndexes)
.showDialog(router)
.showDialog(router)
}
private fun downloadUnreadChapters() {
@ -585,4 +583,4 @@ class LibraryController(
performSearch()
}
}
}
}

View file

@ -33,8 +33,8 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class MangaInfoHeaderAdapter(
private val controller: AnimeController,
private val fromSource: Boolean
private val controller: AnimeController,
private val fromSource: Boolean
) :
RecyclerView.Adapter<AnimeInfoHeaderAdapter.HeaderViewHolder>() {

View file

@ -12,9 +12,9 @@ import androidx.preference.PreferenceScreen
import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateService.Target
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateService.Target
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
import eu.kanade.tachiyomi.network.PREF_DOH_GOOGLE

View file

@ -26,26 +26,26 @@ import androidx.core.view.setPadding
import androidx.lifecycle.lifecycleScope
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
import eu.kanade.tachiyomi.data.preference.toggle
import eu.kanade.tachiyomi.databinding.WatcherActivityBinding
import eu.kanade.tachiyomi.ui.anime.AnimeController
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.anime.AnimeController
import eu.kanade.tachiyomi.ui.watcher.WatcherPresenter.SetAsCoverResult.AddToLibraryFirst
import eu.kanade.tachiyomi.ui.watcher.WatcherPresenter.SetAsCoverResult.Error
import eu.kanade.tachiyomi.ui.watcher.WatcherPresenter.SetAsCoverResult.Success
import eu.kanade.tachiyomi.ui.watcher.model.ViewerEpisodes
import eu.kanade.tachiyomi.ui.watcher.model.WatcherEpisode
import eu.kanade.tachiyomi.ui.watcher.model.WatcherPage
import eu.kanade.tachiyomi.ui.watcher.model.ViewerEpisodes
import eu.kanade.tachiyomi.ui.watcher.setting.OrientationType
import eu.kanade.tachiyomi.ui.watcher.setting.WatcherSettingsSheet
import eu.kanade.tachiyomi.ui.watcher.setting.ReadingModeType
import eu.kanade.tachiyomi.ui.watcher.setting.WatcherSettingsSheet
import eu.kanade.tachiyomi.ui.watcher.viewer.BaseViewer
import eu.kanade.tachiyomi.ui.watcher.viewer.pager.L2RPagerViewer
import eu.kanade.tachiyomi.ui.watcher.viewer.pager.R2LPagerViewer

View file

@ -7,8 +7,8 @@ import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
@ -17,9 +17,9 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.watcher.loader.EpisodeLoader
import eu.kanade.tachiyomi.ui.watcher.model.ViewerEpisodes
import eu.kanade.tachiyomi.ui.watcher.model.WatcherEpisode
import eu.kanade.tachiyomi.ui.watcher.model.WatcherPage
import eu.kanade.tachiyomi.ui.watcher.model.ViewerEpisodes
import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.lang.byteSize
import eu.kanade.tachiyomi.util.lang.launchIO

View file

@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.ui.watcher.viewer
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import eu.kanade.tachiyomi.ui.watcher.model.WatcherPage
import eu.kanade.tachiyomi.ui.watcher.model.ViewerEpisodes
import eu.kanade.tachiyomi.ui.watcher.model.WatcherPage
/**
* Interface for implementing a viewer.

View file

@ -13,8 +13,8 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.watcher.WatcherActivity
import eu.kanade.tachiyomi.ui.watcher.model.EpisodeTransition
import eu.kanade.tachiyomi.ui.watcher.model.InsertPage
import eu.kanade.tachiyomi.ui.watcher.model.WatcherPage
import eu.kanade.tachiyomi.ui.watcher.model.ViewerEpisodes
import eu.kanade.tachiyomi.ui.watcher.model.WatcherPage
import eu.kanade.tachiyomi.ui.watcher.viewer.BaseViewer
import eu.kanade.tachiyomi.ui.watcher.viewer.ViewerNavigation.NavigationRegion
import kotlinx.coroutines.MainScope

View file

@ -4,9 +4,9 @@ import android.view.View
import android.view.ViewGroup
import eu.kanade.tachiyomi.ui.watcher.model.EpisodeTransition
import eu.kanade.tachiyomi.ui.watcher.model.InsertPage
import eu.kanade.tachiyomi.ui.watcher.model.ViewerEpisodes
import eu.kanade.tachiyomi.ui.watcher.model.WatcherEpisode
import eu.kanade.tachiyomi.ui.watcher.model.WatcherPage
import eu.kanade.tachiyomi.ui.watcher.model.ViewerEpisodes
import eu.kanade.tachiyomi.ui.watcher.viewer.hasMissingEpisodes
import eu.kanade.tachiyomi.widget.ViewPagerAdapter
import timber.log.Timber

View file

@ -6,9 +6,9 @@ import android.widget.LinearLayout
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.ui.watcher.model.EpisodeTransition
import eu.kanade.tachiyomi.ui.watcher.model.ViewerEpisodes
import eu.kanade.tachiyomi.ui.watcher.model.WatcherEpisode
import eu.kanade.tachiyomi.ui.watcher.model.WatcherPage
import eu.kanade.tachiyomi.ui.watcher.model.ViewerEpisodes
import eu.kanade.tachiyomi.ui.watcher.viewer.hasMissingEpisodes
/**

View file

@ -12,8 +12,8 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.WebtoonLayoutManager
import eu.kanade.tachiyomi.ui.watcher.WatcherActivity
import eu.kanade.tachiyomi.ui.watcher.model.EpisodeTransition
import eu.kanade.tachiyomi.ui.watcher.model.WatcherPage
import eu.kanade.tachiyomi.ui.watcher.model.ViewerEpisodes
import eu.kanade.tachiyomi.ui.watcher.model.WatcherPage
import eu.kanade.tachiyomi.ui.watcher.viewer.BaseViewer
import eu.kanade.tachiyomi.ui.watcher.viewer.ViewerNavigation.NavigationRegion
import kotlinx.coroutines.MainScope

View file

@ -52,10 +52,10 @@ fun Anime.updateCoverLastModified(db: AnimeDatabaseHelper) {
db.updateAnimeCoverLastModified(this).executeAsBlocking()
}
fun Anime.shouldDownloadNewChapters(db: AnimeDatabaseHelper, prefs: PreferencesHelper): Boolean {
fun Anime.shouldDownloadNewEpisodes(db: AnimeDatabaseHelper, prefs: PreferencesHelper): Boolean {
if (!favorite) return false
// Boolean to determine if user wants to automatically download new chapters.
// Boolean to determine if user wants to automatically download new episodes.
val downloadNew = prefs.downloadNew().get()
if (!downloadNew) return false

View file

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.util.episode
import eu.kanade.tachiyomi.source.model.SEpisode
import eu.kanade.tachiyomi.source.model.SAnime
import eu.kanade.tachiyomi.source.model.SEpisode
/**
* -R> = regex conversion.

View file

@ -1,10 +1,10 @@
package eu.kanade.tachiyomi.util.episode
import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.download.AnimeDownloadManager
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.AnimeSource
import eu.kanade.tachiyomi.source.model.SEpisode
import eu.kanade.tachiyomi.source.online.AnimeHttpSource
import uy.kohesive.injekt.Injekt
@ -25,7 +25,7 @@ fun syncEpisodesWithSource(
db: AnimeDatabaseHelper,
rawSourceEpisodes: List<SEpisode>,
anime: Anime,
source: Source
source: AnimeSource
): Pair<List<Episode>, List<Episode>> {
if (rawSourceEpisodes.isEmpty()) {
throw NoEpisodesException()

View file

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.util.storage
import eu.kanade.tachiyomi.source.model.SEpisode
import eu.kanade.tachiyomi.source.model.SAnime
import eu.kanade.tachiyomi.source.model.SEpisode
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import java.io.Closeable

View file

@ -1,7 +1,7 @@
package tachiyomi.source
import tachiyomi.source.model.EpisodeInfo
import tachiyomi.source.model.AnimeInfo
import tachiyomi.source.model.EpisodeInfo
import tachiyomi.source.model.Page
/**
@ -51,5 +51,4 @@ interface AnimeSource {
fun getRegex(): Regex {
return Regex("")
}
}

View file

@ -1,9 +1,9 @@
package tachiyomi.source.model
data class EpisodeInfo(
var key: String,
var name: String,
var dateUpload: Long = 0,
var number: Float = -1f,
var scanlator: String = ""
var key: String,
var name: String,
var dateUpload: Long = 0,
var number: Float = -1f,
var scanlator: String = ""
)

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:boxBackgroundMode="filled"
app:endIconMode="clear_text"
app:hintEnabled="false">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/track_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/title"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="32dp"
android:layout_marginBottom="32dp"
android:indeterminate="true"
android:visibility="invisible"
tools:visibility="visible" />
<ListView
android:id="@+id/track_search_list"
style="@style/Theme.Widget.CardView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
android:clipToPadding="false"
android:divider="@null"
android:dividerHeight="10dp"
android:footerDividersEnabled="true"
android:headerDividersEnabled="true"
android:listSelector="@drawable/list_item_selector"
android:scrollbars="none"
android:visibility="invisible"
tools:listitem="@layout/track_search_item"
tools:visibility="visible" />
</FrameLayout>
</LinearLayout>

View file

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Theme.Widget.CardView.Item"
android:layout_margin="0dp"
android:padding="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="216dp"
android:background="@drawable/list_item_selector"
android:orientation="horizontal">
<ImageView
android:id="@+id/track_search_cover"
android:layout_width="135dp"
android:layout_height="match_parent"
android:contentDescription="@string/description_cover"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/track_search_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:maxLines="3"
android:textAppearance="@style/TextAppearance.Regular.Body1.Bold"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/track_search_cover"
app:layout_constraintTop_toTopOf="parent"
tools:text="One Piece" />
<TextView
android:id="@+id/track_search_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:maxLines="1"
android:text="@string/track_type"
android:textAppearance="@style/TextAppearance.Regular.Body1.Bold"
android:textSize="12sp"
app:layout_constraintStart_toEndOf="@id/track_search_cover"
app:layout_constraintTop_toBottomOf="@id/track_search_title" />
<TextView
android:id="@+id/track_search_type_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Regular.Body1.Secondary"
android:textSize="12sp"
app:layout_constraintStart_toEndOf="@id/track_search_type"
app:layout_constraintTop_toBottomOf="@id/track_search_title"
tools:text="Manga" />
<TextView
android:id="@+id/track_search_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:maxLines="1"
android:text="@string/track_start_date"
android:textAppearance="@style/TextAppearance.Regular.Body1.Bold"
android:textSize="12sp"
app:layout_constraintStart_toEndOf="@id/track_search_cover"
app:layout_constraintTop_toBottomOf="@id/track_search_type" />
<TextView
android:id="@+id/track_search_start_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Regular.Body1.Secondary"
android:textSize="12sp"
app:layout_constraintStart_toEndOf="@id/track_search_start"
app:layout_constraintTop_toBottomOf="@id/track_search_type"
tools:text="2018-10-01" />
<TextView
android:id="@+id/track_search_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:maxLines="1"
android:text="@string/track_status"
android:textAppearance="@style/TextAppearance.Regular.Body1.Bold"
android:textSize="12sp"
app:layout_constraintStart_toEndOf="@id/track_search_cover"
app:layout_constraintTop_toBottomOf="@id/track_search_start" />
<TextView
android:id="@+id/track_search_status_result"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Regular.Body1.Secondary"
android:textSize="12sp"
app:layout_constraintStart_toEndOf="@id/track_search_status"
app:layout_constraintTop_toBottomOf="@id/track_search_start"
tools:text="Ongoing" />
<TextView
android:id="@+id/track_search_summary"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:ellipsize="end"
android:maxLines="7"
android:textAppearance="@style/TextAppearance.Regular.Body1.Secondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/track_search_cover"
app:layout_constraintTop_toBottomOf="@+id/track_search_status"
app:layout_constraintVertical_bias="0.333"
tools:text="This is the summary of the manga that fits This is the summary of the manga that fits This is the summary of the manga that fits This is the summary of the manga that fits This is the summary of the manga that fits This is the summary of the manga that fits This is the summary of the manga that fits " />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<eu.kanade.tachiyomi.ui.animelib.AnimelibCategoryView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<eu.kanade.tachiyomi.widget.MaterialFastScroll
android:id="@+id/fast_scroller"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
app:fastScrollerBubbleEnabled="false" />
</eu.kanade.tachiyomi.ui.animelib.AnimelibCategoryView>

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_global_search"
style="@style/Theme.Widget.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:visibility="gone"
tools:text="Search"
tools:visibility="visible" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/animelib_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<eu.kanade.tachiyomi.widget.ActionToolbar
android:id="@+id/action_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_dodgeInsetEdges="bottom" />
<eu.kanade.tachiyomi.widget.EmptyView
android:id="@+id/empty_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>