mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-29 09:39:03 +03:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
35278e9858
88 changed files with 1213 additions and 1190 deletions
|
@ -19,6 +19,7 @@ shortcutHelper.setFilePath("./shortcuts.xml")
|
|||
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
android {
|
||||
namespace = "eu.kanade.tachiyomi"
|
||||
compileSdk = AndroidConfig.compileSdk
|
||||
ndkVersion = AndroidConfig.ndk
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="eu.kanade.tachiyomi">
|
||||
xmlns:tools="http://schemas.android.com/tools" >
|
||||
|
||||
<!-- Internet -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
package eu.kanade.core.util
|
||||
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import rx.Emitter
|
||||
import rx.Observable
|
||||
import rx.Observer
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
fun <T : Any> Observable<T>.asFlow(): Flow<T> = callbackFlow {
|
||||
val observer = object : Observer<T> {
|
||||
|
@ -23,3 +30,32 @@ fun <T : Any> Observable<T>.asFlow(): Flow<T> = callbackFlow {
|
|||
val subscription = subscribe(observer)
|
||||
awaitClose { subscription.unsubscribe() }
|
||||
}
|
||||
|
||||
fun <T : Any> Flow<T>.asObservable(
|
||||
context: CoroutineContext = Dispatchers.Unconfined,
|
||||
backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE,
|
||||
): Observable<T> {
|
||||
return Observable.create(
|
||||
{ emitter ->
|
||||
/*
|
||||
* ATOMIC is used here to provide stable behaviour of subscribe+dispose pair even if
|
||||
* asObservable is already invoked from unconfined
|
||||
*/
|
||||
val job = GlobalScope.launch(context = context, start = CoroutineStart.ATOMIC) {
|
||||
try {
|
||||
collect { emitter.onNext(it) }
|
||||
emitter.onCompleted()
|
||||
} catch (e: Throwable) {
|
||||
// Ignore `CancellationException` as error, since it indicates "normal cancellation"
|
||||
if (e !is CancellationException) {
|
||||
emitter.onError(e)
|
||||
} else {
|
||||
emitter.onCompleted()
|
||||
}
|
||||
}
|
||||
}
|
||||
emitter.setCancellation { job.cancel() }
|
||||
},
|
||||
backpressureMode,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ class AnimeRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun moveAnimeToCategories(animeId: Long, categoryIds: List<Long>) {
|
||||
override suspend fun setAnimeCategories(animeId: Long, categoryIds: List<Long>) {
|
||||
handler.await(inTransaction = true) {
|
||||
animes_categoriesQueries.deleteAnimeCategoryByAnimeId(animeId)
|
||||
categoryIds.map { categoryId ->
|
||||
|
@ -57,31 +57,47 @@ class AnimeRepositoryImpl(
|
|||
|
||||
override suspend fun update(update: AnimeUpdate): Boolean {
|
||||
return try {
|
||||
handler.await {
|
||||
animesQueries.update(
|
||||
source = update.source,
|
||||
url = update.url,
|
||||
artist = update.artist,
|
||||
author = update.author,
|
||||
description = update.description,
|
||||
genre = update.genre?.let(listOfStringsAdapter::encode),
|
||||
title = update.title,
|
||||
status = update.status,
|
||||
thumbnailUrl = update.thumbnailUrl,
|
||||
favorite = update.favorite?.toLong(),
|
||||
lastUpdate = update.lastUpdate,
|
||||
initialized = update.initialized?.toLong(),
|
||||
viewer = update.viewerFlags,
|
||||
episodeFlags = update.episodeFlags,
|
||||
coverLastModified = update.coverLastModified,
|
||||
dateAdded = update.dateAdded,
|
||||
animeId = update.id,
|
||||
)
|
||||
}
|
||||
partialUpdate(update)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateAll(values: List<AnimeUpdate>): Boolean {
|
||||
return try {
|
||||
partialUpdate(*values.toTypedArray())
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun partialUpdate(vararg values: AnimeUpdate) {
|
||||
handler.await(inTransaction = true) {
|
||||
values.forEach { value ->
|
||||
animesQueries.update(
|
||||
source = value.source,
|
||||
url = value.url,
|
||||
artist = value.artist,
|
||||
author = value.author,
|
||||
description = value.description,
|
||||
genre = value.genre?.let(listOfStringsAdapter::encode),
|
||||
title = value.title,
|
||||
status = value.status,
|
||||
thumbnailUrl = value.thumbnailUrl,
|
||||
favorite = value.favorite?.toLong(),
|
||||
lastUpdate = value.lastUpdate,
|
||||
initialized = value.initialized?.toLong(),
|
||||
viewer = value.viewerFlags,
|
||||
episodeFlags = value.episodeFlags,
|
||||
coverLastModified = value.coverLastModified,
|
||||
dateAdded = value.dateAdded,
|
||||
animeId = value.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,13 @@ class AnimeTrackRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun subscribeAnimeTracksByAnimeId(animeId: Long): Flow<List<AnimeTrack>> {
|
||||
override fun getAnimeTracksAsFlow(): Flow<List<AnimeTrack>> {
|
||||
return handler.subscribeToList {
|
||||
anime_syncQueries.getAnimeTracks(animetrackMapper)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAnimeTracksByAnimeIdAsFlow(animeId: Long): Flow<List<AnimeTrack>> {
|
||||
return handler.subscribeToList {
|
||||
anime_syncQueries.getTracksByAnimeId(animeId, animetrackMapper)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,18 @@ class CategoryRepositoryImpl(
|
|||
return handler.subscribeToList { categoriesQueries.getCategories(categoryMapper) }
|
||||
}
|
||||
|
||||
override suspend fun getCategoriesByMangaId(mangaId: Long): List<Category> {
|
||||
return handler.awaitList {
|
||||
categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow<List<Category>> {
|
||||
return handler.subscribeToList {
|
||||
categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(DuplicateNameException::class)
|
||||
override suspend fun insert(name: String, order: Long) {
|
||||
if (checkDuplicateName(name)) throw DuplicateNameException(name)
|
||||
|
@ -48,12 +60,6 @@ class CategoryRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getCategoriesForManga(mangaId: Long): List<Category> {
|
||||
return handler.awaitList {
|
||||
categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun checkDuplicateName(name: String): Boolean {
|
||||
return handler
|
||||
.awaitList { categoriesQueries.getCategories() }
|
||||
|
|
|
@ -15,6 +15,18 @@ class CategoryRepositoryImplAnime(
|
|||
return handler.subscribeToList { categoriesQueries.getCategories(categoryMapper) }
|
||||
}
|
||||
|
||||
override suspend fun getCategoriesByAnimeId(animeId: Long): List<Category> {
|
||||
return handler.awaitList {
|
||||
categoriesQueries.getCategoriesByAnimeId(animeId, categoryMapper)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCategoriesByAnimeIdAsFlow(animeId: Long): Flow<List<Category>> {
|
||||
return handler.subscribeToList {
|
||||
categoriesQueries.getCategoriesByAnimeId(animeId, categoryMapper)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(DuplicateNameException::class)
|
||||
override suspend fun insert(name: String, order: Long) {
|
||||
if (checkDuplicateName(name)) throw DuplicateNameException(name)
|
||||
|
@ -48,12 +60,6 @@ class CategoryRepositoryImplAnime(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getCategoriesForAnime(animeId: Long): List<Category> {
|
||||
return handler.awaitList {
|
||||
categoriesQueries.getCategoriesByAnimeId(animeId, categoryMapper)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun checkDuplicateName(name: String): Boolean {
|
||||
return handler
|
||||
.awaitList { categoriesQueries.getCategories() }
|
||||
|
|
|
@ -92,7 +92,11 @@ class ChapterRepositoryImpl(
|
|||
return handler.awaitList { chaptersQueries.getChaptersByMangaId(mangaId, chapterMapper) }
|
||||
}
|
||||
|
||||
override fun getChapterByMangaIdAsFlow(mangaId: Long): Flow<List<Chapter>> {
|
||||
override suspend fun getChapterById(id: Long): Chapter? {
|
||||
return handler.awaitOneOrNull { chaptersQueries.getChapterById(id, chapterMapper) }
|
||||
}
|
||||
|
||||
override suspend fun getChapterByMangaIdAsFlow(mangaId: Long): Flow<List<Chapter>> {
|
||||
return handler.subscribeToList { chaptersQueries.getChaptersByMangaId(mangaId, chapterMapper) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,6 +95,10 @@ class EpisodeRepositoryImpl(
|
|||
return handler.awaitList { episodesQueries.getEpisodesByAnimeId(animeId, episodeMapper) }
|
||||
}
|
||||
|
||||
override suspend fun getEpisodeById(id: Long): Episode? {
|
||||
return handler.awaitOneOrNull { episodesQueries.getEpisodeById(id, episodeMapper) }
|
||||
}
|
||||
|
||||
override fun getEpisodeByAnimeIdAsFlow(animeId: Long): Flow<List<Episode>> {
|
||||
return handler.subscribeToList { episodesQueries.getEpisodesByAnimeId(animeId, episodeMapper) }
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ class MangaRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun moveMangaToCategories(mangaId: Long, categoryIds: List<Long>) {
|
||||
override suspend fun setMangaCategories(mangaId: Long, categoryIds: List<Long>) {
|
||||
handler.await(inTransaction = true) {
|
||||
mangas_categoriesQueries.deleteMangaCategoryByMangaId(mangaId)
|
||||
categoryIds.map { categoryId ->
|
||||
|
@ -57,31 +57,47 @@ class MangaRepositoryImpl(
|
|||
|
||||
override suspend fun update(update: MangaUpdate): Boolean {
|
||||
return try {
|
||||
handler.await {
|
||||
mangasQueries.update(
|
||||
source = update.source,
|
||||
url = update.url,
|
||||
artist = update.artist,
|
||||
author = update.author,
|
||||
description = update.description,
|
||||
genre = update.genre?.let(listOfStringsAdapter::encode),
|
||||
title = update.title,
|
||||
status = update.status,
|
||||
thumbnailUrl = update.thumbnailUrl,
|
||||
favorite = update.favorite?.toLong(),
|
||||
lastUpdate = update.lastUpdate,
|
||||
initialized = update.initialized?.toLong(),
|
||||
viewer = update.viewerFlags,
|
||||
chapterFlags = update.chapterFlags,
|
||||
coverLastModified = update.coverLastModified,
|
||||
dateAdded = update.dateAdded,
|
||||
mangaId = update.id,
|
||||
)
|
||||
}
|
||||
partialUpdate(update)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateAll(values: List<MangaUpdate>): Boolean {
|
||||
return try {
|
||||
partialUpdate(*values.toTypedArray())
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun partialUpdate(vararg values: MangaUpdate) {
|
||||
handler.await(inTransaction = true) {
|
||||
values.forEach { value ->
|
||||
mangasQueries.update(
|
||||
source = value.source,
|
||||
url = value.url,
|
||||
artist = value.artist,
|
||||
author = value.author,
|
||||
description = value.description,
|
||||
genre = value.genre?.let(listOfStringsAdapter::encode),
|
||||
title = value.title,
|
||||
status = value.status,
|
||||
thumbnailUrl = value.thumbnailUrl,
|
||||
favorite = value.favorite?.toLong(),
|
||||
lastUpdate = value.lastUpdate,
|
||||
initialized = value.initialized?.toLong(),
|
||||
viewer = value.viewerFlags,
|
||||
chapterFlags = value.chapterFlags,
|
||||
coverLastModified = value.coverLastModified,
|
||||
dateAdded = value.dateAdded,
|
||||
mangaId = value.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,13 @@ class TrackRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun subscribeTracksByMangaId(mangaId: Long): Flow<List<Track>> {
|
||||
override fun getTracksAsFlow(): Flow<List<Track>> {
|
||||
return handler.subscribeToList {
|
||||
manga_syncQueries.getTracks(trackMapper)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTracksByMangaIdAsFlow(mangaId: Long): Flow<List<Track>> {
|
||||
return handler.subscribeToList {
|
||||
manga_syncQueries.getTracksByMangaId(mangaId, trackMapper)
|
||||
}
|
||||
|
|
|
@ -48,18 +48,20 @@ import eu.kanade.domain.category.interactor.GetCategories
|
|||
import eu.kanade.domain.category.interactor.GetCategoriesAnime
|
||||
import eu.kanade.domain.category.interactor.InsertCategory
|
||||
import eu.kanade.domain.category.interactor.InsertCategoryAnime
|
||||
import eu.kanade.domain.category.interactor.MoveAnimeToCategories
|
||||
import eu.kanade.domain.category.interactor.MoveMangaToCategories
|
||||
import eu.kanade.domain.category.interactor.SetAnimeCategories
|
||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
||||
import eu.kanade.domain.category.interactor.UpdateCategory
|
||||
import eu.kanade.domain.category.interactor.UpdateCategoryAnime
|
||||
import eu.kanade.domain.category.repository.CategoryRepository
|
||||
import eu.kanade.domain.category.repository.CategoryRepositoryAnime
|
||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||
import eu.kanade.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
|
||||
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
||||
import eu.kanade.domain.episode.interactor.GetEpisode
|
||||
import eu.kanade.domain.episode.interactor.GetEpisodeByAnimeId
|
||||
import eu.kanade.domain.episode.interactor.ShouldUpdateDbEpisode
|
||||
import eu.kanade.domain.episode.interactor.SyncEpisodesWithSource
|
||||
|
@ -120,9 +122,10 @@ class DomainModule : InjektModule {
|
|||
addFactory { ResetViewerFlagsAnime(get()) }
|
||||
addFactory { SetAnimeEpisodeFlags(get()) }
|
||||
addFactory { UpdateAnime(get()) }
|
||||
addFactory { MoveAnimeToCategories(get()) }
|
||||
addFactory { SetAnimeCategories(get()) }
|
||||
|
||||
addSingletonFactory<EpisodeRepository> { EpisodeRepositoryImpl(get()) }
|
||||
addFactory { GetEpisode(get()) }
|
||||
addFactory { GetEpisodeByAnimeId(get()) }
|
||||
addFactory { UpdateEpisode(get()) }
|
||||
addFactory { ShouldUpdateDbEpisode() }
|
||||
|
@ -150,7 +153,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { ResetViewerFlags(get()) }
|
||||
addFactory { SetMangaChapterFlags(get()) }
|
||||
addFactory { UpdateManga(get()) }
|
||||
addFactory { MoveMangaToCategories(get()) }
|
||||
addFactory { SetMangaCategories(get()) }
|
||||
|
||||
addSingletonFactory<AnimeTrackRepository> { AnimeTrackRepositoryImpl(get()) }
|
||||
addFactory { DeleteAnimeTrack(get()) }
|
||||
|
@ -163,6 +166,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { InsertTrack(get()) }
|
||||
|
||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||
addFactory { GetChapter(get()) }
|
||||
addFactory { GetChapterByMangaId(get()) }
|
||||
addFactory { UpdateChapter(get()) }
|
||||
addFactory { ShouldUpdateDbChapter() }
|
||||
|
|
|
@ -20,6 +20,10 @@ class UpdateAnime(
|
|||
return animeRepository.update(animeUpdate)
|
||||
}
|
||||
|
||||
suspend fun awaitAll(values: List<AnimeUpdate>): Boolean {
|
||||
return animeRepository.updateAll(values)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFromSource(
|
||||
localAnime: Anime,
|
||||
remoteAnime: AnimeInfo,
|
||||
|
|
|
@ -18,7 +18,9 @@ interface AnimeRepository {
|
|||
|
||||
suspend fun resetViewerFlags(): Boolean
|
||||
|
||||
suspend fun moveAnimeToCategories(animeId: Long, categoryIds: List<Long>)
|
||||
suspend fun setAnimeCategories(animeId: Long, categoryIds: List<Long>)
|
||||
|
||||
suspend fun update(update: AnimeUpdate): Boolean
|
||||
|
||||
suspend fun updateAll(values: List<AnimeUpdate>): Boolean
|
||||
}
|
||||
|
|
|
@ -19,7 +19,11 @@ class GetAnimeTracks(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun subscribe(animeId: Long): Flow<List<AnimeTrack>> {
|
||||
return animetrackRepository.subscribeAnimeTracksByAnimeId(animeId)
|
||||
fun subscribe(): Flow<List<AnimeTrack>> {
|
||||
return animetrackRepository.getAnimeTracksAsFlow()
|
||||
}
|
||||
|
||||
fun subscribe(animeId: Long): Flow<List<AnimeTrack>> {
|
||||
return animetrackRepository.getAnimeTracksByAnimeIdAsFlow(animeId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@ interface AnimeTrackRepository {
|
|||
|
||||
suspend fun getAnimeTracksByAnimeId(animeId: Long): List<AnimeTrack>
|
||||
|
||||
suspend fun subscribeAnimeTracksByAnimeId(animeId: Long): Flow<List<AnimeTrack>>
|
||||
fun getAnimeTracksAsFlow(): Flow<List<AnimeTrack>>
|
||||
|
||||
fun getAnimeTracksByAnimeIdAsFlow(mangaId: Long): Flow<List<AnimeTrack>>
|
||||
|
||||
suspend fun delete(animeId: Long, syncId: Long)
|
||||
|
||||
|
|
|
@ -12,7 +12,11 @@ class GetCategories(
|
|||
return categoryRepository.getAll()
|
||||
}
|
||||
|
||||
fun subscribe(mangaId: Long): Flow<List<Category>> {
|
||||
return categoryRepository.getCategoriesByMangaIdAsFlow(mangaId)
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long): List<Category> {
|
||||
return categoryRepository.getCategoriesForManga(mangaId)
|
||||
return categoryRepository.getCategoriesByMangaId(mangaId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,11 @@ class GetCategoriesAnime(
|
|||
return categoryRepository.getAll()
|
||||
}
|
||||
|
||||
fun subscribe(animeId: Long): Flow<List<Category>> {
|
||||
return categoryRepository.getCategoriesByAnimeIdAsFlow(animeId)
|
||||
}
|
||||
|
||||
suspend fun await(animeId: Long): List<Category> {
|
||||
return categoryRepository.getCategoriesForAnime(animeId)
|
||||
return categoryRepository.getCategoriesByAnimeId(animeId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,13 @@ import eu.kanade.domain.anime.repository.AnimeRepository
|
|||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import logcat.LogPriority
|
||||
|
||||
class MoveAnimeToCategories(
|
||||
private val mangaRepository: AnimeRepository,
|
||||
class SetAnimeCategories(
|
||||
private val animeRepository: AnimeRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long, categoryIds: List<Long>) {
|
||||
suspend fun await(animeId: Long, categoryIds: List<Long>) {
|
||||
try {
|
||||
mangaRepository.moveAnimeToCategories(mangaId, categoryIds)
|
||||
animeRepository.setAnimeCategories(animeId, categoryIds)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
|
@ -4,13 +4,13 @@ import eu.kanade.domain.manga.repository.MangaRepository
|
|||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import logcat.LogPriority
|
||||
|
||||
class MoveMangaToCategories(
|
||||
class SetMangaCategories(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long, categoryIds: List<Long>) {
|
||||
try {
|
||||
mangaRepository.moveMangaToCategories(mangaId, categoryIds)
|
||||
mangaRepository.setMangaCategories(mangaId, categoryIds)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
|
@ -8,6 +8,10 @@ interface CategoryRepository {
|
|||
|
||||
fun getAll(): Flow<List<Category>>
|
||||
|
||||
suspend fun getCategoriesByMangaId(mangaId: Long): List<Category>
|
||||
|
||||
fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow<List<Category>>
|
||||
|
||||
@Throws(DuplicateNameException::class)
|
||||
suspend fun insert(name: String, order: Long)
|
||||
|
||||
|
@ -16,8 +20,6 @@ interface CategoryRepository {
|
|||
|
||||
suspend fun delete(categoryId: Long)
|
||||
|
||||
suspend fun getCategoriesForManga(mangaId: Long): List<Category>
|
||||
|
||||
suspend fun checkDuplicateName(name: String): Boolean
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,10 @@ interface CategoryRepositoryAnime {
|
|||
|
||||
fun getAll(): Flow<List<Category>>
|
||||
|
||||
suspend fun getCategoriesByAnimeId(animeId: Long): List<Category>
|
||||
|
||||
fun getCategoriesByAnimeIdAsFlow(animeId: Long): Flow<List<Category>>
|
||||
|
||||
@Throws(DuplicateNameException::class)
|
||||
suspend fun insert(name: String, order: Long)
|
||||
|
||||
|
@ -16,7 +20,5 @@ interface CategoryRepositoryAnime {
|
|||
|
||||
suspend fun delete(categoryId: Long)
|
||||
|
||||
suspend fun getCategoriesForAnime(animeId: Long): List<Category>
|
||||
|
||||
suspend fun checkDuplicateName(name: String): Boolean
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import logcat.LogPriority
|
||||
|
||||
class GetChapter(
|
||||
private val chapterRepository: ChapterRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(id: Long): Chapter? {
|
||||
return try {
|
||||
chapterRepository.getChapterById(id)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,5 +16,7 @@ interface ChapterRepository {
|
|||
|
||||
suspend fun getChapterByMangaId(mangaId: Long): List<Chapter>
|
||||
|
||||
fun getChapterByMangaIdAsFlow(mangaId: Long): Flow<List<Chapter>>
|
||||
suspend fun getChapterById(id: Long): Chapter?
|
||||
|
||||
suspend fun getChapterByMangaIdAsFlow(mangaId: Long): Flow<List<Chapter>>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package eu.kanade.domain.episode.interactor
|
||||
|
||||
import eu.kanade.domain.episode.model.Episode
|
||||
import eu.kanade.domain.episode.repository.EpisodeRepository
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import logcat.LogPriority
|
||||
|
||||
class GetEpisode(
|
||||
private val episodeRepository: EpisodeRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(id: Long): Episode? {
|
||||
return try {
|
||||
episodeRepository.getEpisodeById(id)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,5 +16,7 @@ interface EpisodeRepository {
|
|||
|
||||
suspend fun getEpisodeByAnimeId(animeId: Long): List<Episode>
|
||||
|
||||
suspend fun getEpisodeById(id: Long): Episode?
|
||||
|
||||
fun getEpisodeByAnimeIdAsFlow(animeId: Long): Flow<List<Episode>>
|
||||
}
|
||||
|
|
|
@ -20,6 +20,10 @@ class UpdateManga(
|
|||
return mangaRepository.update(mangaUpdate)
|
||||
}
|
||||
|
||||
suspend fun awaitAll(values: List<MangaUpdate>): Boolean {
|
||||
return mangaRepository.updateAll(values)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFromSource(
|
||||
localManga: Manga,
|
||||
remoteManga: MangaInfo,
|
||||
|
|
|
@ -18,7 +18,9 @@ interface MangaRepository {
|
|||
|
||||
suspend fun resetViewerFlags(): Boolean
|
||||
|
||||
suspend fun moveMangaToCategories(mangaId: Long, categoryIds: List<Long>)
|
||||
suspend fun setMangaCategories(mangaId: Long, categoryIds: List<Long>)
|
||||
|
||||
suspend fun update(update: MangaUpdate): Boolean
|
||||
|
||||
suspend fun updateAll(values: List<MangaUpdate>): Boolean
|
||||
}
|
||||
|
|
|
@ -19,7 +19,11 @@ class GetTracks(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun subscribe(mangaId: Long): Flow<List<Track>> {
|
||||
return trackRepository.subscribeTracksByMangaId(mangaId)
|
||||
fun subscribe(): Flow<List<Track>> {
|
||||
return trackRepository.getTracksAsFlow()
|
||||
}
|
||||
|
||||
fun subscribe(mangaId: Long): Flow<List<Track>> {
|
||||
return trackRepository.getTracksByMangaIdAsFlow(mangaId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@ interface TrackRepository {
|
|||
|
||||
suspend fun getTracksByMangaId(mangaId: Long): List<Track>
|
||||
|
||||
suspend fun subscribeTracksByMangaId(mangaId: Long): Flow<List<Track>>
|
||||
fun getTracksAsFlow(): Flow<List<Track>>
|
||||
|
||||
fun getTracksByMangaIdAsFlow(mangaId: Long): Flow<List<Track>>
|
||||
|
||||
suspend fun delete(mangaId: Long, syncId: Long)
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import androidx.compose.material.icons.filled.CheckCircle
|
|||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||
|
@ -51,6 +50,14 @@ fun ChapterDownloadIndicator(
|
|||
onClick(ChapterDownloadAction.START)
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
val chapterDownloadAction = when {
|
||||
isDownloaded -> ChapterDownloadAction.DELETE
|
||||
isDownloading -> ChapterDownloadAction.CANCEL
|
||||
else -> ChapterDownloadAction.START_NOW
|
||||
}
|
||||
onClick(chapterDownloadAction)
|
||||
},
|
||||
) {
|
||||
if (isDownloaded) {
|
||||
Icon(
|
||||
|
|
|
@ -10,7 +10,6 @@ import androidx.compose.material.icons.filled.CheckCircle
|
|||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||
|
@ -51,6 +50,14 @@ fun EpisodeDownloadIndicator(
|
|||
onClick(EpisodeDownloadAction.START)
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
val episodeDownloadAction = when {
|
||||
isDownloaded -> EpisodeDownloadAction.DELETE
|
||||
isDownloading -> EpisodeDownloadAction.CANCEL
|
||||
else -> EpisodeDownloadAction.START_NOW
|
||||
}
|
||||
onClick(episodeDownloadAction)
|
||||
},
|
||||
) {
|
||||
if (isDownloaded) {
|
||||
Icon(
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.kanade.presentation.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.Interaction
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButtonColors
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.util.minimumTouchTargetSize
|
||||
|
||||
/**
|
||||
* <a href="https://m3.material.io/components/icon-button/overview" class="external" target="_blank">Material Design standard icon button</a>.
|
||||
*
|
||||
* Icon buttons help people take supplementary actions with a single tap. They’re used when a
|
||||
* compact button is required, such as in a toolbar or image list.
|
||||
*
|
||||
* ![Standard icon button image](https://developer.android.com/images/reference/androidx/compose/material3/standard-icon-button.png)
|
||||
*
|
||||
* [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
|
||||
* custom icon, note that the typical size for the internal icon is 24 x 24 dp.
|
||||
* This icon button has an overall minimum touch target size of 48 x 48dp, to meet accessibility
|
||||
* guidelines.
|
||||
*
|
||||
* @sample androidx.compose.material3.samples.IconButtonSample
|
||||
*
|
||||
* Tachiyomi changes:
|
||||
* * Add on long click
|
||||
*
|
||||
* @param onClick called when this icon button is clicked
|
||||
* @param modifier the [Modifier] to be applied to this icon button
|
||||
* @param enabled controls the enabled state of this icon button. When `false`, this component will
|
||||
* not respond to user input, and it will appear visually disabled and disabled to accessibility
|
||||
* services.
|
||||
* @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
|
||||
* for this icon button. You can create and pass in your own `remember`ed instance to observe
|
||||
* [Interaction]s and customize the appearance / behavior of this icon button in different states.
|
||||
* @param colors [IconButtonColors] that will be used to resolve the colors used for this icon
|
||||
* button in different states. See [IconButtonDefaults.iconButtonColors].
|
||||
* @param content the content of this icon button, typically an [Icon]
|
||||
*/
|
||||
@Composable
|
||||
fun IconButton(
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
colors: IconButtonColors = IconButtonDefaults.iconButtonColors(),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
modifier
|
||||
.minimumTouchTargetSize()
|
||||
.size(IconButtonTokens.StateLayerSize)
|
||||
.background(color = colors.containerColor(enabled).value)
|
||||
.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
enabled = enabled,
|
||||
role = Role.Button,
|
||||
interactionSource = interactionSource,
|
||||
indication = rememberRipple(
|
||||
bounded = false,
|
||||
radius = IconButtonTokens.StateLayerSize / 2,
|
||||
),
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val contentColor = colors.contentColor(enabled).value
|
||||
CompositionLocalProvider(LocalContentColor provides contentColor, content = content)
|
||||
}
|
||||
}
|
||||
|
||||
object IconButtonTokens {
|
||||
val StateLayerSize = 40.0.dp
|
||||
}
|
|
@ -3,20 +3,15 @@ package eu.kanade.tachiyomi.data.database
|
|||
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.AnimeHistoryTypeMapping
|
||||
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.models.Anime
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeCategory
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeHistory
|
||||
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.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
|
||||
|
||||
|
@ -26,17 +21,13 @@ import eu.kanade.tachiyomi.data.database.queries.EpisodeQueries
|
|||
class AnimeDatabaseHelper(
|
||||
openHelper: SupportSQLiteOpenHelper,
|
||||
) :
|
||||
AnimeQueries, EpisodeQueries, AnimeTrackQueries, CategoryQueries, AnimeCategoryQueries {
|
||||
AnimeQueries, EpisodeQueries, CategoryQueries, AnimeCategoryQueries {
|
||||
|
||||
override val db = DefaultStorIOSQLite.builder()
|
||||
.sqliteOpenHelper(openHelper)
|
||||
.addTypeMapping(Anime::class.java, AnimeTypeMapping())
|
||||
.addTypeMapping(Episode::class.java, EpisodeTypeMapping())
|
||||
.addTypeMapping(AnimeTrack::class.java, AnimeTrackTypeMapping())
|
||||
.addTypeMapping(Category::class.java, CategoryTypeMapping())
|
||||
.addTypeMapping(AnimeCategory::class.java, AnimeCategoryTypeMapping())
|
||||
.addTypeMapping(AnimeHistory::class.java, AnimeHistoryTypeMapping())
|
||||
.build()
|
||||
|
||||
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
||||
}
|
||||
|
|
|
@ -4,21 +4,16 @@ import androidx.sqlite.db.SupportSQLiteOpenHelper
|
|||
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
|
||||
import eu.kanade.tachiyomi.data.database.mappers.CategoryTypeMapping
|
||||
import eu.kanade.tachiyomi.data.database.mappers.ChapterTypeMapping
|
||||
import eu.kanade.tachiyomi.data.database.mappers.HistoryTypeMapping
|
||||
import eu.kanade.tachiyomi.data.database.mappers.MangaCategoryTypeMapping
|
||||
import eu.kanade.tachiyomi.data.database.mappers.MangaTypeMapping
|
||||
import eu.kanade.tachiyomi.data.database.mappers.TrackTypeMapping
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.History
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.database.queries.CategoryQueries
|
||||
import eu.kanade.tachiyomi.data.database.queries.ChapterQueries
|
||||
import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries
|
||||
import eu.kanade.tachiyomi.data.database.queries.MangaQueries
|
||||
import eu.kanade.tachiyomi.data.database.queries.TrackQueries
|
||||
|
||||
/**
|
||||
* This class provides operations to manage the database through its interfaces.
|
||||
|
@ -26,17 +21,13 @@ import eu.kanade.tachiyomi.data.database.queries.TrackQueries
|
|||
class DatabaseHelper(
|
||||
openHelper: SupportSQLiteOpenHelper,
|
||||
) :
|
||||
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries {
|
||||
MangaQueries, ChapterQueries, CategoryQueries, MangaCategoryQueries {
|
||||
|
||||
override val db = DefaultStorIOSQLite.builder()
|
||||
.sqliteOpenHelper(openHelper)
|
||||
.addTypeMapping(Manga::class.java, MangaTypeMapping())
|
||||
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
|
||||
.addTypeMapping(Track::class.java, TrackTypeMapping())
|
||||
.addTypeMapping(Category::class.java, CategoryTypeMapping())
|
||||
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
|
||||
.addTypeMapping(History::class.java, HistoryTypeMapping())
|
||||
.build()
|
||||
|
||||
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
||||
}
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.mappers
|
||||
|
||||
import android.database.Cursor
|
||||
import androidx.core.content.contentValuesOf
|
||||
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
|
||||
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
|
||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
||||
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
|
||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeHistory
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeHistoryImpl
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeHistoryTable.COL_EPISODE_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeHistoryTable.COL_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeHistoryTable.COL_LAST_SEEN
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeHistoryTable.TABLE
|
||||
|
||||
class AnimeHistoryTypeMapping : SQLiteTypeMapping<AnimeHistory>(
|
||||
AnimeHistoryPutResolver(),
|
||||
AnimeHistoryGetResolver(),
|
||||
AnimeHistoryDeleteResolver(),
|
||||
)
|
||||
|
||||
open class AnimeHistoryPutResolver : DefaultPutResolver<AnimeHistory>() {
|
||||
|
||||
override fun mapToInsertQuery(obj: AnimeHistory) = InsertQuery.builder()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
|
||||
override fun mapToUpdateQuery(obj: AnimeHistory) = UpdateQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
|
||||
override fun mapToContentValues(obj: AnimeHistory) =
|
||||
contentValuesOf(
|
||||
COL_ID to obj.id,
|
||||
COL_EPISODE_ID to obj.episode_id,
|
||||
COL_LAST_SEEN to obj.last_seen,
|
||||
)
|
||||
}
|
||||
|
||||
class AnimeHistoryGetResolver : DefaultGetResolver<AnimeHistory>() {
|
||||
|
||||
override fun mapFromCursor(cursor: Cursor): AnimeHistory = AnimeHistoryImpl().apply {
|
||||
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
|
||||
episode_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_EPISODE_ID))
|
||||
last_seen = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LAST_SEEN))
|
||||
}
|
||||
}
|
||||
|
||||
class AnimeHistoryDeleteResolver : DefaultDeleteResolver<AnimeHistory>() {
|
||||
|
||||
override fun mapToDeleteQuery(obj: AnimeHistory) = DeleteQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.mappers
|
||||
|
||||
import android.database.Cursor
|
||||
import androidx.core.content.contentValuesOf
|
||||
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
|
||||
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
|
||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
||||
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
|
||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||
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_MEDIA_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_SCORE
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_START_DATE
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_STATUS
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_SYNC_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_TITLE
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_TOTAL_EPISODES
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.COL_TRACKING_URL
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable.TABLE
|
||||
|
||||
class AnimeTrackTypeMapping : SQLiteTypeMapping<AnimeTrack>(
|
||||
AnimeTrackPutResolver(),
|
||||
AnimeTrackGetResolver(),
|
||||
AnimeTrackDeleteResolver(),
|
||||
)
|
||||
|
||||
class AnimeTrackPutResolver : DefaultPutResolver<AnimeTrack>() {
|
||||
|
||||
override fun mapToInsertQuery(obj: AnimeTrack) = InsertQuery.builder()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
|
||||
override fun mapToUpdateQuery(obj: AnimeTrack) = UpdateQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
|
||||
override fun mapToContentValues(obj: AnimeTrack) =
|
||||
contentValuesOf(
|
||||
COL_ID to obj.id,
|
||||
COL_ANIME_ID to obj.anime_id,
|
||||
COL_SYNC_ID to obj.sync_id,
|
||||
COL_MEDIA_ID to obj.media_id,
|
||||
COL_LIBRARY_ID to obj.library_id,
|
||||
COL_TITLE to obj.title,
|
||||
COL_LAST_EPISODE_SEEN to obj.last_episode_seen,
|
||||
COL_TOTAL_EPISODES to obj.total_episodes,
|
||||
COL_STATUS to obj.status,
|
||||
COL_TRACKING_URL to obj.tracking_url,
|
||||
COL_SCORE to obj.score,
|
||||
COL_START_DATE to obj.started_watching_date,
|
||||
COL_FINISH_DATE to obj.finished_watching_date,
|
||||
)
|
||||
}
|
||||
|
||||
class AnimeTrackGetResolver : DefaultGetResolver<AnimeTrack>() {
|
||||
|
||||
override fun mapFromCursor(cursor: Cursor): AnimeTrack = AnimeTrackImpl().apply {
|
||||
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
|
||||
anime_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ANIME_ID))
|
||||
sync_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_SYNC_ID))
|
||||
media_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MEDIA_ID))
|
||||
library_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LIBRARY_ID))
|
||||
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE))
|
||||
last_episode_seen = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_LAST_EPISODE_SEEN))
|
||||
total_episodes = cursor.getInt(cursor.getColumnIndexOrThrow(COL_TOTAL_EPISODES))
|
||||
status = cursor.getInt(cursor.getColumnIndexOrThrow(COL_STATUS))
|
||||
score = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_SCORE))
|
||||
tracking_url = cursor.getString(cursor.getColumnIndexOrThrow(COL_TRACKING_URL))
|
||||
started_watching_date = cursor.getLong(cursor.getColumnIndexOrThrow(COL_START_DATE))
|
||||
finished_watching_date = cursor.getLong(cursor.getColumnIndexOrThrow(COL_FINISH_DATE))
|
||||
}
|
||||
}
|
||||
|
||||
class AnimeTrackDeleteResolver : DefaultDeleteResolver<AnimeTrack>() {
|
||||
|
||||
override fun mapToDeleteQuery(obj: AnimeTrack) = DeleteQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.mappers
|
||||
|
||||
import android.database.Cursor
|
||||
import androidx.core.content.contentValuesOf
|
||||
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
|
||||
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
|
||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
||||
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
|
||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||
import eu.kanade.tachiyomi.data.database.models.History
|
||||
import eu.kanade.tachiyomi.data.database.models.HistoryImpl
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_CHAPTER_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_LAST_READ
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_TIME_READ
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.TABLE
|
||||
|
||||
class HistoryTypeMapping : SQLiteTypeMapping<History>(
|
||||
HistoryPutResolver(),
|
||||
HistoryGetResolver(),
|
||||
HistoryDeleteResolver(),
|
||||
)
|
||||
|
||||
open class HistoryPutResolver : DefaultPutResolver<History>() {
|
||||
|
||||
override fun mapToInsertQuery(obj: History) = InsertQuery.builder()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
|
||||
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
|
||||
override fun mapToContentValues(obj: History) =
|
||||
contentValuesOf(
|
||||
COL_ID to obj.id,
|
||||
COL_CHAPTER_ID to obj.chapter_id,
|
||||
COL_LAST_READ to obj.last_read,
|
||||
COL_TIME_READ to obj.time_read,
|
||||
)
|
||||
}
|
||||
|
||||
class HistoryGetResolver : DefaultGetResolver<History>() {
|
||||
|
||||
override fun mapFromCursor(cursor: Cursor): History = HistoryImpl().apply {
|
||||
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
|
||||
chapter_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_CHAPTER_ID))
|
||||
last_read = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LAST_READ))
|
||||
time_read = cursor.getLong(cursor.getColumnIndexOrThrow(COL_TIME_READ))
|
||||
}
|
||||
}
|
||||
|
||||
class HistoryDeleteResolver : DefaultDeleteResolver<History>() {
|
||||
|
||||
override fun mapToDeleteQuery(obj: History) = DeleteQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.mappers
|
||||
|
||||
import android.database.Cursor
|
||||
import androidx.core.content.contentValuesOf
|
||||
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
|
||||
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
|
||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
||||
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
|
||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_FINISH_DATE
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_LAST_CHAPTER_READ
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_LIBRARY_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_MANGA_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_MEDIA_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_SCORE
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_START_DATE
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_STATUS
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_SYNC_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TITLE
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TOTAL_CHAPTERS
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TRACKING_URL
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.TABLE
|
||||
|
||||
class TrackTypeMapping : SQLiteTypeMapping<Track>(
|
||||
TrackPutResolver(),
|
||||
TrackGetResolver(),
|
||||
TrackDeleteResolver(),
|
||||
)
|
||||
|
||||
class TrackPutResolver : DefaultPutResolver<Track>() {
|
||||
|
||||
override fun mapToInsertQuery(obj: Track) = InsertQuery.builder()
|
||||
.table(TABLE)
|
||||
.build()
|
||||
|
||||
override fun mapToUpdateQuery(obj: Track) = UpdateQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
|
||||
override fun mapToContentValues(obj: Track) =
|
||||
contentValuesOf(
|
||||
COL_ID to obj.id,
|
||||
COL_MANGA_ID to obj.manga_id,
|
||||
COL_SYNC_ID to obj.sync_id,
|
||||
COL_MEDIA_ID to obj.media_id,
|
||||
COL_LIBRARY_ID to obj.library_id,
|
||||
COL_TITLE to obj.title,
|
||||
COL_LAST_CHAPTER_READ to obj.last_chapter_read,
|
||||
COL_TOTAL_CHAPTERS to obj.total_chapters,
|
||||
COL_STATUS to obj.status,
|
||||
COL_TRACKING_URL to obj.tracking_url,
|
||||
COL_SCORE to obj.score,
|
||||
COL_START_DATE to obj.started_reading_date,
|
||||
COL_FINISH_DATE to obj.finished_reading_date,
|
||||
)
|
||||
}
|
||||
|
||||
class TrackGetResolver : DefaultGetResolver<Track>() {
|
||||
|
||||
override fun mapFromCursor(cursor: Cursor): Track = TrackImpl().apply {
|
||||
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
|
||||
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
|
||||
sync_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_SYNC_ID))
|
||||
media_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MEDIA_ID))
|
||||
library_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LIBRARY_ID))
|
||||
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE))
|
||||
last_chapter_read = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_LAST_CHAPTER_READ))
|
||||
total_chapters = cursor.getInt(cursor.getColumnIndexOrThrow(COL_TOTAL_CHAPTERS))
|
||||
status = cursor.getInt(cursor.getColumnIndexOrThrow(COL_STATUS))
|
||||
score = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_SCORE))
|
||||
tracking_url = cursor.getString(cursor.getColumnIndexOrThrow(COL_TRACKING_URL))
|
||||
started_reading_date = cursor.getLong(cursor.getColumnIndexOrThrow(COL_START_DATE))
|
||||
finished_reading_date = cursor.getLong(cursor.getColumnIndexOrThrow(COL_FINISH_DATE))
|
||||
}
|
||||
}
|
||||
|
||||
class TrackDeleteResolver : DefaultDeleteResolver<Track>() {
|
||||
|
||||
override fun mapToDeleteQuery(obj: Track) = DeleteQuery.builder()
|
||||
.table(TABLE)
|
||||
.where("$COL_ID = ?")
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package eu.kanade.tachiyomi.data.database.models
|
||||
|
||||
import dataanime.GetCategories
|
||||
|
||||
class AnimeCategory {
|
||||
|
||||
var id: Long? = null
|
||||
|
@ -18,12 +16,5 @@ class AnimeCategory {
|
|||
ac.category_id = category.id!!
|
||||
return ac
|
||||
}
|
||||
|
||||
fun create(anime: Anime, category: GetCategories): AnimeCategory {
|
||||
val ac = AnimeCategory()
|
||||
ac.anime_id = anime.id!!
|
||||
ac.category_id = category.id.toInt()
|
||||
return ac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.models
|
||||
|
||||
class AnimeEpisode(val anime: Anime, val episode: Episode)
|
|
@ -1,37 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.models
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Object containing the history statistics of a chapter
|
||||
*/
|
||||
interface AnimeHistory : Serializable {
|
||||
|
||||
/**
|
||||
* Id of history object.
|
||||
*/
|
||||
var id: Long?
|
||||
|
||||
/**
|
||||
* Chapter id of history object.
|
||||
*/
|
||||
var episode_id: Long
|
||||
|
||||
/**
|
||||
* Last time episode was read in time long format
|
||||
*/
|
||||
var last_seen: Long
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* History constructor
|
||||
*
|
||||
* @param chapter chapter object
|
||||
* @return history object
|
||||
*/
|
||||
fun create(episode: Episode): AnimeHistory = AnimeHistoryImpl().apply {
|
||||
this.episode_id = episode.id!!
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.models
|
||||
|
||||
/**
|
||||
* Object containing the history statistics of a chapter
|
||||
*/
|
||||
class AnimeHistoryImpl : AnimeHistory {
|
||||
|
||||
/**
|
||||
* Id of history object.
|
||||
*/
|
||||
override var id: Long? = null
|
||||
|
||||
/**
|
||||
* Chapter id of history object.
|
||||
*/
|
||||
override var episode_id: Long = 0
|
||||
|
||||
/**
|
||||
* Last time chapter was read in time long format
|
||||
*/
|
||||
override var last_seen: Long = 0
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.models
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Object containing the history statistics of a chapter
|
||||
*/
|
||||
interface History : Serializable {
|
||||
|
||||
/**
|
||||
* Id of history object.
|
||||
*/
|
||||
var id: Long?
|
||||
|
||||
/**
|
||||
* Chapter id of history object.
|
||||
*/
|
||||
var chapter_id: Long
|
||||
|
||||
/**
|
||||
* Last time chapter was read in time long format
|
||||
*/
|
||||
var last_read: Long
|
||||
|
||||
/**
|
||||
* Total time chapter was read
|
||||
*/
|
||||
var time_read: Long
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* History constructor
|
||||
*
|
||||
* @param chapter chapter object
|
||||
* @return history object
|
||||
*/
|
||||
fun create(chapter: Chapter): History = HistoryImpl().apply {
|
||||
this.chapter_id = chapter.id!!
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.models
|
||||
|
||||
/**
|
||||
* Object containing the history statistics of a chapter
|
||||
*/
|
||||
class HistoryImpl : History {
|
||||
|
||||
/**
|
||||
* Id of history object.
|
||||
*/
|
||||
override var id: Long? = null
|
||||
|
||||
/**
|
||||
* Chapter id of history object.
|
||||
*/
|
||||
override var chapter_id: Long = 0
|
||||
|
||||
/**
|
||||
* Last time chapter was read in time long format
|
||||
*/
|
||||
override var last_read: Long = 0
|
||||
|
||||
/**
|
||||
* Total time chapter was read
|
||||
*/
|
||||
override var time_read: Long = 0
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package eu.kanade.tachiyomi.data.database.models
|
||||
|
||||
import data.GetCategories
|
||||
|
||||
class MangaCategory {
|
||||
|
||||
var id: Long? = null
|
||||
|
@ -18,12 +16,5 @@ class MangaCategory {
|
|||
mc.category_id = category.id!!
|
||||
return mc
|
||||
}
|
||||
|
||||
fun create(manga: Manga, category: GetCategories): MangaCategory {
|
||||
val mc = MangaCategory()
|
||||
mc.manga_id = manga.id!!
|
||||
mc.category_id = category.id.toInt()
|
||||
return mc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.models
|
||||
|
||||
class MangaChapter(val manga: Manga, val chapter: Chapter)
|
|
@ -60,8 +60,6 @@ interface AnimeQueries : DbProvider {
|
|||
|
||||
fun insertAnime(anime: Anime) = db.put().`object`(anime).prepare()
|
||||
|
||||
fun insertAnimes(animes: List<Anime>) = db.put().objects(animes).prepare()
|
||||
|
||||
fun updateEpisodeFlags(anime: Anime) = db.put()
|
||||
.`object`(anime)
|
||||
.withPutResolver(AnimeFlagsPutResolver(AnimeTable.COL_EPISODE_FLAGS, Anime::episode_flags))
|
||||
|
@ -76,34 +74,4 @@ interface AnimeQueries : DbProvider {
|
|||
.`object`(anime)
|
||||
.withPutResolver(AnimeFlagsPutResolver(AnimeTable.COL_VIEWER, Anime::viewer_flags))
|
||||
.prepare()
|
||||
|
||||
fun getLastSeenAnime() = db.get()
|
||||
.listOfObjects(Anime::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getLastSeenAnimeQuery())
|
||||
.observesTables(AnimeTable.TABLE)
|
||||
.build(),
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getLatestEpisodeAnime() = db.get()
|
||||
.listOfObjects(Anime::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getLatestEpisodeAnimeQuery())
|
||||
.observesTables(AnimeTable.TABLE)
|
||||
.build(),
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getEpisodeFetchDateAnime() = db.get()
|
||||
.listOfObjects(Anime::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getEpisodeFetchDateAnimeQuery())
|
||||
.observesTables(AnimeTable.TABLE)
|
||||
.build(),
|
||||
)
|
||||
.prepare()
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.queries
|
||||
|
||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeTrackTable
|
||||
|
||||
interface AnimeTrackQueries : DbProvider {
|
||||
|
||||
fun getTracks() = db.get()
|
||||
.listOfObjects(AnimeTrack::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(AnimeTrackTable.TABLE)
|
||||
.build(),
|
||||
)
|
||||
.prepare()
|
||||
}
|
|
@ -3,9 +3,7 @@ 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.tables.CategoryTable
|
||||
|
||||
interface CategoryQueries : DbProvider {
|
||||
|
@ -20,25 +18,23 @@ interface CategoryQueries : DbProvider {
|
|||
)
|
||||
.prepare()
|
||||
|
||||
fun getCategoriesForManga(manga: Manga) = db.get()
|
||||
fun getCategoriesForManga(mangaId: Long) = db.get()
|
||||
.listOfObjects(Category::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getCategoriesForMangaQuery())
|
||||
.args(manga.id)
|
||||
.args(mangaId)
|
||||
.build(),
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getCategoriesForAnime(anime: Anime) = db.get()
|
||||
fun getCategoriesForAnime(animeId: Long) = db.get()
|
||||
.listOfObjects(Category::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getCategoriesForAnimeQuery())
|
||||
.args(anime.id)
|
||||
.args(animeId)
|
||||
.build(),
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun insertCategory(category: Category) = db.put().`object`(category).prepare()
|
||||
}
|
||||
|
|
|
@ -3,19 +3,18 @@ package eu.kanade.tachiyomi.data.database.queries
|
|||
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver
|
||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||
|
||||
interface ChapterQueries : DbProvider {
|
||||
|
||||
fun getChapters(manga: Manga) = db.get()
|
||||
fun getChapters(mangaId: Long) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.whereArgs(mangaId)
|
||||
.build(),
|
||||
)
|
||||
.prepare()
|
||||
|
@ -46,9 +45,4 @@ interface ChapterQueries : DbProvider {
|
|||
.`object`(chapter)
|
||||
.withPutResolver(ChapterProgressPutResolver())
|
||||
.prepare()
|
||||
|
||||
fun updateChaptersProgress(chapters: List<Chapter>) = db.put()
|
||||
.objects(chapters)
|
||||
.withPutResolver(ChapterProgressPutResolver())
|
||||
.prepare()
|
||||
}
|
||||
|
|
|
@ -2,20 +2,19 @@ package eu.kanade.tachiyomi.data.database.queries
|
|||
|
||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||
import eu.kanade.tachiyomi.data.database.models.Anime
|
||||
import eu.kanade.tachiyomi.data.database.models.Episode
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.EpisodeProgressPutResolver
|
||||
import eu.kanade.tachiyomi.data.database.tables.EpisodeTable
|
||||
|
||||
interface EpisodeQueries : DbProvider {
|
||||
|
||||
fun getEpisodes(anime: Anime) = db.get()
|
||||
fun getEpisodes(animeId: Long) = db.get()
|
||||
.listOfObjects(Episode::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(EpisodeTable.TABLE)
|
||||
.where("${EpisodeTable.COL_ANIME_ID} = ?")
|
||||
.whereArgs(anime.id)
|
||||
.whereArgs(animeId)
|
||||
.build(),
|
||||
)
|
||||
.prepare()
|
||||
|
@ -57,9 +56,4 @@ interface EpisodeQueries : DbProvider {
|
|||
.`object`(episode)
|
||||
.withPutResolver(EpisodeProgressPutResolver())
|
||||
.prepare()
|
||||
|
||||
fun updateEpisodesProgress(episodes: List<Episode>) = db.put()
|
||||
.objects(episodes)
|
||||
.withPutResolver(EpisodeProgressPutResolver())
|
||||
.prepare()
|
||||
}
|
||||
|
|
|
@ -60,8 +60,6 @@ interface MangaQueries : DbProvider {
|
|||
|
||||
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
|
||||
|
||||
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
|
||||
|
||||
fun updateChapterFlags(manga: Manga) = db.put()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags))
|
||||
|
@ -76,34 +74,4 @@ interface MangaQueries : DbProvider {
|
|||
.`object`(manga)
|
||||
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags))
|
||||
.prepare()
|
||||
|
||||
fun getLastReadManga() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getLastReadMangaQuery())
|
||||
.observesTables(MangaTable.TABLE)
|
||||
.build(),
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getLatestChapterManga() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getLatestChapterMangaQuery())
|
||||
.observesTables(MangaTable.TABLE)
|
||||
.build(),
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChapterFetchDateManga() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getChapterFetchDateMangaQuery())
|
||||
.observesTables(MangaTable.TABLE)
|
||||
.build(),
|
||||
)
|
||||
.prepare()
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package eu.kanade.tachiyomi.data.database.queries
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeCategoryTable as AnimeCategory
|
||||
import eu.kanade.tachiyomi.data.database.tables.AnimeHistoryTable as AnimeHistory
|
||||
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.EpisodeTable as Episode
|
||||
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.MangaTable as Manga
|
||||
|
||||
|
@ -71,72 +69,6 @@ val animelibQuery =
|
|||
ON MC.${AnimeCategory.COL_ANIME_ID} = M.${Anime.COL_ID}
|
||||
"""
|
||||
|
||||
fun getLastReadMangaQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
JOIN ${History.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
WHERE ${Manga.TABLE}.${Manga.COL_FAVORITE} = 1
|
||||
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
|
||||
ORDER BY max DESC
|
||||
"""
|
||||
|
||||
fun getLastSeenAnimeQuery() =
|
||||
"""
|
||||
SELECT ${Anime.TABLE}.*, MAX(${AnimeHistory.TABLE}.${AnimeHistory.COL_LAST_SEEN}) AS max
|
||||
FROM ${Anime.TABLE}
|
||||
JOIN ${Episode.TABLE}
|
||||
ON ${Anime.TABLE}.${Anime.COL_ID} = ${Episode.TABLE}.${Episode.COL_ANIME_ID}
|
||||
JOIN ${AnimeHistory.TABLE}
|
||||
ON ${Episode.TABLE}.${Episode.COL_ID} = ${AnimeHistory.TABLE}.${AnimeHistory.COL_EPISODE_ID}
|
||||
WHERE ${Anime.TABLE}.${Anime.COL_FAVORITE} = 1
|
||||
GROUP BY ${Anime.TABLE}.${Anime.COL_ID}
|
||||
ORDER BY max DESC
|
||||
"""
|
||||
|
||||
fun getLatestChapterMangaQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*, MAX(${Chapter.TABLE}.${Chapter.COL_DATE_UPLOAD}) AS max
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
|
||||
ORDER by max DESC
|
||||
"""
|
||||
|
||||
fun getLatestEpisodeAnimeQuery() =
|
||||
"""
|
||||
SELECT ${Anime.TABLE}.*, MAX(${Episode.TABLE}.${Episode.COL_DATE_UPLOAD}) AS max
|
||||
FROM ${Anime.TABLE}
|
||||
JOIN ${Episode.TABLE}
|
||||
ON ${Anime.TABLE}.${Anime.COL_ID} = ${Episode.TABLE}.${Episode.COL_ANIME_ID}
|
||||
GROUP BY ${Anime.TABLE}.${Anime.COL_ID}
|
||||
ORDER by max DESC
|
||||
"""
|
||||
|
||||
fun getChapterFetchDateMangaQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*, MAX(${Chapter.TABLE}.${Chapter.COL_DATE_FETCH}) AS max
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
|
||||
ORDER by max DESC
|
||||
"""
|
||||
|
||||
fun getEpisodeFetchDateAnimeQuery() =
|
||||
"""
|
||||
SELECT ${Anime.TABLE}.*, MAX(${Episode.TABLE}.${Episode.COL_DATE_FETCH}) AS max
|
||||
FROM ${Anime.TABLE}
|
||||
JOIN ${Episode.TABLE}
|
||||
ON ${Anime.TABLE}.${Anime.COL_ID} = ${Episode.TABLE}.${Episode.COL_ANIME_ID}
|
||||
GROUP BY ${Anime.TABLE}.${Anime.COL_ID}
|
||||
ORDER by max DESC
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the categories for a manga.
|
||||
*/
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.queries
|
||||
|
||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable
|
||||
|
||||
interface TrackQueries : DbProvider {
|
||||
|
||||
fun getTracks() = db.get()
|
||||
.listOfObjects(Track::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(TrackTable.TABLE)
|
||||
.build(),
|
||||
)
|
||||
.prepare()
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
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.AnimeGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.mappers.EpisodeGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeEpisode
|
||||
|
||||
class AnimeEpisodeGetResolver : DefaultGetResolver<AnimeEpisode>() {
|
||||
|
||||
companion object {
|
||||
val INSTANCE = AnimeEpisodeGetResolver()
|
||||
}
|
||||
|
||||
private val animeGetResolver = AnimeGetResolver()
|
||||
|
||||
private val episodeGetResolver = EpisodeGetResolver()
|
||||
|
||||
override fun mapFromCursor(cursor: Cursor): AnimeEpisode {
|
||||
val anime = animeGetResolver.mapFromCursor(cursor)
|
||||
val episode = episodeGetResolver.mapFromCursor(cursor)
|
||||
anime.id = episode.anime_id
|
||||
anime.url = cursor.getString(cursor.getColumnIndexOrThrow("animeUrl"))
|
||||
|
||||
return AnimeEpisode(anime, episode)
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
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.ChapterGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.mappers.MangaGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||
|
||||
class MangaChapterGetResolver : DefaultGetResolver<MangaChapter>() {
|
||||
|
||||
companion object {
|
||||
val INSTANCE = MangaChapterGetResolver()
|
||||
}
|
||||
|
||||
private val mangaGetResolver = MangaGetResolver()
|
||||
|
||||
private val chapterGetResolver = ChapterGetResolver()
|
||||
|
||||
override fun mapFromCursor(cursor: Cursor): MangaChapter {
|
||||
val manga = mangaGetResolver.mapFromCursor(cursor)
|
||||
val chapter = chapterGetResolver.mapFromCursor(cursor)
|
||||
manga.id = chapter.manga_id
|
||||
manga.url = cursor.getString(cursor.getColumnIndexOrThrow("mangaUrl"))
|
||||
|
||||
return MangaChapter(manga, chapter)
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.tables
|
||||
|
||||
object AnimeHistoryTable {
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
const val TABLE = "animehistory"
|
||||
|
||||
/**
|
||||
* Id column name
|
||||
*/
|
||||
const val COL_ID = "_id"
|
||||
|
||||
/**
|
||||
* Episode id column name
|
||||
*/
|
||||
const val COL_EPISODE_ID = "episode_id"
|
||||
|
||||
/**
|
||||
* Last seen column name
|
||||
*/
|
||||
const val COL_LAST_SEEN = "last_seen"
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.tables
|
||||
|
||||
object AnimeTrackTable {
|
||||
|
||||
const val TABLE = "anime_sync"
|
||||
|
||||
const val COL_ID = "_id"
|
||||
|
||||
const val COL_ANIME_ID = "anime_id"
|
||||
|
||||
const val COL_SYNC_ID = "sync_id"
|
||||
|
||||
const val COL_MEDIA_ID = "remote_id"
|
||||
|
||||
const val COL_LIBRARY_ID = "library_id"
|
||||
|
||||
const val COL_TITLE = "title"
|
||||
|
||||
const val COL_LAST_EPISODE_SEEN = "last_episode_seen"
|
||||
|
||||
const val COL_STATUS = "status"
|
||||
|
||||
const val COL_SCORE = "score"
|
||||
|
||||
const val COL_TOTAL_EPISODES = "total_episodes"
|
||||
|
||||
const val COL_TRACKING_URL = "remote_url"
|
||||
|
||||
const val COL_START_DATE = "start_date"
|
||||
|
||||
const val COL_FINISH_DATE = "finish_date"
|
||||
|
||||
val insertFromTempTable: String
|
||||
get() =
|
||||
"""
|
||||
|INSERT INTO $TABLE($COL_ID,$COL_ANIME_ID,$COL_SYNC_ID,$COL_MEDIA_ID,$COL_LIBRARY_ID,$COL_TITLE,$COL_LAST_EPISODE_SEEN,$COL_TOTAL_EPISODES,$COL_STATUS,$COL_SCORE,$COL_TRACKING_URL,$COL_START_DATE,$COL_FINISH_DATE)
|
||||
|SELECT $COL_ID,$COL_ANIME_ID,$COL_SYNC_ID,$COL_MEDIA_ID,$COL_LIBRARY_ID,$COL_TITLE,$COL_LAST_EPISODE_SEEN,$COL_TOTAL_EPISODES,$COL_STATUS,$COL_SCORE,$COL_TRACKING_URL,$COL_START_DATE,$COL_FINISH_DATE
|
||||
|FROM ${TABLE}_tmp
|
||||
""".trimMargin()
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.tables
|
||||
|
||||
object HistoryTable {
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
const val TABLE = "history"
|
||||
|
||||
/**
|
||||
* Id column name
|
||||
*/
|
||||
const val COL_ID = "_id"
|
||||
|
||||
/**
|
||||
* Chapter id column name
|
||||
*/
|
||||
const val COL_CHAPTER_ID = "chapter_id"
|
||||
|
||||
/**
|
||||
* Last read column name
|
||||
*/
|
||||
const val COL_LAST_READ = "last_read"
|
||||
|
||||
/**
|
||||
* Time read column name
|
||||
*/
|
||||
const val COL_TIME_READ = "time_read"
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.database.tables
|
||||
|
||||
object TrackTable {
|
||||
|
||||
const val TABLE = "manga_sync"
|
||||
|
||||
const val COL_ID = "_id"
|
||||
|
||||
const val COL_MANGA_ID = "manga_id"
|
||||
|
||||
const val COL_SYNC_ID = "sync_id"
|
||||
|
||||
const val COL_MEDIA_ID = "remote_id"
|
||||
|
||||
const val COL_LIBRARY_ID = "library_id"
|
||||
|
||||
const val COL_TITLE = "title"
|
||||
|
||||
const val COL_LAST_CHAPTER_READ = "last_chapter_read"
|
||||
|
||||
const val COL_STATUS = "status"
|
||||
|
||||
const val COL_SCORE = "score"
|
||||
|
||||
const val COL_TOTAL_CHAPTERS = "total_chapters"
|
||||
|
||||
const val COL_TRACKING_URL = "remote_url"
|
||||
|
||||
const val COL_START_DATE = "start_date"
|
||||
|
||||
const val COL_FINISH_DATE = "finish_date"
|
||||
}
|
|
@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.data.download.model.AnimeDownloadQueue
|
|||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import logcat.LogPriority
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
|
@ -104,10 +105,12 @@ class AnimeDownloadManager(
|
|||
|
||||
fun startDownloadNow(episodeId: Long?) {
|
||||
if (episodeId == null) return
|
||||
val download = downloader.queue.find { it.episode.id == episodeId } ?: return
|
||||
val download = downloader.queue.find { it.episode.id == episodeId }
|
||||
// If not in queue try to start a new download
|
||||
val toAdd = download ?: runBlocking { AnimeDownload.fromEpisodeId(episodeId) } ?: return
|
||||
val queue = downloader.queue.toMutableList()
|
||||
queue.remove(download)
|
||||
queue.add(0, download)
|
||||
download?.let { queue.remove(it) }
|
||||
queue.add(0, toAdd)
|
||||
reorderQueue(queue)
|
||||
if (isPaused()) {
|
||||
if (AnimeDownloadService.isRunning(context)) {
|
||||
|
@ -363,7 +366,7 @@ class AnimeDownloadManager(
|
|||
private fun getEpisodesToDelete(episodes: List<Episode>, anime: Anime): List<Episode> {
|
||||
// Retrieve the categories that are set to exclude from being deleted on read
|
||||
val categoriesToExclude = preferences.removeExcludeAnimeCategories().get().map(String::toInt)
|
||||
val categoriesForAnime = db.getCategoriesForAnime(anime).executeAsBlocking()
|
||||
val categoriesForAnime = db.getCategoriesForAnime(anime.id!!).executeAsBlocking()
|
||||
.mapNotNull { it.id }
|
||||
.takeUnless { it.isEmpty() }
|
||||
?: listOf(0)
|
||||
|
|
|
@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.source.SourceManager
|
|||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import logcat.LogPriority
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
|
@ -104,10 +105,12 @@ class DownloadManager(
|
|||
|
||||
fun startDownloadNow(chapterId: Long?) {
|
||||
if (chapterId == null) return
|
||||
val download = downloader.queue.find { it.chapter.id == chapterId } ?: return
|
||||
val download = downloader.queue.find { it.chapter.id == chapterId }
|
||||
// If not in queue try to start a new download
|
||||
val toAdd = download ?: runBlocking { Download.fromChapterId(chapterId) } ?: return
|
||||
val queue = downloader.queue.toMutableList()
|
||||
queue.remove(download)
|
||||
queue.add(0, download)
|
||||
download?.let { queue.remove(it) }
|
||||
queue.add(0, toAdd)
|
||||
reorderQueue(queue)
|
||||
if (isPaused()) {
|
||||
if (DownloadService.isRunning(context)) {
|
||||
|
@ -359,7 +362,7 @@ class DownloadManager(
|
|||
private fun getChaptersToDelete(chapters: List<Chapter>, manga: Manga): List<Chapter> {
|
||||
// Retrieve the categories that are set to exclude from being deleted on read
|
||||
val categoriesToExclude = preferences.removeExcludeCategories().get().map(String::toInt)
|
||||
val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking()
|
||||
val categoriesForManga = db.getCategoriesForManga(manga.id!!).executeAsBlocking()
|
||||
.mapNotNull { it.id }
|
||||
.takeUnless { it.isEmpty() }
|
||||
?: listOf(0)
|
||||
|
|
|
@ -493,7 +493,12 @@ class Downloader(
|
|||
// check if the original page was previously splitted before then skip.
|
||||
if (imageFile.name!!.contains("__")) return true
|
||||
|
||||
return ImageUtil.splitTallImage(imageFile, imageFilePath)
|
||||
return try {
|
||||
ImageUtil.splitTallImage(imageFile, imageFilePath)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
package eu.kanade.tachiyomi.data.download.model
|
||||
|
||||
import eu.kanade.domain.anime.interactor.GetAnimeById
|
||||
import eu.kanade.domain.anime.model.toDbAnime
|
||||
import eu.kanade.domain.episode.interactor.GetEpisode
|
||||
import eu.kanade.domain.episode.model.toDbEpisode
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.data.database.models.Anime
|
||||
import eu.kanade.tachiyomi.data.database.models.Episode
|
||||
import rx.subjects.PublishSubject
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
data class AnimeDownload(
|
||||
val source: AnimeHttpSource,
|
||||
|
@ -77,4 +84,19 @@ data class AnimeDownload(
|
|||
DOWNLOADED(3),
|
||||
ERROR(4),
|
||||
}
|
||||
|
||||
companion object {
|
||||
suspend fun fromEpisodeId(
|
||||
chapterId: Long,
|
||||
getEpisode: GetEpisode = Injekt.get(),
|
||||
getAnimeById: GetAnimeById = Injekt.get(),
|
||||
sourceManager: AnimeSourceManager = Injekt.get(),
|
||||
): AnimeDownload? {
|
||||
val episode = getEpisode.await(chapterId) ?: return null
|
||||
val anime = getAnimeById.await(episode.animeId) ?: return null
|
||||
val source = sourceManager.get(anime.source) as? AnimeHttpSource ?: return null
|
||||
|
||||
return AnimeDownload(source, anime.toDbAnime(), episode.toDbEpisode())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
package eu.kanade.tachiyomi.data.download.model
|
||||
|
||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
||||
import eu.kanade.domain.chapter.model.toDbChapter
|
||||
import eu.kanade.domain.manga.interactor.GetMangaById
|
||||
import eu.kanade.domain.manga.model.toDbManga
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import rx.subjects.PublishSubject
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
data class Download(
|
||||
val source: HttpSource,
|
||||
|
@ -57,4 +64,19 @@ data class Download(
|
|||
DOWNLOADED(3),
|
||||
ERROR(4),
|
||||
}
|
||||
|
||||
companion object {
|
||||
suspend fun fromChapterId(
|
||||
chapterId: Long,
|
||||
getChapter: GetChapter = Injekt.get(),
|
||||
getMangaById: GetMangaById = Injekt.get(),
|
||||
sourceManager: SourceManager = Injekt.get(),
|
||||
): Download? {
|
||||
val chapter = getChapter.await(chapterId) ?: return null
|
||||
val manga = getMangaById.await(chapter.mangaId) ?: return null
|
||||
val source = sourceManager.get(manga.source) as? HttpSource ?: return null
|
||||
|
||||
return Download(source, manga.toDbManga(), chapter.toDbChapter())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack
|
|||
import eu.kanade.domain.animetrack.model.toDbTrack
|
||||
import eu.kanade.domain.animetrack.model.toDomainTrack
|
||||
import eu.kanade.domain.category.interactor.GetCategoriesAnime
|
||||
import eu.kanade.domain.category.interactor.MoveAnimeToCategories
|
||||
import eu.kanade.domain.category.interactor.SetAnimeCategories
|
||||
import eu.kanade.domain.episode.interactor.SyncEpisodesWithSource
|
||||
import eu.kanade.domain.episode.interactor.SyncEpisodesWithTrackServiceTwoWay
|
||||
import eu.kanade.domain.episode.interactor.UpdateEpisode
|
||||
|
@ -91,7 +91,7 @@ class AnimePresenter(
|
|||
private val getCategories: GetCategoriesAnime = Injekt.get(),
|
||||
private val deleteTrack: DeleteAnimeTrack = Injekt.get(),
|
||||
private val getTracks: GetAnimeTracks = Injekt.get(),
|
||||
private val moveAnimeToCategories: MoveAnimeToCategories = Injekt.get(),
|
||||
private val moveAnimeToCategories: SetAnimeCategories = Injekt.get(),
|
||||
private val insertTrack: InsertAnimeTrack = Injekt.get(),
|
||||
private val syncEpisodesWithTrackServiceTwoWay: SyncEpisodesWithTrackServiceTwoWay = Injekt.get(),
|
||||
) : BasePresenter<AnimeController>() {
|
||||
|
|
|
@ -29,6 +29,8 @@ import eu.kanade.tachiyomi.ui.base.controller.TabbedController
|
|||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchController
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.preference.asImmediateFlow
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
|
@ -36,6 +38,7 @@ import eu.kanade.tachiyomi.util.system.toast
|
|||
import eu.kanade.tachiyomi.widget.ActionModeWithToolbar
|
||||
import eu.kanade.tachiyomi.widget.EmptyView
|
||||
import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -226,6 +229,7 @@ class AnimelibController(
|
|||
destroyActionModeIfNeeded()
|
||||
adapter?.onDestroy()
|
||||
adapter = null
|
||||
settingsSheet?.sheetScope?.cancel()
|
||||
settingsSheet = null
|
||||
tabsVisibilitySubscription?.unsubscribe()
|
||||
tabsVisibilitySubscription = null
|
||||
|
@ -541,25 +545,29 @@ class AnimelibController(
|
|||
* Move the selected anime to a list of categories.
|
||||
*/
|
||||
private fun showAnimeCategoriesDialog() {
|
||||
// Create a copy of selected anime
|
||||
val animes = selectedAnimes.toList()
|
||||
viewScope.launchIO {
|
||||
// Create a copy of selected anime
|
||||
val animes = selectedAnimes.toList()
|
||||
|
||||
// Hide the default category because it has a different behavior than the ones from db.
|
||||
val categories = presenter.categories.filter { it.id != 0 }
|
||||
// Hide the default category because it has a different behavior than the ones from db.
|
||||
val categories = presenter.categories.filter { it.id != 0 }
|
||||
|
||||
// Get indexes of the common categories to preselect.
|
||||
val common = presenter.getCommonCategories(animes)
|
||||
// Get indexes of the mix categories to preselect.
|
||||
val mix = presenter.getMixCategories(animes)
|
||||
val preselected = categories.map {
|
||||
when (it) {
|
||||
in common -> QuadStateTextView.State.CHECKED.ordinal
|
||||
in mix -> QuadStateTextView.State.INDETERMINATE.ordinal
|
||||
else -> QuadStateTextView.State.UNCHECKED.ordinal
|
||||
// Get indexes of the common categories to preselect.
|
||||
val common = presenter.getCommonCategories(animes)
|
||||
// Get indexes of the mix categories to preselect.
|
||||
val mix = presenter.getMixCategories(animes)
|
||||
val preselected = categories.map {
|
||||
when (it) {
|
||||
in common -> QuadStateTextView.State.CHECKED.ordinal
|
||||
in mix -> QuadStateTextView.State.INDETERMINATE.ordinal
|
||||
else -> QuadStateTextView.State.UNCHECKED.ordinal
|
||||
}
|
||||
}.toIntArray()
|
||||
launchUI {
|
||||
ChangeAnimeCategoriesDialog(this@AnimelibController, animes, categories, preselected)
|
||||
.showDialog(router)
|
||||
}
|
||||
}.toIntArray()
|
||||
ChangeAnimeCategoriesDialog(this, animes, categories, preselected)
|
||||
.showDialog(router)
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadUnseenEpisodes() {
|
||||
|
@ -579,7 +587,7 @@ class AnimelibController(
|
|||
}
|
||||
|
||||
override fun updateCategoriesForAnimes(animes: List<Anime>, addCategories: List<Category>, removeCategories: List<Category>) {
|
||||
presenter.updateAnimesToCategories(animes, addCategories, removeCategories)
|
||||
presenter.setAnimeCategories(animes, addCategories, removeCategories)
|
||||
destroyActionModeIfNeeded()
|
||||
}
|
||||
|
||||
|
|
|
@ -2,13 +2,24 @@ package eu.kanade.tachiyomi.ui.animelib
|
|||
|
||||
import android.os.Bundle
|
||||
import com.jakewharton.rxrelay.BehaviorRelay
|
||||
import eu.kanade.core.util.asObservable
|
||||
import eu.kanade.data.AnimeDatabaseHandler
|
||||
import eu.kanade.domain.anime.interactor.UpdateAnime
|
||||
import eu.kanade.domain.anime.model.AnimeUpdate
|
||||
import eu.kanade.domain.animetrack.interactor.GetAnimeTracks
|
||||
import eu.kanade.domain.category.interactor.GetCategoriesAnime
|
||||
import eu.kanade.domain.category.interactor.SetAnimeCategories
|
||||
import eu.kanade.domain.category.model.toDbCategory
|
||||
import eu.kanade.domain.episode.interactor.GetEpisodeByAnimeId
|
||||
import eu.kanade.domain.episode.interactor.UpdateEpisode
|
||||
import eu.kanade.domain.episode.model.EpisodeUpdate
|
||||
import eu.kanade.domain.episode.model.toDbEpisode
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
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.AnimeCategory
|
||||
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.download.AnimeDownloadManager
|
||||
|
@ -23,6 +34,8 @@ import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
|
|||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
|
@ -47,7 +60,13 @@ private typealias AnimelibMap = Map<Int, List<AnimelibItem>>
|
|||
* Presenter of [AnimelibController].
|
||||
*/
|
||||
class AnimelibPresenter(
|
||||
private val db: AnimeDatabaseHelper = Injekt.get(),
|
||||
private val handler: AnimeDatabaseHandler = Injekt.get(),
|
||||
private val getTracks: GetAnimeTracks = Injekt.get(),
|
||||
private val getCategories: GetCategoriesAnime = Injekt.get(),
|
||||
private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get(),
|
||||
private val updateEpisode: UpdateEpisode = Injekt.get(),
|
||||
private val updateAnime: UpdateAnime = Injekt.get(),
|
||||
private val setAnimeCategories: SetAnimeCategories = Injekt.get(),
|
||||
private val preferences: PreferencesHelper = Injekt.get(),
|
||||
private val coverCache: AnimeCoverCache = Injekt.get(),
|
||||
private val sourceManager: AnimeSourceManager = Injekt.get(),
|
||||
|
@ -92,6 +111,7 @@ class AnimelibPresenter(
|
|||
* Subscribes to animelib if needed.
|
||||
*/
|
||||
fun subscribeAnimelib() {
|
||||
// TODO: Move this to a coroutine world
|
||||
if (animelibSubscription.isNullOrUnsubscribed()) {
|
||||
animelibSubscription = getAnimelibObservable()
|
||||
.combineLatest(badgeTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
|
||||
|
@ -115,7 +135,7 @@ class AnimelibPresenter(
|
|||
*
|
||||
* @param map the map to filter.
|
||||
*/
|
||||
private fun applyFilters(map: AnimelibMap, trackMap: Map<Long, Map<Int, Boolean>>): AnimelibMap {
|
||||
private fun applyFilters(map: AnimelibMap, trackMap: Map<Long, Map<Long, Boolean>>): AnimelibMap {
|
||||
val downloadedOnly = preferences.downloadedOnly().get()
|
||||
val filterDownloaded = preferences.filterDownloaded().get()
|
||||
val filterUnseen = preferences.filterUnread().get()
|
||||
|
@ -251,20 +271,32 @@ class AnimelibPresenter(
|
|||
* @param map the map to sort.
|
||||
*/
|
||||
private fun applySort(categories: List<Category>, map: AnimelibMap): AnimelibMap {
|
||||
val lastReadAnime by lazy {
|
||||
val lastSeenAnime by lazy {
|
||||
var counter = 0
|
||||
// Result comes as newest to oldest so it's reversed
|
||||
db.getLastSeenAnime().executeAsBlocking().reversed().associate { it.id!! to counter++ }
|
||||
// TODO: Make [applySort] a suspended function
|
||||
runBlocking {
|
||||
handler.awaitList {
|
||||
animesQueries.getLastSeen()
|
||||
}.associate { it._id to counter++ }
|
||||
}
|
||||
}
|
||||
val latestChapterAnime by lazy {
|
||||
val latestEpisodeAnime by lazy {
|
||||
var counter = 0
|
||||
// Result comes as newest to oldest so it's reversed
|
||||
db.getLatestEpisodeAnime().executeAsBlocking().reversed().associate { it.id!! to counter++ }
|
||||
// TODO: Make [applySort] a suspended function
|
||||
runBlocking {
|
||||
handler.awaitList {
|
||||
animesQueries.getLatestByEpisodeUploadDate()
|
||||
}.associate { it._id to counter++ }
|
||||
}
|
||||
}
|
||||
val chapterFetchDateAnime by lazy {
|
||||
var counter = 0
|
||||
// Result comes as newest to oldest so it's reversed
|
||||
db.getEpisodeFetchDateAnime().executeAsBlocking().reversed().associate { it.id!! to counter++ }
|
||||
// TODO: Make [applySort] a suspended function
|
||||
runBlocking {
|
||||
handler.awaitList {
|
||||
animesQueries.getLatestByEpisodeFetchDate()
|
||||
}.associate { it._id to counter++ }
|
||||
}
|
||||
}
|
||||
|
||||
val sortingModes = categories.associate { category ->
|
||||
|
@ -287,8 +319,8 @@ class AnimelibPresenter(
|
|||
collator.compare(i1.anime.title.lowercase(locale), i2.anime.title.lowercase(locale))
|
||||
}
|
||||
SortModeSetting.LAST_READ -> {
|
||||
val anime1LastRead = lastReadAnime[i1.anime.id!!] ?: 0
|
||||
val anime2LastRead = lastReadAnime[i2.anime.id!!] ?: 0
|
||||
val anime1LastRead = lastSeenAnime[i1.anime.id!!] ?: 0
|
||||
val anime2LastRead = lastSeenAnime[i2.anime.id!!] ?: 0
|
||||
anime1LastRead.compareTo(anime2LastRead)
|
||||
}
|
||||
SortModeSetting.LAST_MANGA_UPDATE -> {
|
||||
|
@ -305,10 +337,10 @@ class AnimelibPresenter(
|
|||
i1.anime.totalEpisodes.compareTo(i2.anime.totalEpisodes)
|
||||
}
|
||||
SortModeSetting.LATEST_CHAPTER -> {
|
||||
val anime1latestEpisode = latestChapterAnime[i1.anime.id!!]
|
||||
?: latestChapterAnime.size
|
||||
val anime2latestEpisode = latestChapterAnime[i2.anime.id!!]
|
||||
?: latestChapterAnime.size
|
||||
val anime1latestEpisode = latestEpisodeAnime[i1.anime.id!!]
|
||||
?: latestEpisodeAnime.size
|
||||
val anime2latestEpisode = latestEpisodeAnime[i2.anime.id!!]
|
||||
?: latestEpisodeAnime.size
|
||||
anime1latestEpisode.compareTo(anime2latestEpisode)
|
||||
}
|
||||
SortModeSetting.CHAPTER_FETCH_DATE -> {
|
||||
|
@ -367,7 +399,7 @@ class AnimelibPresenter(
|
|||
* @return an observable of the categories.
|
||||
*/
|
||||
private fun getCategoriesObservable(): Observable<List<Category>> {
|
||||
return db.getCategories().asRxObservable()
|
||||
return getCategories.subscribe().map { it.map { it.toDbCategory() } }.asObservable()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -379,7 +411,36 @@ class AnimelibPresenter(
|
|||
private fun getAnimelibAnimesObservable(): Observable<AnimelibMap> {
|
||||
val defaultLibraryDisplayMode = preferences.libraryDisplayMode()
|
||||
val shouldSetFromCategory = preferences.categorizedDisplaySettings()
|
||||
return db.getAnimelibAnimes().asRxObservable()
|
||||
|
||||
// TODO: Move this to domain/data layer
|
||||
return handler
|
||||
.subscribeToList {
|
||||
animesQueries.getAnimelib { _id: Long, source: Long, url: String, artist: String?, author: String?, description: String?, genre: List<String>?, title: String, status: Long, thumbnail_url: String?, favorite: Boolean, last_update: Long?, next_update: Long?, initialized: Boolean, viewer: Long, episode_flags: Long, cover_last_modified: Long, date_added: Long, unseen_count: Long, seen_count: Long, category: Long ->
|
||||
AnimelibAnime().apply {
|
||||
this.id = _id
|
||||
this.source = source
|
||||
this.url = url
|
||||
this.artist = artist
|
||||
this.author = author
|
||||
this.description = description
|
||||
this.genre = genre?.joinToString()
|
||||
this.title = title
|
||||
this.status = status.toInt()
|
||||
this.thumbnail_url = thumbnail_url
|
||||
this.favorite = favorite
|
||||
this.last_update = last_update ?: 0
|
||||
this.initialized = initialized
|
||||
this.viewer_flags = viewer.toInt()
|
||||
this.episode_flags = episode_flags.toInt()
|
||||
this.cover_last_modified = cover_last_modified
|
||||
this.date_added = date_added
|
||||
this.unseenCount = unseen_count.toInt()
|
||||
this.seenCount = seen_count.toInt()
|
||||
this.category = category.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
.asObservable()
|
||||
.map { list ->
|
||||
list.map { animelibAnime ->
|
||||
// Display mode based on user preference: take it from global library setting or category
|
||||
|
@ -397,7 +458,7 @@ class AnimelibPresenter(
|
|||
*
|
||||
* @return an observable of tracked anime.
|
||||
*/
|
||||
private fun getFilterObservable(): Observable<Map<Long, Map<Int, Boolean>>> {
|
||||
private fun getFilterObservable(): Observable<Map<Long, Map<Long, Boolean>>> {
|
||||
return getTracksObservable().combineLatest(filterTriggerRelay.observeOn(Schedulers.io())) { tracks, _ -> tracks }
|
||||
}
|
||||
|
||||
|
@ -406,16 +467,20 @@ class AnimelibPresenter(
|
|||
*
|
||||
* @return an observable of tracked anime.
|
||||
*/
|
||||
private fun getTracksObservable(): Observable<Map<Long, Map<Int, Boolean>>> {
|
||||
return db.getTracks().asRxObservable().map { tracks ->
|
||||
tracks.groupBy { it.anime_id }
|
||||
.mapValues { tracksForAnimeId ->
|
||||
// Check if any of the trackers is logged in for the current anime id
|
||||
tracksForAnimeId.value.associate {
|
||||
Pair(it.sync_id, trackManager.getService(it.sync_id.toLong())?.isLogged ?: false)
|
||||
private fun getTracksObservable(): Observable<Map<Long, Map<Long, Boolean>>> {
|
||||
// TODO: Move this to domain/data layer
|
||||
return getTracks.subscribe()
|
||||
.asObservable().map { tracks ->
|
||||
tracks
|
||||
.groupBy { it.animeId }
|
||||
.mapValues { tracksForAnimeId ->
|
||||
// Check if any of the trackers is logged in for the current manga id
|
||||
tracksForAnimeId.value.associate {
|
||||
Pair(it.syncId, trackManager.getService(it.syncId)?.isLogged ?: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.observeOn(Schedulers.io())
|
||||
}
|
||||
.observeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -452,11 +517,11 @@ class AnimelibPresenter(
|
|||
*
|
||||
* @param animes the list of anime.
|
||||
*/
|
||||
fun getCommonCategories(animes: List<Anime>): Collection<Category> {
|
||||
suspend fun getCommonCategories(animes: List<Anime>): Collection<Category> {
|
||||
if (animes.isEmpty()) return emptyList()
|
||||
return animes.toSet()
|
||||
.map { db.getCategoriesForAnime(it).executeAsBlocking() }
|
||||
.reduce { set1: Iterable<Category>, set2 -> set1.intersect(set2).toMutableList() }
|
||||
.map { getCategories.await(it.id!!).map { it.toDbCategory() } }
|
||||
.reduce { set1, set2 -> set1.intersect(set2).toMutableList() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -464,9 +529,9 @@ class AnimelibPresenter(
|
|||
*
|
||||
* @param animes the list of anime.
|
||||
*/
|
||||
fun getMixCategories(animes: List<Anime>): Collection<Category> {
|
||||
suspend fun getMixCategories(animes: List<Anime>): Collection<Category> {
|
||||
if (animes.isEmpty()) return emptyList()
|
||||
val animeCategories = animes.toSet().map { db.getCategoriesForAnime(it).executeAsBlocking() }
|
||||
val animeCategories = animes.toSet().map { getCategories.await(it.id!!).map { it.toDbCategory() } }
|
||||
val common = animeCategories.reduce { set1, set2 -> set1.intersect(set2).toMutableList() }
|
||||
return animeCategories.flatten().distinct().subtract(common).toMutableList()
|
||||
}
|
||||
|
@ -479,8 +544,9 @@ class AnimelibPresenter(
|
|||
fun downloadUnseenEpisodes(animes: List<Anime>) {
|
||||
animes.forEach { anime ->
|
||||
launchIO {
|
||||
val episodes = db.getEpisodes(anime).executeAsBlocking()
|
||||
val episodes = getEpisodeByAnimeId.await(anime.id!!)
|
||||
.filter { !it.seen }
|
||||
.map { it.toDbEpisode() }
|
||||
|
||||
downloadManager.downloadEpisodes(anime, episodes)
|
||||
}
|
||||
|
@ -495,17 +561,20 @@ class AnimelibPresenter(
|
|||
fun markSeenStatus(animes: List<Anime>, seen: Boolean) {
|
||||
animes.forEach { anime ->
|
||||
launchIO {
|
||||
val episodes = db.getEpisodes(anime).executeAsBlocking()
|
||||
episodes.forEach {
|
||||
it.seen = seen
|
||||
if (!seen) {
|
||||
it.last_second_seen = 0
|
||||
val episodes = getEpisodeByAnimeId.await(anime.id!!)
|
||||
|
||||
val toUpdate = episodes
|
||||
.map { chapter ->
|
||||
EpisodeUpdate(
|
||||
seen = seen,
|
||||
lastSecondSeen = if (seen) 0 else null,
|
||||
id = chapter.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
db.updateEpisodesProgress(episodes).executeAsBlocking()
|
||||
updateEpisode.awaitAll(toUpdate)
|
||||
|
||||
if (seen && preferences.removeAfterMarkedAsRead()) {
|
||||
deleteEpisodes(anime, episodes)
|
||||
deleteEpisodes(anime, episodes.map { it.toDbEpisode() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -520,20 +589,23 @@ class AnimelibPresenter(
|
|||
/**
|
||||
* Remove the selected anime.
|
||||
*
|
||||
* @param animes the list of anime to delete.
|
||||
* @param animeList the list of anime to delete.
|
||||
* @param deleteFromAnimelib whether to delete anime from animelib.
|
||||
* @param deleteEpisodes whether to delete downloaded episodes.
|
||||
*/
|
||||
fun removeAnimes(animes: List<Anime>, deleteFromAnimelib: Boolean, deleteEpisodes: Boolean) {
|
||||
fun removeAnimes(animeList: List<Anime>, deleteFromAnimelib: Boolean, deleteEpisodes: Boolean) {
|
||||
launchIO {
|
||||
val animeToDelete = animes.distinctBy { it.id }
|
||||
val animeToDelete = animeList.distinctBy { it.id }
|
||||
|
||||
if (deleteFromAnimelib) {
|
||||
animeToDelete.forEach {
|
||||
it.favorite = false
|
||||
val toDelete = animeToDelete.map {
|
||||
it.removeCovers(coverCache)
|
||||
AnimeUpdate(
|
||||
favorite = false,
|
||||
id = it.id!!,
|
||||
)
|
||||
}
|
||||
db.insertAnimes(animeToDelete).executeAsBlocking()
|
||||
updateAnime.awaitAll(toDelete)
|
||||
}
|
||||
|
||||
if (deleteEpisodes) {
|
||||
|
@ -548,35 +620,22 @@ class AnimelibPresenter(
|
|||
}
|
||||
|
||||
/**
|
||||
* Move the given list of anime to categories.
|
||||
* Bulk update categories of anime using old and new common categories.
|
||||
*
|
||||
* @param categories the selected categories.
|
||||
* @param animes the list of anime to move.
|
||||
*/
|
||||
fun moveAnimesToCategories(categories: List<Category>, animes: List<Anime>) {
|
||||
val mc = mutableListOf<AnimeCategory>()
|
||||
|
||||
for (anime in animes) {
|
||||
categories.mapTo(mc) { AnimeCategory.create(anime, it) }
|
||||
}
|
||||
|
||||
db.setAnimeCategories(mc, animes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk update categories of animes using old and new common categories.
|
||||
*
|
||||
* @param animes the list of anime to move.
|
||||
* @param animeList the list of anime to move.
|
||||
* @param addCategories the categories to add for all animes.
|
||||
* @param removeCategories the categories to remove in all animes.
|
||||
*/
|
||||
fun updateAnimesToCategories(animes: List<Anime>, addCategories: List<Category>, removeCategories: List<Category>) {
|
||||
val animeCategories = animes.map { anime ->
|
||||
val categories = db.getCategoriesForAnime(anime).executeAsBlocking()
|
||||
.subtract(removeCategories).plus(addCategories).distinct()
|
||||
categories.map { AnimeCategory.create(anime, it) }
|
||||
}.flatten()
|
||||
|
||||
db.setAnimeCategories(animeCategories, animes)
|
||||
fun setAnimeCategories(animeList: List<Anime>, addCategories: List<Category>, removeCategories: List<Category>) {
|
||||
presenterScope.launchIO {
|
||||
animeList.map { anime ->
|
||||
val categoryIds = getCategories.await(anime.id!!)
|
||||
.map { it.toDbCategory() }
|
||||
.subtract(removeCategories)
|
||||
.plus(addCategories)
|
||||
.mapNotNull { it.id?.toLong() }
|
||||
setAnimeCategories.await(anime.id!!, categoryIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,9 @@ import android.content.Context
|
|||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import eu.kanade.domain.category.interactor.UpdateCategoryAnime
|
||||
import eu.kanade.domain.category.model.CategoryUpdate
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.track.MangaTrackService
|
||||
|
@ -14,9 +15,13 @@ import eu.kanade.tachiyomi.data.track.TrackService
|
|||
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
|
||||
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
|
||||
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
|
||||
import eu.kanade.tachiyomi.widget.sheet.TabbedBottomSheetDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
@ -24,13 +29,15 @@ import uy.kohesive.injekt.injectLazy
|
|||
class AnimelibSettingsSheet(
|
||||
router: Router,
|
||||
private val trackManager: TrackManager = Injekt.get(),
|
||||
private val updateCategory: UpdateCategoryAnime = Injekt.get(),
|
||||
onGroupClickListener: (ExtendedNavigationView.Group) -> Unit,
|
||||
) : TabbedBottomSheetDialog(router.activity!!) {
|
||||
|
||||
val filters: Filter
|
||||
private val sort: Sort
|
||||
private val display: Display
|
||||
private val db: AnimeDatabaseHelper by injectLazy()
|
||||
|
||||
val sheetScope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
|
||||
init {
|
||||
filters = Filter(router.activity!!)
|
||||
|
@ -253,7 +260,14 @@ class AnimelibSettingsSheet(
|
|||
if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
|
||||
currentCategory?.sortDirection = flag.flag
|
||||
|
||||
db.insertCategory(currentCategory!!).executeAsBlocking()
|
||||
sheetScope.launchIO {
|
||||
updateCategory.await(
|
||||
CategoryUpdate(
|
||||
id = currentCategory!!.id?.toLong()!!,
|
||||
flags = currentCategory!!.flags.toLong(),
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
preferences.librarySortingAscending().set(flag)
|
||||
}
|
||||
|
@ -275,7 +289,14 @@ class AnimelibSettingsSheet(
|
|||
if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
|
||||
currentCategory?.sortMode = flag.flag
|
||||
|
||||
db.insertCategory(currentCategory!!).executeAsBlocking()
|
||||
sheetScope.launchIO {
|
||||
updateCategory.await(
|
||||
CategoryUpdate(
|
||||
id = currentCategory!!.id?.toLong()!!,
|
||||
flags = currentCategory!!.flags.toLong(),
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
preferences.librarySortingMode().set(flag)
|
||||
}
|
||||
|
@ -364,7 +385,14 @@ class AnimelibSettingsSheet(
|
|||
if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
|
||||
currentCategory?.displayMode = flag.flag
|
||||
|
||||
db.insertCategory(currentCategory!!).executeAsBlocking()
|
||||
sheetScope.launchIO {
|
||||
updateCategory.await(
|
||||
CategoryUpdate(
|
||||
id = currentCategory!!.id?.toLong()!!,
|
||||
flags = currentCategory!!.flags.toLong(),
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
preferences.libraryDisplayMode().set(flag)
|
||||
}
|
||||
|
|
|
@ -373,7 +373,7 @@ open class BrowseAnimeSourcePresenter(
|
|||
* @return Array of category ids the anime is in, if none returns default id
|
||||
*/
|
||||
fun getAnimeCategoryIds(anime: Anime): Array<Long?> {
|
||||
val categories = db.getCategoriesForAnime(anime).executeAsBlocking()
|
||||
val categories = db.getCategoriesForAnime(anime.id!!).executeAsBlocking()
|
||||
return categories.mapNotNull { it?.id?.toLong() }.toTypedArray()
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import eu.kanade.domain.anime.model.toDbAnime
|
|||
import eu.kanade.domain.animetrack.interactor.GetAnimeTracks
|
||||
import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack
|
||||
import eu.kanade.domain.category.interactor.GetCategoriesAnime
|
||||
import eu.kanade.domain.category.interactor.MoveAnimeToCategories
|
||||
import eu.kanade.domain.category.interactor.SetAnimeCategories
|
||||
import eu.kanade.domain.episode.interactor.GetEpisodeByAnimeId
|
||||
import eu.kanade.domain.episode.interactor.SyncEpisodesWithSource
|
||||
import eu.kanade.domain.episode.interactor.UpdateEpisode
|
||||
|
@ -48,7 +48,7 @@ class AnimeSearchPresenter(
|
|||
private val getCategories: GetCategoriesAnime = Injekt.get(),
|
||||
private val getTracks: GetAnimeTracks = Injekt.get(),
|
||||
private val insertTrack: InsertAnimeTrack = Injekt.get(),
|
||||
private val moveAnimeToCategories: MoveAnimeToCategories = Injekt.get(),
|
||||
private val setAnimeCategories: SetAnimeCategories = Injekt.get(),
|
||||
) : GlobalAnimeSearchPresenter(initialQuery) {
|
||||
|
||||
private val replacingAnimeRelay = BehaviorRelay.create<Pair<Boolean, Anime?>>()
|
||||
|
@ -164,7 +164,7 @@ class AnimeSearchPresenter(
|
|||
// Update categories
|
||||
if (migrateCategories) {
|
||||
val categoryIds = getCategories.await(prevDomainAnime.id).map { it.id }
|
||||
moveAnimeToCategories.await(domainAnime.id, categoryIds)
|
||||
setAnimeCategories.await(domainAnime.id, categoryIds)
|
||||
}
|
||||
|
||||
// Update track
|
||||
|
|
|
@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
|
|||
import android.os.Bundle
|
||||
import com.jakewharton.rxrelay.BehaviorRelay
|
||||
import eu.kanade.domain.category.interactor.GetCategories
|
||||
import eu.kanade.domain.category.interactor.MoveMangaToCategories
|
||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
||||
|
@ -48,7 +48,7 @@ class SearchPresenter(
|
|||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val getTracks: GetTracks = Injekt.get(),
|
||||
private val insertTrack: InsertTrack = Injekt.get(),
|
||||
private val moveMangaToCategories: MoveMangaToCategories = Injekt.get(),
|
||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||
) : GlobalSearchPresenter(initialQuery) {
|
||||
|
||||
private val replacingMangaRelay = BehaviorRelay.create<Pair<Boolean, Manga?>>()
|
||||
|
@ -164,7 +164,7 @@ class SearchPresenter(
|
|||
// Update categories
|
||||
if (migrateCategories) {
|
||||
val categoryIds = getCategories.await(prevDomainManga.id).map { it.id }
|
||||
moveMangaToCategories.await(domainManga.id, categoryIds)
|
||||
setMangaCategories.await(domainManga.id, categoryIds)
|
||||
}
|
||||
|
||||
// Update track
|
||||
|
|
|
@ -373,7 +373,7 @@ open class BrowseSourcePresenter(
|
|||
* @return Array of category ids the manga is in, if none returns default id
|
||||
*/
|
||||
fun getMangaCategoryIds(manga: Manga): Array<Long?> {
|
||||
val categories = db.getCategoriesForManga(manga).executeAsBlocking()
|
||||
val categories = db.getCategoriesForManga(manga.id!!).executeAsBlocking()
|
||||
return categories.mapNotNull { it?.id?.toLong() }.toTypedArray()
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.preference.asImmediateFlow
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
|
@ -36,6 +38,7 @@ import eu.kanade.tachiyomi.util.system.toast
|
|||
import eu.kanade.tachiyomi.widget.ActionModeWithToolbar
|
||||
import eu.kanade.tachiyomi.widget.EmptyView
|
||||
import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -226,6 +229,7 @@ class LibraryController(
|
|||
destroyActionModeIfNeeded()
|
||||
adapter?.onDestroy()
|
||||
adapter = null
|
||||
settingsSheet?.sheetScope?.cancel()
|
||||
settingsSheet = null
|
||||
tabsVisibilitySubscription?.unsubscribe()
|
||||
tabsVisibilitySubscription = null
|
||||
|
@ -541,25 +545,29 @@ class LibraryController(
|
|||
* Move the selected manga to a list of categories.
|
||||
*/
|
||||
private fun showMangaCategoriesDialog() {
|
||||
// Create a copy of selected manga
|
||||
val mangas = selectedMangas.toList()
|
||||
viewScope.launchIO {
|
||||
// Create a copy of selected manga
|
||||
val mangas = selectedMangas.toList()
|
||||
|
||||
// Hide the default category because it has a different behavior than the ones from db.
|
||||
val categories = presenter.categories.filter { it.id != 0 }
|
||||
// Hide the default category because it has a different behavior than the ones from db.
|
||||
val categories = presenter.categories.filter { it.id != 0 }
|
||||
|
||||
// Get indexes of the common categories to preselect.
|
||||
val common = presenter.getCommonCategories(mangas)
|
||||
// Get indexes of the mix categories to preselect.
|
||||
val mix = presenter.getMixCategories(mangas)
|
||||
val preselected = categories.map {
|
||||
when (it) {
|
||||
in common -> QuadStateTextView.State.CHECKED.ordinal
|
||||
in mix -> QuadStateTextView.State.INDETERMINATE.ordinal
|
||||
else -> QuadStateTextView.State.UNCHECKED.ordinal
|
||||
// Get indexes of the common categories to preselect.
|
||||
val common = presenter.getCommonCategories(mangas)
|
||||
// Get indexes of the mix categories to preselect.
|
||||
val mix = presenter.getMixCategories(mangas)
|
||||
val preselected = categories.map {
|
||||
when (it) {
|
||||
in common -> QuadStateTextView.State.CHECKED.ordinal
|
||||
in mix -> QuadStateTextView.State.INDETERMINATE.ordinal
|
||||
else -> QuadStateTextView.State.UNCHECKED.ordinal
|
||||
}
|
||||
}.toIntArray()
|
||||
launchUI {
|
||||
ChangeMangaCategoriesDialog(this@LibraryController, mangas, categories, preselected)
|
||||
.showDialog(router)
|
||||
}
|
||||
}.toIntArray()
|
||||
ChangeMangaCategoriesDialog(this, mangas, categories, preselected)
|
||||
.showDialog(router)
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadUnreadChapters() {
|
||||
|
@ -579,7 +587,7 @@ class LibraryController(
|
|||
}
|
||||
|
||||
override fun updateCategoriesForMangas(mangas: List<Manga>, addCategories: List<Category>, removeCategories: List<Category>) {
|
||||
presenter.updateMangasToCategories(mangas, addCategories, removeCategories)
|
||||
presenter.setMangaCategories(mangas, addCategories, removeCategories)
|
||||
destroyActionModeIfNeeded()
|
||||
}
|
||||
|
||||
|
|
|
@ -2,12 +2,23 @@ package eu.kanade.tachiyomi.ui.library
|
|||
|
||||
import android.os.Bundle
|
||||
import com.jakewharton.rxrelay.BehaviorRelay
|
||||
import eu.kanade.core.util.asObservable
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.domain.category.interactor.GetCategories
|
||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
||||
import eu.kanade.domain.category.model.toDbCategory
|
||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
||||
import eu.kanade.domain.chapter.model.ChapterUpdate
|
||||
import eu.kanade.domain.chapter.model.toDbChapter
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.MangaUpdate
|
||||
import eu.kanade.domain.track.interactor.GetTracks
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
|
@ -23,6 +34,8 @@ import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
|
|||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
|
@ -47,7 +60,13 @@ private typealias LibraryMap = Map<Int, List<LibraryItem>>
|
|||
* Presenter of [LibraryController].
|
||||
*/
|
||||
class LibraryPresenter(
|
||||
private val db: DatabaseHelper = Injekt.get(),
|
||||
private val handler: DatabaseHandler = Injekt.get(),
|
||||
private val getTracks: GetTracks = Injekt.get(),
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||
private val preferences: PreferencesHelper = Injekt.get(),
|
||||
private val coverCache: CoverCache = Injekt.get(),
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
|
@ -92,6 +111,7 @@ class LibraryPresenter(
|
|||
* Subscribes to library if needed.
|
||||
*/
|
||||
fun subscribeLibrary() {
|
||||
// TODO: Move this to a coroutine world
|
||||
if (librarySubscription.isNullOrUnsubscribed()) {
|
||||
librarySubscription = getLibraryObservable()
|
||||
.combineLatest(badgeTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
|
||||
|
@ -115,7 +135,7 @@ class LibraryPresenter(
|
|||
*
|
||||
* @param map the map to filter.
|
||||
*/
|
||||
private fun applyFilters(map: LibraryMap, trackMap: Map<Long, Map<Int, Boolean>>): LibraryMap {
|
||||
private fun applyFilters(map: LibraryMap, trackMap: Map<Long, Map<Long, Boolean>>): LibraryMap {
|
||||
val downloadedOnly = preferences.downloadedOnly().get()
|
||||
val filterDownloaded = preferences.filterDownloaded().get()
|
||||
val filterUnread = preferences.filterUnread().get()
|
||||
|
@ -252,18 +272,30 @@ class LibraryPresenter(
|
|||
private fun applySort(categories: List<Category>, map: LibraryMap): LibraryMap {
|
||||
val lastReadManga by lazy {
|
||||
var counter = 0
|
||||
// Result comes as newest to oldest so it's reversed
|
||||
db.getLastReadManga().executeAsBlocking().reversed().associate { it.id!! to counter++ }
|
||||
// TODO: Make [applySort] a suspended function
|
||||
runBlocking {
|
||||
handler.awaitList {
|
||||
mangasQueries.getLastRead()
|
||||
}.associate { it._id to counter++ }
|
||||
}
|
||||
}
|
||||
val latestChapterManga by lazy {
|
||||
var counter = 0
|
||||
// Result comes as newest to oldest so it's reversed
|
||||
db.getLatestChapterManga().executeAsBlocking().reversed().associate { it.id!! to counter++ }
|
||||
// TODO: Make [applySort] a suspended function
|
||||
runBlocking {
|
||||
handler.awaitList {
|
||||
mangasQueries.getLatestByChapterUploadDate()
|
||||
}.associate { it._id to counter++ }
|
||||
}
|
||||
}
|
||||
val chapterFetchDateManga by lazy {
|
||||
var counter = 0
|
||||
// Result comes as newest to oldest so it's reversed
|
||||
db.getChapterFetchDateManga().executeAsBlocking().reversed().associate { it.id!! to counter++ }
|
||||
// TODO: Make [applySort] a suspended function
|
||||
runBlocking {
|
||||
handler.awaitList {
|
||||
mangasQueries.getLatestByChapterFetchDate()
|
||||
}.associate { it._id to counter++ }
|
||||
}
|
||||
}
|
||||
|
||||
val sortingModes = categories.associate { category ->
|
||||
|
@ -366,7 +398,7 @@ class LibraryPresenter(
|
|||
* @return an observable of the categories.
|
||||
*/
|
||||
private fun getCategoriesObservable(): Observable<List<Category>> {
|
||||
return db.getCategories().asRxObservable()
|
||||
return getCategories.subscribe().map { it.map { it.toDbCategory() } }.asObservable()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -378,7 +410,36 @@ class LibraryPresenter(
|
|||
private fun getLibraryMangasObservable(): Observable<LibraryMap> {
|
||||
val defaultLibraryDisplayMode = preferences.libraryDisplayMode()
|
||||
val shouldSetFromCategory = preferences.categorizedDisplaySettings()
|
||||
return db.getLibraryMangas().asRxObservable()
|
||||
|
||||
// TODO: Move this to domain/data layer
|
||||
return handler
|
||||
.subscribeToList {
|
||||
mangasQueries.getLibrary { _id: Long, source: Long, url: String, artist: String?, author: String?, description: String?, genre: List<String>?, title: String, status: Long, thumbnail_url: String?, favorite: Boolean, last_update: Long?, next_update: Long?, initialized: Boolean, viewer: Long, chapter_flags: Long, cover_last_modified: Long, date_added: Long, unread_count: Long, read_count: Long, category: Long ->
|
||||
LibraryManga().apply {
|
||||
this.id = _id
|
||||
this.source = source
|
||||
this.url = url
|
||||
this.artist = artist
|
||||
this.author = author
|
||||
this.description = description
|
||||
this.genre = genre?.joinToString()
|
||||
this.title = title
|
||||
this.status = status.toInt()
|
||||
this.thumbnail_url = thumbnail_url
|
||||
this.favorite = favorite
|
||||
this.last_update = last_update ?: 0
|
||||
this.initialized = initialized
|
||||
this.viewer_flags = viewer.toInt()
|
||||
this.chapter_flags = chapter_flags.toInt()
|
||||
this.cover_last_modified = cover_last_modified
|
||||
this.date_added = date_added
|
||||
this.unreadCount = unread_count.toInt()
|
||||
this.readCount = read_count.toInt()
|
||||
this.category = category.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
.asObservable()
|
||||
.map { list ->
|
||||
list.map { libraryManga ->
|
||||
// Display mode based on user preference: take it from global library setting or category
|
||||
|
@ -396,7 +457,7 @@ class LibraryPresenter(
|
|||
*
|
||||
* @return an observable of tracked manga.
|
||||
*/
|
||||
private fun getFilterObservable(): Observable<Map<Long, Map<Int, Boolean>>> {
|
||||
private fun getFilterObservable(): Observable<Map<Long, Map<Long, Boolean>>> {
|
||||
return getTracksObservable().combineLatest(filterTriggerRelay.observeOn(Schedulers.io())) { tracks, _ -> tracks }
|
||||
}
|
||||
|
||||
|
@ -405,16 +466,20 @@ class LibraryPresenter(
|
|||
*
|
||||
* @return an observable of tracked manga.
|
||||
*/
|
||||
private fun getTracksObservable(): Observable<Map<Long, Map<Int, Boolean>>> {
|
||||
return db.getTracks().asRxObservable().map { tracks ->
|
||||
tracks.groupBy { it.manga_id }
|
||||
.mapValues { tracksForMangaId ->
|
||||
// Check if any of the trackers is logged in for the current manga id
|
||||
tracksForMangaId.value.associate {
|
||||
Pair(it.sync_id, trackManager.getService(it.sync_id.toLong())?.isLogged ?: false)
|
||||
private fun getTracksObservable(): Observable<Map<Long, Map<Long, Boolean>>> {
|
||||
// TODO: Move this to domain/data layer
|
||||
return getTracks.subscribe()
|
||||
.asObservable().map { tracks ->
|
||||
tracks
|
||||
.groupBy { it.mangaId }
|
||||
.mapValues { tracksForMangaId ->
|
||||
// Check if any of the trackers is logged in for the current manga id
|
||||
tracksForMangaId.value.associate {
|
||||
Pair(it.syncId, trackManager.getService(it.syncId)?.isLogged ?: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.observeOn(Schedulers.io())
|
||||
}
|
||||
.observeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -451,11 +516,11 @@ class LibraryPresenter(
|
|||
*
|
||||
* @param mangas the list of manga.
|
||||
*/
|
||||
fun getCommonCategories(mangas: List<Manga>): Collection<Category> {
|
||||
suspend fun getCommonCategories(mangas: List<Manga>): Collection<Category> {
|
||||
if (mangas.isEmpty()) return emptyList()
|
||||
return mangas.toSet()
|
||||
.map { db.getCategoriesForManga(it).executeAsBlocking() }
|
||||
.reduce { set1: Iterable<Category>, set2 -> set1.intersect(set2).toMutableList() }
|
||||
.map { getCategories.await(it.id!!).map { it.toDbCategory() } }
|
||||
.reduce { set1, set2 -> set1.intersect(set2).toMutableList() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -463,9 +528,9 @@ class LibraryPresenter(
|
|||
*
|
||||
* @param mangas the list of manga.
|
||||
*/
|
||||
fun getMixCategories(mangas: List<Manga>): Collection<Category> {
|
||||
suspend fun getMixCategories(mangas: List<Manga>): Collection<Category> {
|
||||
if (mangas.isEmpty()) return emptyList()
|
||||
val mangaCategories = mangas.toSet().map { db.getCategoriesForManga(it).executeAsBlocking() }
|
||||
val mangaCategories = mangas.toSet().map { getCategories.await(it.id!!).map { it.toDbCategory() } }
|
||||
val common = mangaCategories.reduce { set1, set2 -> set1.intersect(set2).toMutableList() }
|
||||
return mangaCategories.flatten().distinct().subtract(common).toMutableList()
|
||||
}
|
||||
|
@ -478,8 +543,9 @@ class LibraryPresenter(
|
|||
fun downloadUnreadChapters(mangas: List<Manga>) {
|
||||
mangas.forEach { manga ->
|
||||
launchIO {
|
||||
val chapters = db.getChapters(manga).executeAsBlocking()
|
||||
val chapters = getChapterByMangaId.await(manga.id!!)
|
||||
.filter { !it.read }
|
||||
.map { it.toDbChapter() }
|
||||
|
||||
downloadManager.downloadChapters(manga, chapters)
|
||||
}
|
||||
|
@ -494,17 +560,20 @@ class LibraryPresenter(
|
|||
fun markReadStatus(mangas: List<Manga>, read: Boolean) {
|
||||
mangas.forEach { manga ->
|
||||
launchIO {
|
||||
val chapters = db.getChapters(manga).executeAsBlocking()
|
||||
chapters.forEach {
|
||||
it.read = read
|
||||
if (!read) {
|
||||
it.last_page_read = 0
|
||||
val chapters = getChapterByMangaId.await(manga.id!!)
|
||||
|
||||
val toUpdate = chapters
|
||||
.map { chapter ->
|
||||
ChapterUpdate(
|
||||
read = read,
|
||||
lastPageRead = if (read) 0 else null,
|
||||
id = chapter.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
db.updateChaptersProgress(chapters).executeAsBlocking()
|
||||
updateChapter.awaitAll(toUpdate)
|
||||
|
||||
if (read && preferences.removeAfterMarkedAsRead()) {
|
||||
deleteChapters(manga, chapters)
|
||||
deleteChapters(manga, chapters.map { it.toDbChapter() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -519,20 +588,23 @@ class LibraryPresenter(
|
|||
/**
|
||||
* Remove the selected manga.
|
||||
*
|
||||
* @param mangas the list of manga to delete.
|
||||
* @param mangaList the list of manga to delete.
|
||||
* @param deleteFromLibrary whether to delete manga from library.
|
||||
* @param deleteChapters whether to delete downloaded chapters.
|
||||
*/
|
||||
fun removeMangas(mangas: List<Manga>, deleteFromLibrary: Boolean, deleteChapters: Boolean) {
|
||||
fun removeMangas(mangaList: List<Manga>, deleteFromLibrary: Boolean, deleteChapters: Boolean) {
|
||||
launchIO {
|
||||
val mangaToDelete = mangas.distinctBy { it.id }
|
||||
val mangaToDelete = mangaList.distinctBy { it.id }
|
||||
|
||||
if (deleteFromLibrary) {
|
||||
mangaToDelete.forEach {
|
||||
it.favorite = false
|
||||
val toDelete = mangaToDelete.map {
|
||||
it.removeCovers(coverCache)
|
||||
MangaUpdate(
|
||||
favorite = false,
|
||||
id = it.id!!,
|
||||
)
|
||||
}
|
||||
db.insertMangas(mangaToDelete).executeAsBlocking()
|
||||
updateManga.awaitAll(toDelete)
|
||||
}
|
||||
|
||||
if (deleteChapters) {
|
||||
|
@ -547,35 +619,22 @@ class LibraryPresenter(
|
|||
}
|
||||
|
||||
/**
|
||||
* Move the given list of manga to categories.
|
||||
* Bulk update categories of manga using old and new common categories.
|
||||
*
|
||||
* @param categories the selected categories.
|
||||
* @param mangas the list of manga to move.
|
||||
*/
|
||||
fun moveMangasToCategories(categories: List<Category>, mangas: List<Manga>) {
|
||||
val mc = mutableListOf<MangaCategory>()
|
||||
|
||||
for (manga in mangas) {
|
||||
categories.mapTo(mc) { MangaCategory.create(manga, it) }
|
||||
}
|
||||
|
||||
db.setMangaCategories(mc, mangas)
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk update categories of mangas using old and new common categories.
|
||||
*
|
||||
* @param mangas the list of manga to move.
|
||||
* @param mangaList the list of manga to move.
|
||||
* @param addCategories the categories to add for all mangas.
|
||||
* @param removeCategories the categories to remove in all mangas.
|
||||
*/
|
||||
fun updateMangasToCategories(mangas: List<Manga>, addCategories: List<Category>, removeCategories: List<Category>) {
|
||||
val mangaCategories = mangas.map { manga ->
|
||||
val categories = db.getCategoriesForManga(manga).executeAsBlocking()
|
||||
.subtract(removeCategories).plus(addCategories).distinct()
|
||||
categories.map { MangaCategory.create(manga, it) }
|
||||
}.flatten()
|
||||
|
||||
db.setMangaCategories(mangaCategories, mangas)
|
||||
fun setMangaCategories(mangaList: List<Manga>, addCategories: List<Category>, removeCategories: List<Category>) {
|
||||
presenterScope.launchIO {
|
||||
mangaList.map { manga ->
|
||||
val categoryIds = getCategories.await(manga.id!!)
|
||||
.map { it.toDbCategory() }
|
||||
.subtract(removeCategories)
|
||||
.plus(addCategories)
|
||||
.mapNotNull { it.id?.toLong() }
|
||||
setMangaCategories.await(manga.id!!, categoryIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,9 @@ import android.content.Context
|
|||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import eu.kanade.domain.category.interactor.UpdateCategory
|
||||
import eu.kanade.domain.category.model.CategoryUpdate
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.track.AnimeTrackService
|
||||
|
@ -14,9 +15,13 @@ import eu.kanade.tachiyomi.data.track.TrackService
|
|||
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
|
||||
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
|
||||
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
|
||||
import eu.kanade.tachiyomi.widget.sheet.TabbedBottomSheetDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
@ -24,13 +29,15 @@ import uy.kohesive.injekt.injectLazy
|
|||
class LibrarySettingsSheet(
|
||||
router: Router,
|
||||
private val trackManager: TrackManager = Injekt.get(),
|
||||
private val updateCategory: UpdateCategory = Injekt.get(),
|
||||
onGroupClickListener: (ExtendedNavigationView.Group) -> Unit,
|
||||
) : TabbedBottomSheetDialog(router.activity!!) {
|
||||
|
||||
val filters: Filter
|
||||
private val sort: Sort
|
||||
private val display: Display
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
|
||||
val sheetScope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
|
||||
init {
|
||||
filters = Filter(router.activity!!)
|
||||
|
@ -252,8 +259,14 @@ class LibrarySettingsSheet(
|
|||
|
||||
if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
|
||||
currentCategory?.sortDirection = flag.flag
|
||||
|
||||
db.insertCategory(currentCategory!!).executeAsBlocking()
|
||||
sheetScope.launchIO {
|
||||
updateCategory.await(
|
||||
CategoryUpdate(
|
||||
id = currentCategory!!.id?.toLong()!!,
|
||||
flags = currentCategory!!.flags.toLong(),
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
preferences.librarySortingAscending().set(flag)
|
||||
}
|
||||
|
@ -274,8 +287,14 @@ class LibrarySettingsSheet(
|
|||
|
||||
if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
|
||||
currentCategory?.sortMode = flag.flag
|
||||
|
||||
db.insertCategory(currentCategory!!).executeAsBlocking()
|
||||
sheetScope.launchIO {
|
||||
updateCategory.await(
|
||||
CategoryUpdate(
|
||||
id = currentCategory!!.id?.toLong()!!,
|
||||
flags = currentCategory!!.flags.toLong(),
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
preferences.librarySortingMode().set(flag)
|
||||
}
|
||||
|
@ -363,8 +382,14 @@ class LibrarySettingsSheet(
|
|||
|
||||
if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
|
||||
currentCategory?.displayMode = flag.flag
|
||||
|
||||
db.insertCategory(currentCategory!!).executeAsBlocking()
|
||||
sheetScope.launchIO {
|
||||
updateCategory.await(
|
||||
CategoryUpdate(
|
||||
id = currentCategory!!.id?.toLong()!!,
|
||||
flags = currentCategory!!.flags.toLong(),
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
preferences.libraryDisplayMode().set(flag)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.manga
|
|||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Immutable
|
||||
import eu.kanade.domain.category.interactor.GetCategories
|
||||
import eu.kanade.domain.category.interactor.MoveMangaToCategories
|
||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
|
||||
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
||||
|
@ -91,7 +91,7 @@ class MangaPresenter(
|
|||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val deleteTrack: DeleteTrack = Injekt.get(),
|
||||
private val getTracks: GetTracks = Injekt.get(),
|
||||
private val moveMangaToCategories: MoveMangaToCategories = Injekt.get(),
|
||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||
private val insertTrack: InsertTrack = Injekt.get(),
|
||||
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
|
||||
) : BasePresenter<MangaController>() {
|
||||
|
@ -359,7 +359,7 @@ class MangaPresenter(
|
|||
val mangaId = manga.id ?: return
|
||||
val categoryIds = categories.mapNotNull { it.id?.toLong() }
|
||||
presenterScope.launchIO {
|
||||
moveMangaToCategories.await(mangaId, categoryIds)
|
||||
setMangaCategories.await(mangaId, categoryIds)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ class PlayerPresenter(
|
|||
|
||||
private fun initEpisodeList(): List<Episode> {
|
||||
val anime = anime!!
|
||||
val dbEpisodes = db.getEpisodes(anime).executeAsBlocking()
|
||||
val dbEpisodes = db.getEpisodes(anime.id!!).executeAsBlocking()
|
||||
|
||||
val selectedEpisode = dbEpisodes.find { it.id == episodeId }
|
||||
?: error("Requested episode of id $episodeId not found in episode list")
|
||||
|
@ -338,7 +338,7 @@ class PlayerPresenter(
|
|||
else -> throw NotImplementedError("Unknown sorting method")
|
||||
}
|
||||
|
||||
val episodes = db.getEpisodes(anime).executeAsBlocking()
|
||||
val episodes = db.getEpisodes(anime.id!!).executeAsBlocking()
|
||||
.sortedWith { e1, e2 -> sortFunction(e1, e2) }
|
||||
|
||||
val currentEpisodePosition = episodes.indexOf(episode)
|
||||
|
|
|
@ -118,7 +118,7 @@ class ReaderPresenter(
|
|||
*/
|
||||
private val chapterList by lazy {
|
||||
val manga = manga!!
|
||||
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||
val dbChapters = db.getChapters(manga.id!!).executeAsBlocking()
|
||||
|
||||
val selectedChapter = dbChapters.find { it.id == chapterId }
|
||||
?: error("Requested chapter of id $chapterId not found in chapter list")
|
||||
|
|
|
@ -74,7 +74,7 @@ fun DomainAnime.shouldDownloadNewEpisodes(db: AnimeDatabaseHelper, prefs: Prefer
|
|||
|
||||
// Get all categories, else default category (0)
|
||||
val categoriesForAnime =
|
||||
db.getCategoriesForAnime(toDbAnime()).executeAsBlocking()
|
||||
db.getCategoriesForAnime(id).executeAsBlocking()
|
||||
.mapNotNull { it.id }
|
||||
.takeUnless { it.isEmpty() } ?: listOf(0)
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ fun DomainManga.shouldDownloadNewChapters(db: DatabaseHelper, prefs: Preferences
|
|||
|
||||
// Get all categories, else default category (0)
|
||||
val categoriesForManga =
|
||||
db.getCategoriesForManga(toDbManga()).executeAsBlocking()
|
||||
db.getCategoriesForManga(id).executeAsBlocking()
|
||||
.mapNotNull { it.id }
|
||||
.takeUnless { it.isEmpty() } ?: listOf(0)
|
||||
|
||||
|
|
|
@ -185,7 +185,7 @@ object ImageUtil {
|
|||
* @return true if the height:width ratio is greater than 3.
|
||||
*/
|
||||
private fun isTallImage(imageStream: InputStream): Boolean {
|
||||
val options = extractImageOptions(imageStream, false)
|
||||
val options = extractImageOptions(imageStream, resetAfterExtraction = false)
|
||||
return (options.outHeight / options.outWidth) > 3
|
||||
}
|
||||
|
||||
|
@ -197,16 +197,34 @@ object ImageUtil {
|
|||
return true
|
||||
}
|
||||
|
||||
val options = extractImageOptions(imageFile.openInputStream(), false).apply { inJustDecodeBounds = false }
|
||||
val options = extractImageOptions(imageFile.openInputStream(), resetAfterExtraction = false).apply { inJustDecodeBounds = false }
|
||||
// Values are stored as they get modified during split loop
|
||||
val imageHeight = options.outHeight
|
||||
val imageWidth = options.outWidth
|
||||
|
||||
val splitHeight = getDisplayMaxHeightInPx
|
||||
val splitHeight = (getDisplayMaxHeightInPx * 1.5).toInt()
|
||||
// -1 so it doesn't try to split when imageHeight = getDisplayHeightInPx
|
||||
val partCount = (imageHeight - 1) / getDisplayMaxHeightInPx + 1
|
||||
val partCount = (imageHeight - 1) / splitHeight + 1
|
||||
|
||||
logcat { "Splitting ${imageHeight}px height image into $partCount part with estimated ${splitHeight}px per height" }
|
||||
val optimalSplitHeight = imageHeight / partCount
|
||||
|
||||
val splitDataList = (0 until partCount).fold(mutableListOf<SplitData>()) { list, index ->
|
||||
list.apply {
|
||||
// Only continue if the list is empty or there is image remaining
|
||||
if (isEmpty() || imageHeight > last().bottomOffset) {
|
||||
val topOffset = index * optimalSplitHeight
|
||||
var outputImageHeight = min(optimalSplitHeight, imageHeight - topOffset)
|
||||
|
||||
val remainingHeight = imageHeight - (topOffset + outputImageHeight)
|
||||
// If remaining height is smaller or equal to 1/3th of
|
||||
// optimal split height then include it in current page
|
||||
if (remainingHeight <= (optimalSplitHeight / 3)) {
|
||||
outputImageHeight += remainingHeight
|
||||
}
|
||||
add(SplitData(index, topOffset, outputImageHeight))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val bitmapRegionDecoder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
BitmapRegionDecoder.newInstance(imageFile.openInputStream())
|
||||
|
@ -220,36 +238,52 @@ object ImageUtil {
|
|||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
(0 until partCount).forEach { splitIndex ->
|
||||
val splitPath = imageFilePath.substringBeforeLast(".") + "__${"%03d".format(splitIndex + 1)}.jpg"
|
||||
logcat {
|
||||
"Splitting image with height of $imageHeight into $partCount part " +
|
||||
"with estimated ${optimalSplitHeight}px height per split"
|
||||
}
|
||||
|
||||
val topOffset = splitIndex * splitHeight
|
||||
val outputImageHeight = min(splitHeight, imageHeight - topOffset)
|
||||
val bottomOffset = topOffset + outputImageHeight
|
||||
logcat { "Split #$splitIndex with topOffset=$topOffset height=$outputImageHeight bottomOffset=$bottomOffset" }
|
||||
return try {
|
||||
splitDataList.forEach { splitData ->
|
||||
val splitPath = splitImagePath(imageFilePath, splitData.index)
|
||||
|
||||
val region = Rect(0, topOffset, imageWidth, bottomOffset)
|
||||
val region = Rect(0, splitData.topOffset, imageWidth, splitData.bottomOffset)
|
||||
|
||||
FileOutputStream(splitPath).use { outputStream ->
|
||||
val splitBitmap = bitmapRegionDecoder.decodeRegion(region, options)
|
||||
splitBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
|
||||
splitBitmap.recycle()
|
||||
}
|
||||
logcat {
|
||||
"Success: Split #${splitData.index + 1} with topOffset=${splitData.topOffset} " +
|
||||
"height=${splitData.outputImageHeight} bottomOffset=${splitData.bottomOffset}"
|
||||
}
|
||||
}
|
||||
imageFile.delete()
|
||||
return true
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
// Image splits were not successfully saved so delete them and keep the original image
|
||||
(0 until partCount)
|
||||
.map { imageFilePath.substringBeforeLast(".") + "__${"%03d".format(it + 1)}.jpg" }
|
||||
splitDataList
|
||||
.map { splitImagePath(imageFilePath, it.index) }
|
||||
.forEach { File(it).delete() }
|
||||
logcat(LogPriority.ERROR, e)
|
||||
return false
|
||||
false
|
||||
} finally {
|
||||
bitmapRegionDecoder.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
private fun splitImagePath(imageFilePath: String, index: Int) =
|
||||
imageFilePath.substringBeforeLast(".") + "__${"%03d".format(index + 1)}.jpg"
|
||||
|
||||
data class SplitData(
|
||||
val index: Int,
|
||||
val topOffset: Int,
|
||||
val outputImageHeight: Int,
|
||||
) {
|
||||
val bottomOffset = topOffset + outputImageHeight
|
||||
}
|
||||
|
||||
/**
|
||||
* Algorithm for determining what background to accompany a comic/manga page
|
||||
*/
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<string name="label_library">Bibliotēka</string>
|
||||
<string name="label_recent_manga">Vēsture</string>
|
||||
<string name="label_recent_updates">Atjauninājumi</string>
|
||||
<string name="label_backup">Rezerves kopija</string>
|
||||
<string name="label_backup">Dublēšana un atjaunošana</string>
|
||||
<string name="action_settings">Iestatījumi</string>
|
||||
<string name="action_filter">Filtrs</string>
|
||||
<string name="action_filter_downloaded">Lejupieladēts</string>
|
||||
|
@ -76,12 +76,12 @@
|
|||
<string name="download_notifier_downloader_title">Lejupielādētājs</string>
|
||||
<string name="information_webview_outdated">Lai uzlabotu saderību, lūdzu, atjauniniet WebView lietotni</string>
|
||||
<string name="information_webview_required">Tachiyomi ir nepieciešams WebView</string>
|
||||
<string name="restoring_backup_error">Dublējuma atjaunināšana neizdevās</string>
|
||||
<string name="restoring_backup">Atjaunina dublējumu</string>
|
||||
<string name="restore_in_progress">Atjaunināšana jau notiek</string>
|
||||
<string name="restoring_backup_error">Dublējuma atjaunošana neizdevās</string>
|
||||
<string name="restoring_backup">Dublējuma atjaunošana</string>
|
||||
<string name="restore_in_progress">Atjaunošana jau notiek</string>
|
||||
<string name="creating_backup_error">Dublēšana neizdevās</string>
|
||||
<string name="creating_backup">Taisa dublējumu</string>
|
||||
<string name="backup_choice">Ko tu gribi dublēt\?</string>
|
||||
<string name="creating_backup">Izveido dublējumu</string>
|
||||
<string name="backup_choice">Ko vēlaties dublēt\?</string>
|
||||
<string name="backup_in_progress">Dublēšana jau notiek</string>
|
||||
<plurals name="restore_completed_message">
|
||||
<item quantity="zero">Pabeigts %1$s ar %2$s kļūdām</item>
|
||||
|
@ -89,9 +89,9 @@
|
|||
<item quantity="other">Pabeigts %1$s ar %2$s kļūdām</item>
|
||||
</plurals>
|
||||
<string name="restore_duration">%02d min, %02d sec</string>
|
||||
<string name="restore_completed">Atjaunināšana pabeigta</string>
|
||||
<string name="backup_restore_missing_sources">Trūkst avotu:</string>
|
||||
<string name="invalid_backup_file_missing_manga">Dublējums nesatur nekādu mangu.</string>
|
||||
<string name="restore_completed">Atjaunošana pabeigta</string>
|
||||
<string name="backup_restore_missing_sources">Trūkstošie avoti:</string>
|
||||
<string name="invalid_backup_file_missing_manga">Dublējumā nav nevienas manga.</string>
|
||||
<string name="label_extension_info">Paplašinājumu informācija</string>
|
||||
<string name="label_extensions">Paplašinājumi</string>
|
||||
<string name="unlock_app">Atslēgt Anijomi</string>
|
||||
|
@ -99,7 +99,7 @@
|
|||
<string name="tapping_inverted_vertical">Vertikāls</string>
|
||||
<string name="tapping_inverted_horizontal">Horizontāls</string>
|
||||
<string name="tapping_inverted_none">Nav</string>
|
||||
<string name="pref_read_with_tapping_inverted">Invertēt spiešanu</string>
|
||||
<string name="pref_read_with_tapping_inverted">Invertētas skāriena zonas</string>
|
||||
<string name="channel_ext_updates">Paplašinājumu atjauninājumi</string>
|
||||
<string name="channel_new_chapters_episodes">Nodaļu atjauninājumi</string>
|
||||
<string name="download_notifier_download_paused_chapters">Lejuplāde pauzēta</string>
|
||||
|
@ -123,7 +123,7 @@
|
|||
<string name="action_menu">Izvēlne</string>
|
||||
<string name="confirm_exit">Vēlreiz nospiediet atpakaļ, lai izietu</string>
|
||||
<string name="information_empty_category">Jums nav nevienas kategorijas. Pieskarieties plus pogu, lai izveidotu vienu savas bibliotēkas organizēšanai.</string>
|
||||
<string name="information_empty_library">Jūsu bibliotēka ir tukša. Pievienojiet sērijas savai bibliotēkai no Pārlūka.</string>
|
||||
<string name="information_empty_library">Jūsu bibliotēka ir tukša</string>
|
||||
<string name="information_no_recent_manga">Nekas nesen lasīts</string>
|
||||
<string name="information_no_recent">Nekas nesen lasīts</string>
|
||||
<string name="action_newest">Jaunākais</string>
|
||||
|
@ -168,8 +168,8 @@
|
|||
<string name="action_move_to_bottom">Pārvietot uz leju</string>
|
||||
<string name="action_move_to_top">Pārvietot uz augšu</string>
|
||||
<string name="action_oldest">Vecākais</string>
|
||||
<string name="action_display_download_badge">Lejuplādes žetoni</string>
|
||||
<string name="action_display_unread_badge">Nelasītas nozīmes</string>
|
||||
<string name="action_display_download_badge">Lejuplādēt nodaļas</string>
|
||||
<string name="action_display_unread_badge">Nelasītās nodaļas</string>
|
||||
<string name="action_desc">Dilstoši</string>
|
||||
<string name="action_asc">Augoši</string>
|
||||
<string name="action_order_by_chapter_number">Pēc nodaļas numura</string>
|
||||
|
@ -186,12 +186,12 @@
|
|||
<string name="title">Nosaukums</string>
|
||||
<string name="pref_category_delete_chapters">Izdzēst nodaļas</string>
|
||||
<string name="pref_download_directory">Lejuplādes vieta</string>
|
||||
<string name="pref_category_auto_download">Automātiska lejuplāde</string>
|
||||
<string name="pref_category_auto_download">Automātiska lejupielāde</string>
|
||||
<string name="pref_restore_backup">Atjaunot dublējumu</string>
|
||||
<string name="backup_restore_missing_trackers">Nav pieslēgts pie izsekotājiems:</string>
|
||||
<string name="backup_restore_content_full">Dati no dublējuma faila būs atjaunoti.
|
||||
<string name="backup_restore_missing_trackers">Sekotāji, kas nav pieteikušies:</string>
|
||||
<string name="backup_restore_content_full">Dublējuma faila dati tiks atjaunoti.
|
||||
\n
|
||||
\nJums būs nepieciešams instalēt jebkādus trūkstošos paplašinājumus un pēc tam pieslēgties izsekošanas pakalpojumiem.</string>
|
||||
\nJums būs jāinstalē visi trūkstošie paplašinājumi un jāpiesakās izsekošanas pakalpojumos, lai tos izmantotu.</string>
|
||||
<string name="unread">Neizlasītie</string>
|
||||
<string name="all">Visi</string>
|
||||
<string name="ext_update">Atjauninājums</string>
|
||||
|
@ -330,4 +330,170 @@
|
|||
<string name="update_48hour">Ik pēc 2 dienam</string>
|
||||
<string name="update_72hour">Ik pēc 3 dienām</string>
|
||||
<string name="connected_to_wifi">Tikai uz Wi-Fi</string>
|
||||
<string name="automatic_background">Automātisks</string>
|
||||
<string name="pref_keep_screen_on">Turēt ekrānu ieslēgtu</string>
|
||||
<string name="zoom_start_center">Centrs</string>
|
||||
<string name="rotation_type">Rotācijas tips</string>
|
||||
<string name="rotation_free">Brīvs</string>
|
||||
<string name="scale_type_original_size">Oriģinālais lielums</string>
|
||||
<string name="zoom_start_automatic">Automātisks</string>
|
||||
<string name="zoom_start_left">Pa kreisi</string>
|
||||
<string name="zoom_start_right">Pa labi</string>
|
||||
<string name="double_tap_anim_speed_normal">Normāls</string>
|
||||
<string name="webtoon_side_padding_5">5%</string>
|
||||
<string name="webtoon_side_padding_10">10%</string>
|
||||
<string name="pref_remove_bookmarked_chapters">Atļaut dzēst ar grāmatzīmēm atzīmētās nodaļas</string>
|
||||
<string name="last_read_chapter">Pēdējā lasītā nodaļa</string>
|
||||
<string name="second_to_last">Pirmspēdējā nodaļa</string>
|
||||
<string name="pref_download_new">Lejupielādē jaunās nodaļas</string>
|
||||
<string name="rotation_reverse_portrait">Pretējs portrets</string>
|
||||
<string name="black_background">Melns</string>
|
||||
<string name="label_default">Noklusējums</string>
|
||||
<string name="pref_reader_actions">Darbība</string>
|
||||
<string name="color_filter_r_value">R</string>
|
||||
<string name="color_filter_g_value">G</string>
|
||||
<string name="color_filter_b_value">B</string>
|
||||
<string name="color_filter_a_value">A</string>
|
||||
<string name="pref_always_show_chapter_transition">Vienmēr rādīt nodaļu pāreju</string>
|
||||
<string name="webtoon_side_padding_20">20%</string>
|
||||
<string name="webtoon_side_padding_25">25%</string>
|
||||
<string name="pref_hide_threshold">Jutīgums, lai ritinājumā paslēptu izvēlni</string>
|
||||
<string name="rotation_force_landscape">Aizslēgts ainavas režīmā</string>
|
||||
<string name="pref_lowest">Zemākais</string>
|
||||
<string name="pref_remove_after_marked_as_read">Pēc manuālas atzīmēšanas kā lasītu</string>
|
||||
<string name="save_chapter_as_cbz">Saglabāt kā CBZ arhīvu</string>
|
||||
<string name="nav_zone_next">Nākamais</string>
|
||||
<string name="l_nav">L formas</string>
|
||||
<string name="webtoon_side_padding_15">15%</string>
|
||||
<string name="gray_background">Pelēks</string>
|
||||
<string name="disabled_nav">Atspējots</string>
|
||||
<string name="white_background">Balts</string>
|
||||
<string name="vertical_plus_viewer">Nepārtraukta vertikāle</string>
|
||||
<string name="webtoon_side_padding_0">Nekāds</string>
|
||||
<string name="pref_webtoon_side_padding">Sānu platums</string>
|
||||
<string name="pref_remove_exclude_categories">Izslēgtās kategorijas</string>
|
||||
<string name="action_display_local_badge">Lokālā manga</string>
|
||||
<string name="pref_create_folder_per_manga">Saglabāt lappuses atsevišķās mapēs</string>
|
||||
<string name="nav_zone_prev">Iepriekšējais</string>
|
||||
<string name="pref_viewer_type">Noklusējuma lasīšanas režīms</string>
|
||||
<string name="kindlish_nav">Kindle-ish</string>
|
||||
<string name="nav_zone_left">Pa kreisi</string>
|
||||
<string name="edge_nav">Mala</string>
|
||||
<string name="right_and_left_nav">Pa labi un pa kreisi</string>
|
||||
<string name="nav_zone_right">Pa labi</string>
|
||||
<string name="left_to_right_viewer">No kreisās puses uz labo</string>
|
||||
<string name="webtoon_viewer">Webtoon</string>
|
||||
<string name="pref_viewer_nav">Skāriena zonas</string>
|
||||
<string name="scale_type_stretch">Izstiept</string>
|
||||
<string name="scale_type_fit_width">Ietilpt platumā</string>
|
||||
<string name="scale_type_fit_height">Ietilpt augstumā</string>
|
||||
<string name="double_tap_anim_speed_0">Nav animācijas</string>
|
||||
<string name="rotation_landscape">Ainava</string>
|
||||
<string name="rotation_portrait">Portrets</string>
|
||||
<string name="rotation_force_portrait">Aizslēgts portreta režīmā</string>
|
||||
<string name="pref_category_reading_mode">Lasīšanas režīms</string>
|
||||
<string name="action_filter_started">Sākts</string>
|
||||
<string name="right_to_left_viewer">No labās puses uz kreiso</string>
|
||||
<string name="vertical_viewer">Vertikāls</string>
|
||||
<string name="manga">Manga</string>
|
||||
<string name="double_tap_anim_speed_fast">Ātrs</string>
|
||||
<string name="pref_rotation_type">Noklusējuma rotācijas tips</string>
|
||||
<string name="pref_high">Augsts</string>
|
||||
<string name="pref_highest">Augstākais</string>
|
||||
<string name="label_warning">Brīdinājums</string>
|
||||
<string name="pref_skip_read_chapters">Izlaist izlasītās nodaļas</string>
|
||||
<string name="custom_dir">Pielāgota lokācija</string>
|
||||
<string name="pref_inverted_colors">Invertēts</string>
|
||||
<string name="filter_mode_multiply">Reizināt</string>
|
||||
<string name="filter_mode_screen">Ekrāns</string>
|
||||
<string name="pref_skip_filtered_chapters">Izlaist filtrētās nodaļas</string>
|
||||
<string name="pref_reader_navigation">Navigācija</string>
|
||||
<string name="pref_read_with_volume_keys_inverted">Invertēt skaļuma regulēšanas taustiņus</string>
|
||||
<string name="pref_read_with_long_tap">Rādīt ar ilgu pieskārienu</string>
|
||||
<string name="pref_create_folder_per_manga_summary">Izveido mapes atbilstoši manga nosaukumam</string>
|
||||
<string name="pref_reader_theme">Fona krāsa</string>
|
||||
<string name="pref_image_scale_type">Mēroga tips</string>
|
||||
<string name="scale_type_fit_screen">Ietilpt ekrānā</string>
|
||||
<string name="scale_type_smart_fit">Viedā ietilpšana</string>
|
||||
<string name="pref_zoom_start">Tālummaiņas sākuma pozīcija</string>
|
||||
<string name="pref_low">Zems</string>
|
||||
<string name="pref_remove_after_read">Pēc lasīšanas automātiski izdzēst nodaļas</string>
|
||||
<string name="disabled">Atspējots</string>
|
||||
<string name="pref_download_new_categories_details">Izslēgto kategoriju manga netiks lejupielādēta pat tad, ja tās ir arī iekļautajās kategorijās.</string>
|
||||
<string name="split_tall_images">Automātiski sadalīt garus attēlus</string>
|
||||
<string name="pref_read_with_volume_keys">Skaļuma regulēšanas taustiņi</string>
|
||||
<string name="privacy_policy">Konfidencialitātes politika</string>
|
||||
<string name="pref_create_backup_summ">Var izmantot, lai atjaunotu pašreizējo bibliotēku</string>
|
||||
<string name="split_tall_images_summary">Uzlabo lasītāja veiktspēju, sadalot garus lejupielādētus attēlus.</string>
|
||||
<string name="enhanced_tracking_info">Pakalpojumi, kas nodrošina uzlabotus līdzekļus konkrētiem avotiem. Pievienojot bibliotēkai, manga tiks automātiski izsekota.</string>
|
||||
<string name="pref_duplicate_pinned_sources">Rādīt dublētus piespraustos avotus</string>
|
||||
<string name="pref_duplicate_pinned_sources_summary">Vēlreiz rādīt piespraustos avotus atbilstošajās valodu grupās</string>
|
||||
<string name="pref_create_backup">Izveidot dublējumu</string>
|
||||
<string name="pref_backup_directory">Dublējuma atrašanās vieta</string>
|
||||
<string name="invalid_backup_file">Nederīgs dublējuma fails</string>
|
||||
<string name="pref_backup_service_category">Automātiskā dublēšana</string>
|
||||
<string name="pref_backup_interval">Dublējumu biežums</string>
|
||||
<string name="backup_info">Automātiskā dublēšana ir ļoti ieteicama. Kopijas vajadzētu glabāt arī citās vietās.</string>
|
||||
<string name="about_dont_kill_my_app">Dažiem ražotājiem ir papildu lietojumprogrammu ierobežojumi, kas iznīcina fona pakalpojumus. Šajā vietnē ir vairāk informācijas par to, kā to izlabot.</string>
|
||||
<string name="tracking_info">Vienvirziena sinhronizācija, lai atjauninātu sekošanas pakalpojumu nodaļas progresu. Iestatiet izsekošanu atsevišķiem manga ierakstiem, izmantojot sekošanas pogu.</string>
|
||||
<string name="pref_disable_battery_optimization_summary">Palīdz ar fona bibliotēku atjauninājumiem un dublējumiem</string>
|
||||
<string name="battery_optimization_disabled">Akumulatora optimizācija jau ir atspējota</string>
|
||||
<string name="pref_verbose_logging_summary">Drukāt verbose žurnālus sistēmas žurnālā (samazina programmas veiktspēju)</string>
|
||||
<string name="pref_auto_update_manga_sync">Atjaunot progresu pēc lasīšanas</string>
|
||||
<string name="tracking_guide">Sekošanas rokasgrāmata</string>
|
||||
<string name="pref_dump_crash_logs">Saglabā avārijas žurnālu</string>
|
||||
<string name="pref_dump_crash_logs_summary">Saglabā kļūdu žurnālus failā priekš koplietošanas ar izstrādātājiem</string>
|
||||
<string name="pref_search_pinned_sources_only">Iekļaut tikai piespraustos avotus</string>
|
||||
<string name="pref_clear_chapter_cache">Notīrīt nodaļas kešatmiņu</string>
|
||||
<string name="pref_refresh_library_covers">Atsvaidzināt bibliotēkas vākus</string>
|
||||
<string name="clear_database_completed">Ieraksti izdzēsti</string>
|
||||
<string name="empty_backup_error">Nav bibliotēkas ierakstu, ko dublēt</string>
|
||||
<string name="restoring_backup_canceled">Atjaunošana atcelta</string>
|
||||
<string name="restore_miui_warning">Dublēšana/atjaunošana var nedarboties pareizi, ja ir atspējota MIUI Optimization.</string>
|
||||
<string name="pref_auto_clear_chapter_cache">Notīriet nodaļu kešatmiņu, aizverot lietotni</string>
|
||||
<string name="clear_database_source_item_count">%1$d grāmatas, kas nav bibliotēkas, ir datu bāzē</string>
|
||||
<string name="label_network">Tīkls</string>
|
||||
<string name="label_data">Dati</string>
|
||||
<string name="used_cache">Izmantots: %1$s</string>
|
||||
<string name="clear_database_confirmation">Vai esi pārliecināts\? Lasītās nodaļas un progress priekš manga, kuras nav bibliotēkā, būs zudis</string>
|
||||
<string name="database_clean">Datu bāze tīra</string>
|
||||
<string name="pref_disable_battery_optimization">Atspējot akumulatora optimizāciju</string>
|
||||
<string name="pref_enable_automatic_extension_updates">Pārbaudīt, vai nav paplašinājumu atjauninājumi</string>
|
||||
<string name="pref_restore_backup_summ">Atjaunot bibliotēku no dublējuma faila</string>
|
||||
<string name="pref_backup_slots">Maksimālais dublējumu skaits</string>
|
||||
<string name="pref_clear_cookies">Notīrīt sīkfailus</string>
|
||||
<string name="pref_dns_over_https">Izvēlēties DNS pār HTTPS (DoH)</string>
|
||||
<string name="cookies_cleared">Sīkfaili notīrīti</string>
|
||||
<string name="cache_delete_error">Tīrīšanas laikā radās kļūda</string>
|
||||
<string name="pref_clear_database">Notīrīt datu bāzi</string>
|
||||
<string name="pref_clear_database_summary">Dzēst vēsturi preikš manga, kas nav saglabāta bibliotēkā</string>
|
||||
<string name="pref_refresh_library_tracking_summary">Atjaunina statusu, vērtējumu un pēdējo izlasīto nodaļu no sekošanas servisa</string>
|
||||
<string name="pref_reset_viewer_flags">Atiestatīt atsevišķu sēriju lasītāja iestatījumus</string>
|
||||
<string name="pref_reset_viewer_flags_summary">Atiestatīt katras sērijas lasīšanas režīmu un orientāciju</string>
|
||||
<string name="pref_tablet_ui_mode">Planšetdatora lietotāja interfeiss</string>
|
||||
<string name="pref_verbose_logging">Verbose reģistrēšana</string>
|
||||
<string name="website">Tīmekļa vietne</string>
|
||||
<string name="version">Versija</string>
|
||||
<string name="whats_new">Jaunumi</string>
|
||||
<string name="updated_version">Atjaunināts uz v%1$s</string>
|
||||
<string name="licenses">Atvērtā avota licences</string>
|
||||
<string name="check_for_updates">Pārbaudīt, vai nav atjauninājumu</string>
|
||||
<string name="pref_clear_webview_data">Notīrīt WebView datus</string>
|
||||
<string name="webview_data_deleted">WebView dati notīrīti</string>
|
||||
<string name="crash_log_saved">Avārijas žurnāli saglabāti</string>
|
||||
<string name="label_background_activity">Fona darbība</string>
|
||||
<string name="battery_optimization_setting_activity_not_found">Nevarēja atvērt ierīces iestatījumus</string>
|
||||
<string name="services">Serviss</string>
|
||||
<string name="backup_created">Dublējums izveidots</string>
|
||||
<string name="pref_refresh_library_tracking">Atsvaidzināt sekošanu</string>
|
||||
<string name="pref_reset_viewer_flags_success">Visi lasītāja iestatījumi atiestatīti</string>
|
||||
<string name="pref_reset_viewer_flags_error">Nevarēja atiestatīt lasītāja iestatījumus</string>
|
||||
<string name="requires_app_restart">Lai stātos spēkā, ir nepieciešama lietotnes restartēšana</string>
|
||||
<string name="action_track">Sekot</string>
|
||||
<string name="enhanced_services">Uzlabotie pakalpojumi</string>
|
||||
<string name="tracker_not_logged_in">Nav pieteicies: %1$s</string>
|
||||
<string name="backup_restore_invalid_uri">Kļūda: tukšs URI</string>
|
||||
<string name="help_translate">Palīdzi tulkot</string>
|
||||
<string name="pref_enable_acra">Sūtīt avārijas ziņojumus</string>
|
||||
<string name="cache_deleted">Kešatmiņa nodzēsta. %1$d faili ir izdzēsti</string>
|
||||
</resources>
|
|
@ -21,6 +21,10 @@ delete:
|
|||
DELETE FROM manga_sync
|
||||
WHERE manga_id = :mangaId AND sync_id = :syncId;
|
||||
|
||||
getTracks:
|
||||
SELECT *
|
||||
FROM manga_sync;
|
||||
|
||||
getTracksByMangaId:
|
||||
SELECT *
|
||||
FROM manga_sync
|
||||
|
|
|
@ -86,6 +86,61 @@ AND C.date_upload > :after
|
|||
AND C.date_fetch > M.date_added
|
||||
ORDER BY C.date_upload DESC;
|
||||
|
||||
getLibrary:
|
||||
SELECT M.*, COALESCE(MC.category_id, 0) AS category
|
||||
FROM (
|
||||
SELECT mangas.*, COALESCE(C.unreadCount, 0) AS unread_count, COALESCE(R.readCount, 0) AS read_count
|
||||
FROM mangas
|
||||
LEFT JOIN (
|
||||
SELECT chapters.manga_id, COUNT(*) AS unreadCount
|
||||
FROM chapters
|
||||
WHERE chapters.read = 0
|
||||
GROUP BY chapters.manga_id
|
||||
) AS C
|
||||
ON mangas._id = C.manga_id
|
||||
LEFT JOIN (
|
||||
SELECT chapters.manga_id, COUNT(*) AS readCount
|
||||
FROM chapters
|
||||
WHERE chapters.read = 1
|
||||
GROUP BY chapters.manga_id
|
||||
) AS R
|
||||
WHERE mangas.favorite = 1
|
||||
GROUP BY mangas._id
|
||||
ORDER BY mangas.title
|
||||
) AS M
|
||||
LEFT JOIN (
|
||||
SELECT *
|
||||
FROM mangas_categories
|
||||
) AS MC
|
||||
ON M._id = MC.manga_id;
|
||||
|
||||
getLastRead:
|
||||
SELECT M.*, MAX(H.last_read) AS max
|
||||
FROM mangas M
|
||||
JOIN chapters C
|
||||
ON M._id = C.manga_id
|
||||
JOIN history H
|
||||
ON C._id = H.chapter_id
|
||||
WHERE M.favorite = 1
|
||||
GROUP BY M._id
|
||||
ORDER BY max ASC;
|
||||
|
||||
getLatestByChapterUploadDate:
|
||||
SELECT M.*, MAX(C.date_upload) AS max
|
||||
FROM mangas M
|
||||
JOIN chapters C
|
||||
ON M._id = C.manga_id
|
||||
GROUP BY M._id
|
||||
ORDER BY max ASC;
|
||||
|
||||
getLatestByChapterFetchDate:
|
||||
SELECT M.*, MAX(C.date_fetch) AS max
|
||||
FROM mangas M
|
||||
JOIN chapters C
|
||||
ON M._id = C.manga_id
|
||||
GROUP BY M._id
|
||||
ORDER BY max ASC;
|
||||
|
||||
deleteMangasNotInLibraryBySourceIds:
|
||||
DELETE FROM mangas
|
||||
WHERE favorite = 0 AND source IN :sourceIds;
|
||||
|
|
|
@ -21,6 +21,9 @@ delete:
|
|||
DELETE FROM anime_sync
|
||||
WHERE anime_id = :animeId AND sync_id = :syncId;
|
||||
|
||||
getAnimeTracks:
|
||||
SELECT *
|
||||
FROM anime_sync;
|
||||
|
||||
getTracksByAnimeId:
|
||||
SELECT *
|
||||
|
|
|
@ -86,6 +86,62 @@ AND EP.date_upload > :after
|
|||
AND EP.date_fetch > A.date_added
|
||||
ORDER BY EP.date_upload DESC;
|
||||
|
||||
getAnimelib:
|
||||
SELECT M.*, COALESCE(MC.category_id, 0) AS category
|
||||
FROM (
|
||||
SELECT animes.*, COALESCE(C.unseenCount, 0) AS unseen_count, COALESCE(R.seenCount, 0) AS seen_count
|
||||
FROM animes
|
||||
LEFT JOIN (
|
||||
SELECT episodes.anime_id, COUNT(*) AS unseenCount
|
||||
FROM episodes
|
||||
WHERE episodes.seen = 0
|
||||
GROUP BY episodes.anime_id
|
||||
) AS C
|
||||
ON animes._id = C.anime_id
|
||||
LEFT JOIN (
|
||||
SELECT episodes.anime_id, COUNT(*) AS seenCount
|
||||
FROM episodes
|
||||
WHERE episodes.seen = 1
|
||||
GROUP BY episodes.anime_id
|
||||
) AS R
|
||||
WHERE animes.favorite = 1
|
||||
GROUP BY animes._id
|
||||
ORDER BY animes.title
|
||||
) AS M
|
||||
LEFT JOIN (
|
||||
SELECT *
|
||||
FROM animes_categories
|
||||
) AS MC
|
||||
ON M._id = MC.anime_id;
|
||||
|
||||
getLastSeen:
|
||||
SELECT M.*, MAX(H.last_seen) AS max
|
||||
FROM animes M
|
||||
JOIN episodes C
|
||||
ON M._id = C.anime_id
|
||||
JOIN animehistory H
|
||||
ON C._id = H.episode_id
|
||||
WHERE M.favorite = 1
|
||||
GROUP BY M._id
|
||||
ORDER BY max ASC;
|
||||
|
||||
getLatestByEpisodeUploadDate:
|
||||
SELECT M.*, MAX(C.date_upload) AS max
|
||||
FROM animes M
|
||||
JOIN episodes C
|
||||
ON M._id = C.anime_id
|
||||
GROUP BY M._id
|
||||
ORDER BY max ASC;
|
||||
|
||||
getLatestByEpisodeFetchDate:
|
||||
SELECT M.*, MAX(C.date_fetch) AS max
|
||||
FROM animes M
|
||||
JOIN episodes C
|
||||
ON M._id = C.anime_id
|
||||
GROUP BY M._id
|
||||
ORDER BY max ASC;
|
||||
|
||||
|
||||
deleteAnimesNotInLibraryBySourceIds:
|
||||
DELETE FROM animes
|
||||
WHERE favorite = 0 AND source IN :sourceIds;
|
||||
|
|
Loading…
Reference in a new issue