mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-27 11:37:51 +02:00
Significantly improve browsing speed (near instantaneous) (#1946)
This commit is contained in:
@ -16,6 +16,9 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
|
|||||||
- Add user manga notes ([@imkunet](https://github.com/imkunet), [@AntsyLich](https://github.com/AntsyLich)) ([#428](https://github.com/mihonapp/mihon/pull/428))
|
- Add user manga notes ([@imkunet](https://github.com/imkunet), [@AntsyLich](https://github.com/AntsyLich)) ([#428](https://github.com/mihonapp/mihon/pull/428))
|
||||||
- Fix user notes not restoring when manga doesn't exist in DB ([@AntsyLich](https://github.com/AntsyLich)) ([#1945](https://github.com/mihonapp/mihon/pull/1945))
|
- Fix user notes not restoring when manga doesn't exist in DB ([@AntsyLich](https://github.com/AntsyLich)) ([#1945](https://github.com/mihonapp/mihon/pull/1945))
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
- Significantly improve browsing speed (near instantaneous) ([@AntsyLich](https://github.com/AntsyLich)) ([#1946](https://github.com/mihonapp/mihon/pull/1946))
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Fix Bangumi search results including novels ([@MajorTanya](https://github.com/MajorTanya)) ([#1885](https://github.com/mihonapp/mihon/pull/1885))
|
- Fix Bangumi search results including novels ([@MajorTanya](https://github.com/MajorTanya)) ([#1885](https://github.com/mihonapp/mihon/pull/1885))
|
||||||
- Fix next chapter button occasionally jumping to the last page of the current chapter ([@perokhe](https://github.com/perokhe)) ([#1920](https://github.com/mihonapp/mihon/pull/1920))
|
- Fix next chapter button occasionally jumping to the last page of the current chapter ([@perokhe](https://github.com/perokhe)) ([#1920](https://github.com/mihonapp/mihon/pull/1920))
|
||||||
|
@ -69,22 +69,6 @@ fun Manga.copyFrom(other: SManga): Manga {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun SManga.toDomainManga(sourceId: Long): Manga {
|
|
||||||
return Manga.create().copy(
|
|
||||||
url = url,
|
|
||||||
title = title,
|
|
||||||
artist = artist,
|
|
||||||
author = author,
|
|
||||||
description = description,
|
|
||||||
genre = getGenres(),
|
|
||||||
status = status.toLong(),
|
|
||||||
thumbnailUrl = thumbnail_url,
|
|
||||||
updateStrategy = update_strategy,
|
|
||||||
initialized = initialized,
|
|
||||||
source = sourceId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
||||||
return coverCache.getCustomCoverFile(id).exists()
|
return coverCache.getCustomCoverFile(id).exists()
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import cafe.adriel.voyager.core.model.StateScreenModel
|
|||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.manga.model.toDomainManga
|
|
||||||
import eu.kanade.domain.source.interactor.GetIncognitoState
|
import eu.kanade.domain.source.interactor.GetIncognitoState
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.domain.track.interactor.AddTracks
|
import eu.kanade.domain.track.interactor.AddTracks
|
||||||
@ -29,7 +28,6 @@ import kotlinx.collections.immutable.toImmutableList
|
|||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
@ -45,7 +43,6 @@ import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
|||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
||||||
import tachiyomi.domain.manga.interactor.GetManga
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.toMangaUpdate
|
import tachiyomi.domain.manga.model.toMangaUpdate
|
||||||
import tachiyomi.domain.source.interactor.GetRemoteManga
|
import tachiyomi.domain.source.interactor.GetRemoteManga
|
||||||
@ -68,7 +65,6 @@ class BrowseSourceScreenModel(
|
|||||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||||
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
||||||
private val getManga: GetManga = Injekt.get(),
|
private val getManga: GetManga = Injekt.get(),
|
||||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
|
||||||
private val updateManga: UpdateManga = Injekt.get(),
|
private val updateManga: UpdateManga = Injekt.get(),
|
||||||
private val addTracks: AddTracks = Injekt.get(),
|
private val addTracks: AddTracks = Injekt.get(),
|
||||||
private val getIncognitoState: GetIncognitoState = Injekt.get(),
|
private val getIncognitoState: GetIncognitoState = Injekt.get(),
|
||||||
@ -110,12 +106,11 @@ class BrowseSourceScreenModel(
|
|||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.map { listing ->
|
.map { listing ->
|
||||||
Pager(PagingConfig(pageSize = 25)) {
|
Pager(PagingConfig(pageSize = 25)) {
|
||||||
getRemoteManga.subscribe(sourceId, listing.query ?: "", listing.filters)
|
getRemoteManga(sourceId, listing.query ?: "", listing.filters)
|
||||||
}.flow.map { pagingData ->
|
}.flow.map { pagingData ->
|
||||||
pagingData.map {
|
pagingData.map { manga ->
|
||||||
networkToLocalManga.await(it.toDomainManga(sourceId))
|
getManga.subscribe(manga.url, manga.source)
|
||||||
.let { localManga -> getManga.subscribe(localManga.url, localManga.source) }
|
.map { it ?: manga }
|
||||||
.filterNotNull()
|
|
||||||
.stateIn(ioCoroutineScope)
|
.stateIn(ioCoroutineScope)
|
||||||
}
|
}
|
||||||
.filter { !hideInLibraryItems || !it.value.favorite }
|
.filter { !hideInLibraryItems || !it.value.favorite }
|
||||||
|
@ -5,7 +5,6 @@ import androidx.compose.runtime.Immutable
|
|||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.domain.manga.model.toDomainManga
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.presentation.util.ioCoroutineScope
|
import eu.kanade.presentation.util.ioCoroutineScope
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
@ -24,6 +23,7 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import mihon.domain.manga.model.toDomainManga
|
||||||
import tachiyomi.core.common.preference.toggle
|
import tachiyomi.core.common.preference.toggle
|
||||||
import tachiyomi.domain.manga.interactor.GetManga
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||||
@ -165,9 +165,8 @@ abstract class SearchScreenModel(
|
|||||||
source.getSearchManga(1, query, source.getFilterList())
|
source.getSearchManga(1, query, source.getFilterList())
|
||||||
}
|
}
|
||||||
|
|
||||||
val titles = page.mangas.map {
|
val titles = page.mangas.map { it.toDomainManga(source.id) }
|
||||||
networkToLocalManga.await(it.toDomainManga(source.id))
|
.let { networkToLocalManga(it) }
|
||||||
}
|
|
||||||
|
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
updateItem(source, SearchItemResult.Success(titles))
|
updateItem(source, SearchItemResult.Success(titles))
|
||||||
|
@ -4,18 +4,16 @@ import androidx.compose.runtime.Immutable
|
|||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||||
import eu.kanade.domain.manga.model.toDomainManga
|
|
||||||
import eu.kanade.domain.manga.model.toSManga
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import eu.kanade.tachiyomi.source.online.ResolvableSource
|
import eu.kanade.tachiyomi.source.online.ResolvableSource
|
||||||
import eu.kanade.tachiyomi.source.online.UriType
|
import eu.kanade.tachiyomi.source.online.UriType
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import mihon.domain.manga.model.toDomainManga
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
|
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
|
|
||||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
@ -27,7 +25,6 @@ class DeepLinkScreenModel(
|
|||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||||
private val getChapterByUrlAndMangaId: GetChapterByUrlAndMangaId = Injekt.get(),
|
private val getChapterByUrlAndMangaId: GetChapterByUrlAndMangaId = Injekt.get(),
|
||||||
private val getMangaByUrlAndSourceId: GetMangaByUrlAndSourceId = Injekt.get(),
|
|
||||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
||||||
) : StateScreenModel<DeepLinkScreenModel.State>(State.Loading) {
|
) : StateScreenModel<DeepLinkScreenModel.State>(State.Loading) {
|
||||||
|
|
||||||
@ -38,7 +35,7 @@ class DeepLinkScreenModel(
|
|||||||
.firstOrNull { it.getUriType(query) != UriType.Unknown }
|
.firstOrNull { it.getUriType(query) != UriType.Unknown }
|
||||||
|
|
||||||
val manga = source?.getManga(query)?.let {
|
val manga = source?.getManga(query)?.let {
|
||||||
getMangaFromSManga(it, source.id)
|
networkToLocalManga(it.toDomainManga(source.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
val chapter = if (source?.getUriType(query) == UriType.Chapter && manga != null) {
|
val chapter = if (source?.getUriType(query) == UriType.Chapter && manga != null) {
|
||||||
@ -73,11 +70,6 @@ class DeepLinkScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getMangaFromSManga(sManga: SManga, sourceId: Long): Manga {
|
|
||||||
return getMangaByUrlAndSourceId.await(sManga.url, sourceId)
|
|
||||||
?: networkToLocalManga.await(sManga.toDomainManga(sourceId))
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface State {
|
sealed interface State {
|
||||||
@Immutable
|
@Immutable
|
||||||
data object Loading : State
|
data object Loading : State
|
||||||
|
@ -97,35 +97,6 @@ class MangaRepositoryImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun insert(manga: Manga): Long? {
|
|
||||||
return handler.awaitOneOrNullExecutable(inTransaction = true) {
|
|
||||||
mangasQueries.insert(
|
|
||||||
source = manga.source,
|
|
||||||
url = manga.url,
|
|
||||||
artist = manga.artist,
|
|
||||||
author = manga.author,
|
|
||||||
description = manga.description,
|
|
||||||
genre = manga.genre,
|
|
||||||
title = manga.title,
|
|
||||||
status = manga.status,
|
|
||||||
thumbnailUrl = manga.thumbnailUrl,
|
|
||||||
favorite = manga.favorite,
|
|
||||||
lastUpdate = manga.lastUpdate,
|
|
||||||
nextUpdate = manga.nextUpdate,
|
|
||||||
calculateInterval = manga.fetchInterval.toLong(),
|
|
||||||
initialized = manga.initialized,
|
|
||||||
viewerFlags = manga.viewerFlags,
|
|
||||||
chapterFlags = manga.chapterFlags,
|
|
||||||
coverLastModified = manga.coverLastModified,
|
|
||||||
dateAdded = manga.dateAdded,
|
|
||||||
updateStrategy = manga.updateStrategy,
|
|
||||||
version = manga.version,
|
|
||||||
notes = manga.notes,
|
|
||||||
)
|
|
||||||
mangasQueries.selectLastInsertedRowId()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun update(update: MangaUpdate): Boolean {
|
override suspend fun update(update: MangaUpdate): Boolean {
|
||||||
return try {
|
return try {
|
||||||
partialUpdate(update)
|
partialUpdate(update)
|
||||||
@ -146,6 +117,37 @@ class MangaRepositoryImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun insertNetworkManga(manga: List<Manga>): List<Manga> {
|
||||||
|
return handler.await(inTransaction = true) {
|
||||||
|
manga.map {
|
||||||
|
mangasQueries.insertNetworkManga(
|
||||||
|
source = it.source,
|
||||||
|
url = it.url,
|
||||||
|
artist = it.artist,
|
||||||
|
author = it.author,
|
||||||
|
description = it.description,
|
||||||
|
genre = it.genre,
|
||||||
|
title = it.title,
|
||||||
|
status = it.status,
|
||||||
|
thumbnailUrl = it.thumbnailUrl,
|
||||||
|
favorite = it.favorite,
|
||||||
|
lastUpdate = it.lastUpdate,
|
||||||
|
nextUpdate = it.nextUpdate,
|
||||||
|
calculateInterval = it.fetchInterval.toLong(),
|
||||||
|
initialized = it.initialized,
|
||||||
|
viewerFlags = it.viewerFlags,
|
||||||
|
chapterFlags = it.chapterFlags,
|
||||||
|
coverLastModified = it.coverLastModified,
|
||||||
|
dateAdded = it.dateAdded,
|
||||||
|
updateStrategy = it.updateStrategy,
|
||||||
|
version = it.version,
|
||||||
|
mapper = MangaMapper::mapManga,
|
||||||
|
)
|
||||||
|
.executeAsOne()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun partialUpdate(vararg mangaUpdates: MangaUpdate) {
|
private suspend fun partialUpdate(vararg mangaUpdates: MangaUpdate) {
|
||||||
handler.await(inTransaction = true) {
|
handler.await(inTransaction = true) {
|
||||||
mangaUpdates.forEach { value ->
|
mangaUpdates.forEach { value ->
|
||||||
|
@ -4,56 +4,67 @@ import androidx.paging.PagingState
|
|||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import mihon.domain.manga.model.toDomainManga
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
import tachiyomi.domain.source.repository.SourcePagingSourceType
|
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.source.repository.SourcePagingSource
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class SourceSearchPagingSource(source: CatalogueSource, val query: String, val filters: FilterList) :
|
class SourceSearchPagingSource(
|
||||||
SourcePagingSource(source) {
|
source: CatalogueSource,
|
||||||
|
private val query: String,
|
||||||
|
private val filters: FilterList,
|
||||||
|
) : BaseSourcePagingSource(source) {
|
||||||
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
||||||
return source.getSearchManga(currentPage, query, filters)
|
return source.getSearchManga(currentPage, query, filters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SourcePopularPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
|
class SourcePopularPagingSource(source: CatalogueSource) : BaseSourcePagingSource(source) {
|
||||||
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
||||||
return source.getPopularManga(currentPage)
|
return source.getPopularManga(currentPage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SourceLatestPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
|
class SourceLatestPagingSource(source: CatalogueSource) : BaseSourcePagingSource(source) {
|
||||||
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
||||||
return source.getLatestUpdates(currentPage)
|
return source.getLatestUpdates(currentPage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class SourcePagingSource(
|
abstract class BaseSourcePagingSource(
|
||||||
protected val source: CatalogueSource,
|
protected val source: CatalogueSource,
|
||||||
) : SourcePagingSourceType() {
|
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||||
|
) : SourcePagingSource() {
|
||||||
|
|
||||||
abstract suspend fun requestNextPage(currentPage: Int): MangasPage
|
abstract suspend fun requestNextPage(currentPage: Int): MangasPage
|
||||||
|
|
||||||
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, SManga> {
|
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, Manga> {
|
||||||
val page = params.key ?: 1
|
val page = params.key ?: 1
|
||||||
|
|
||||||
val mangasPage = try {
|
return try {
|
||||||
withIOContext {
|
val mangasPage = withIOContext {
|
||||||
requestNextPage(page.toInt())
|
requestNextPage(page.toInt())
|
||||||
.takeIf { it.mangas.isNotEmpty() }
|
.takeIf { it.mangas.isNotEmpty() }
|
||||||
?: throw NoResultsException()
|
?: throw NoResultsException()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
return LoadResult.Error(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return LoadResult.Page(
|
val manga = mangasPage.mangas.map { it.toDomainManga(source.id) }
|
||||||
data = mangasPage.mangas,
|
.let { networkToLocalManga(it) }
|
||||||
|
|
||||||
|
LoadResult.Page(
|
||||||
|
data = manga,
|
||||||
prevKey = null,
|
prevKey = null,
|
||||||
nextKey = if (mangasPage.hasNextPage) page + 1 else null,
|
nextKey = if (mangasPage.hasNextPage) page + 1 else null,
|
||||||
)
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoadResult.Error(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRefreshKey(state: PagingState<Long, SManga>): Long? {
|
override fun getRefreshKey(state: PagingState<Long, Manga>): Long? {
|
||||||
return state.anchorPosition?.let { anchorPosition ->
|
return state.anchorPosition?.let { anchorPosition ->
|
||||||
val anchorPage = state.closestPageToPosition(anchorPosition)
|
val anchorPage = state.closestPageToPosition(anchorPosition)
|
||||||
anchorPage?.prevKey ?: anchorPage?.nextKey
|
anchorPage?.prevKey ?: anchorPage?.nextKey
|
||||||
|
@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.map
|
|||||||
import tachiyomi.data.DatabaseHandler
|
import tachiyomi.data.DatabaseHandler
|
||||||
import tachiyomi.domain.source.model.SourceWithCount
|
import tachiyomi.domain.source.model.SourceWithCount
|
||||||
import tachiyomi.domain.source.model.StubSource
|
import tachiyomi.domain.source.model.StubSource
|
||||||
import tachiyomi.domain.source.repository.SourcePagingSourceType
|
import tachiyomi.domain.source.repository.SourcePagingSource
|
||||||
import tachiyomi.domain.source.repository.SourceRepository
|
import tachiyomi.domain.source.repository.SourceRepository
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import tachiyomi.domain.source.model.Source as DomainSource
|
import tachiyomi.domain.source.model.Source as DomainSource
|
||||||
@ -72,17 +72,17 @@ class SourceRepositoryImpl(
|
|||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
query: String,
|
query: String,
|
||||||
filterList: FilterList,
|
filterList: FilterList,
|
||||||
): SourcePagingSourceType {
|
): SourcePagingSource {
|
||||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
val source = sourceManager.get(sourceId) as CatalogueSource
|
||||||
return SourceSearchPagingSource(source, query, filterList)
|
return SourceSearchPagingSource(source, query, filterList)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPopular(sourceId: Long): SourcePagingSourceType {
|
override fun getPopular(sourceId: Long): SourcePagingSource {
|
||||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
val source = sourceManager.get(sourceId) as CatalogueSource
|
||||||
return SourcePopularPagingSource(source)
|
return SourcePopularPagingSource(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLatest(sourceId: Long): SourcePagingSourceType {
|
override fun getLatest(sourceId: Long): SourcePagingSource {
|
||||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
val source = sourceManager.get(sourceId) as CatalogueSource
|
||||||
return SourceLatestPagingSource(source)
|
return SourceLatestPagingSource(source)
|
||||||
}
|
}
|
||||||
|
@ -182,3 +182,31 @@ WHERE _id = :mangaId;
|
|||||||
|
|
||||||
selectLastInsertedRowId:
|
selectLastInsertedRowId:
|
||||||
SELECT last_insert_rowid();
|
SELECT last_insert_rowid();
|
||||||
|
|
||||||
|
insertNetworkManga {
|
||||||
|
-- Insert the manga if it doesn't exist already
|
||||||
|
INSERT INTO mangas(
|
||||||
|
source, url, artist, author, description, genre, title, status, thumbnail_url, favorite,
|
||||||
|
last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added,
|
||||||
|
update_strategy, calculate_interval, last_modified_at, version
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite,
|
||||||
|
:lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded,
|
||||||
|
:updateStrategy, :calculateInterval, 0, :version
|
||||||
|
WHERE NOT EXISTS(SELECT 0 FROM mangas WHERE source = :source AND url = :url);
|
||||||
|
|
||||||
|
-- Update the title if it is not favorite
|
||||||
|
UPDATE mangas
|
||||||
|
SET title = :title
|
||||||
|
WHERE source = :source
|
||||||
|
AND url = :url
|
||||||
|
AND favorite = 0;
|
||||||
|
|
||||||
|
-- Finally return the manga
|
||||||
|
SELECT *
|
||||||
|
FROM mangas
|
||||||
|
WHERE source = :source
|
||||||
|
AND url = :url
|
||||||
|
LIMIT 1;
|
||||||
|
}
|
||||||
|
20
domain/src/main/java/mihon/domain/manga/model/SManga.kt
Normal file
20
domain/src/main/java/mihon/domain/manga/model/SManga.kt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package mihon.domain.manga.model
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
|
||||||
|
fun SManga.toDomainManga(sourceId: Long): Manga {
|
||||||
|
return Manga.create().copy(
|
||||||
|
url = url,
|
||||||
|
title = title,
|
||||||
|
artist = artist,
|
||||||
|
author = author,
|
||||||
|
description = description,
|
||||||
|
genre = getGenres(),
|
||||||
|
status = status.toLong(),
|
||||||
|
thumbnailUrl = thumbnail_url,
|
||||||
|
updateStrategy = update_strategy,
|
||||||
|
initialized = initialized,
|
||||||
|
source = sourceId,
|
||||||
|
)
|
||||||
|
}
|
@ -7,29 +7,11 @@ class NetworkToLocalManga(
|
|||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(manga: Manga): Manga {
|
suspend operator fun invoke(manga: Manga): Manga {
|
||||||
val localManga = getManga(manga.url, manga.source)
|
return mangaRepository.insertNetworkManga(listOf(manga)).single()
|
||||||
return when {
|
|
||||||
localManga == null -> {
|
|
||||||
val id = insertManga(manga)
|
|
||||||
manga.copy(id = id!!)
|
|
||||||
}
|
|
||||||
!localManga.favorite -> {
|
|
||||||
// if the manga isn't a favorite, set its display title from source
|
|
||||||
// if it later becomes a favorite, updated title will go to db
|
|
||||||
localManga.copy(title = manga.title)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
localManga
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getManga(url: String, sourceId: Long): Manga? {
|
suspend operator fun invoke(manga: List<Manga>): List<Manga> {
|
||||||
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
|
return mangaRepository.insertNetworkManga(manga)
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun insertManga(manga: Manga): Long? {
|
|
||||||
return mangaRepository.insert(manga)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,9 @@ interface MangaRepository {
|
|||||||
|
|
||||||
suspend fun setMangaCategories(mangaId: Long, categoryIds: List<Long>)
|
suspend fun setMangaCategories(mangaId: Long, categoryIds: List<Long>)
|
||||||
|
|
||||||
suspend fun insert(manga: Manga): Long?
|
|
||||||
|
|
||||||
suspend fun update(update: MangaUpdate): Boolean
|
suspend fun update(update: MangaUpdate): Boolean
|
||||||
|
|
||||||
suspend fun updateAll(mangaUpdates: List<MangaUpdate>): Boolean
|
suspend fun updateAll(mangaUpdates: List<MangaUpdate>): Boolean
|
||||||
|
|
||||||
|
suspend fun insertNetworkManga(manga: List<Manga>): List<Manga>
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
package tachiyomi.domain.source.interactor
|
package tachiyomi.domain.source.interactor
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import tachiyomi.domain.source.repository.SourcePagingSourceType
|
import tachiyomi.domain.source.repository.SourcePagingSource
|
||||||
import tachiyomi.domain.source.repository.SourceRepository
|
import tachiyomi.domain.source.repository.SourceRepository
|
||||||
|
|
||||||
class GetRemoteManga(
|
class GetRemoteManga(
|
||||||
private val repository: SourceRepository,
|
private val repository: SourceRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun subscribe(sourceId: Long, query: String, filterList: FilterList): SourcePagingSourceType {
|
operator fun invoke(sourceId: Long, query: String, filterList: FilterList): SourcePagingSource {
|
||||||
return when (query) {
|
return when (query) {
|
||||||
QUERY_POPULAR -> repository.getPopular(sourceId)
|
QUERY_POPULAR -> repository.getPopular(sourceId)
|
||||||
QUERY_LATEST -> repository.getLatest(sourceId)
|
QUERY_LATEST -> repository.getLatest(sourceId)
|
||||||
|
@ -2,12 +2,12 @@ package tachiyomi.domain.source.repository
|
|||||||
|
|
||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
import tachiyomi.domain.source.model.SourceWithCount
|
import tachiyomi.domain.source.model.SourceWithCount
|
||||||
|
|
||||||
typealias SourcePagingSourceType = PagingSource<Long, SManga>
|
typealias SourcePagingSource = PagingSource<Long, Manga>
|
||||||
|
|
||||||
interface SourceRepository {
|
interface SourceRepository {
|
||||||
|
|
||||||
@ -19,9 +19,9 @@ interface SourceRepository {
|
|||||||
|
|
||||||
fun getSourcesWithNonLibraryManga(): Flow<List<SourceWithCount>>
|
fun getSourcesWithNonLibraryManga(): Flow<List<SourceWithCount>>
|
||||||
|
|
||||||
fun search(sourceId: Long, query: String, filterList: FilterList): SourcePagingSourceType
|
fun search(sourceId: Long, query: String, filterList: FilterList): SourcePagingSource
|
||||||
|
|
||||||
fun getPopular(sourceId: Long): SourcePagingSourceType
|
fun getPopular(sourceId: Long): SourcePagingSource
|
||||||
|
|
||||||
fun getLatest(sourceId: Long): SourcePagingSourceType
|
fun getLatest(sourceId: Long): SourcePagingSource
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user