Chapters graphql, reader is broken

This commit is contained in:
Syer10
2024-09-02 18:08:57 -04:00
parent 856aa34aad
commit 478c58a9ac
37 changed files with 791 additions and 892 deletions

View File

@@ -20,11 +20,11 @@ class ReaderActivity : AppCompatActivity() {
fun newIntent( fun newIntent(
context: Context, context: Context,
mangaId: Long, mangaId: Long,
chapterIndex: Int, chapterId: Long,
): Intent = ): Intent =
Intent(context, ReaderActivity::class.java).apply { Intent(context, ReaderActivity::class.java).apply {
putExtra("manga", mangaId) putExtra("manga", mangaId)
putExtra("chapter", chapterIndex) putExtra("chapter", chapterId)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
} }
} }
@@ -34,8 +34,8 @@ class ReaderActivity : AppCompatActivity() {
val hooks = AppComponent.getInstance(applicationContext).hooks val hooks = AppComponent.getInstance(applicationContext).hooks
val mangaId = intent.extras!!.getLong("manga", -1) val mangaId = intent.extras!!.getLong("manga", -1)
val chapterIndex = intent.extras!!.getInt("chapter", -1) val chapterId = intent.extras!!.getLong("chapter", -1)
if (mangaId == -1L || chapterIndex == -1) { if (mangaId == -1L || chapterId == -1L) {
finish() finish()
return return
} }
@@ -44,7 +44,7 @@ class ReaderActivity : AppCompatActivity() {
CompositionLocalProvider(*hooks) { CompositionLocalProvider(*hooks) {
AppTheme { AppTheme {
ReaderMenu( ReaderMenu(
chapterIndex = chapterIndex, chapterId = chapterId,
mangaId = mangaId, mangaId = mangaId,
onCloseRequest = onBackPressedDispatcher::onBackPressed, onCloseRequest = onBackPressedDispatcher::onBackPressed,
) )

View File

@@ -0,0 +1,84 @@
fragment ChapterFragment on ChapterType {
chapterNumber
fetchedAt
id
isBookmarked
isDownloaded
isRead
lastPageRead
lastReadAt
mangaId
name
pageCount
realUrl
scanlator
sourceOrder
uploadDate
url
meta {
key
value
}
}
query GetChapter($id: Int!) {
chapter(id: $id) {
...ChapterFragment
}
}
query GetMangaChapters($id: Int!) {
chapters(
condition: {mangaId: $id}
orderBy: SOURCE_ORDER,
orderByType: DESC,
) {
nodes {
...ChapterFragment
}
}
}
mutation UpdateChapter($id: Int!, $patch: UpdateChapterPatchInput!) {
updateChapter(input: {id: $id, patch: $patch}) {
clientMutationId
}
}
mutation UpdateChapters($id: [Int!]!, $patch: UpdateChapterPatchInput!) {
updateChapters(input: {ids: $id, patch: $patch}) {
clientMutationId
}
}
mutation UpdateChapterMeta($id: Int!, $key: String!, $value: String!) {
setChapterMeta(input: {meta: {chapterId: $id, key: $key, value: $value}}) {
clientMutationId
}
}
mutation DeleteDownloadedChapter($id: Int!) {
deleteDownloadedChapter(input: {id: $id}) {
clientMutationId
}
}
mutation DeleteDownloadedChapters($ids: [Int!]!) {
deleteDownloadedChapters(input: {ids: $ids}) {
clientMutationId
}
}
mutation FetchChapters($mangaId: Int!) {
fetchChapters(input: {mangaId: $mangaId}) {
chapters {
...ChapterFragment
}
}
}
mutation FetchChapterPages($id: Int!) {
fetchChapterPages(input: {chapterId: $id}) {
pages
}
}

View File

@@ -7,7 +7,9 @@
package ca.gosyer.jui.data package ca.gosyer.jui.data
import ca.gosyer.jui.core.lang.addSuffix import ca.gosyer.jui.core.lang.addSuffix
import ca.gosyer.jui.data.chapter.ChapterRepositoryImpl
import ca.gosyer.jui.data.settings.SettingsRepositoryImpl import ca.gosyer.jui.data.settings.SettingsRepositoryImpl
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import ca.gosyer.jui.domain.server.Http import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.domain.server.service.ServerPreferences import ca.gosyer.jui.domain.server.service.ServerPreferences
import ca.gosyer.jui.domain.settings.service.SettingsRepository import ca.gosyer.jui.domain.settings.service.SettingsRepository
@@ -50,4 +52,11 @@ interface DataComponent : SharedDataComponent {
@Provides @Provides
fun settingsRepository(apolloClient: ApolloClient): SettingsRepository = SettingsRepositoryImpl(apolloClient) fun settingsRepository(apolloClient: ApolloClient): SettingsRepository = SettingsRepositoryImpl(apolloClient)
@Provides
fun chapterRepository(
apolloClient: ApolloClient,
http: Http,
serverPreferences: ServerPreferences,
): ChapterRepository = ChapterRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get())
} }

View File

@@ -0,0 +1,208 @@
package ca.gosyer.jui.data.chapter
import ca.gosyer.jui.data.graphql.DeleteDownloadedChapterMutation
import ca.gosyer.jui.data.graphql.DeleteDownloadedChaptersMutation
import ca.gosyer.jui.data.graphql.FetchChapterPagesMutation
import ca.gosyer.jui.data.graphql.FetchChaptersMutation
import ca.gosyer.jui.data.graphql.GetChapterQuery
import ca.gosyer.jui.data.graphql.GetMangaChaptersQuery
import ca.gosyer.jui.data.graphql.UpdateChapterMetaMutation
import ca.gosyer.jui.data.graphql.UpdateChapterMutation
import ca.gosyer.jui.data.graphql.UpdateChaptersMutation
import ca.gosyer.jui.data.graphql.fragment.ChapterFragment
import ca.gosyer.jui.data.graphql.type.UpdateChapterPatchInput
import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.model.ChapterMeta
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import ca.gosyer.jui.domain.server.Http
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.api.Optional
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.get
import io.ktor.client.statement.readBytes
import io.ktor.http.Url
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
class ChapterRepositoryImpl(
private val apolloClient: ApolloClient,
private val http: Http,
private val serverUrl: Url,
) : ChapterRepository {
override fun getChapter(chapterId: Long): Flow<Chapter> {
return apolloClient.query(
GetChapterQuery(chapterId.toInt())
)
.toFlow()
.map {
val data = it.dataAssertNoErrors
data.chapter.chapterFragment.toChapter()
}
}
override fun getChapters(mangaId: Long): Flow<List<Chapter>> {
return apolloClient.query(
GetMangaChaptersQuery(mangaId.toInt())
)
.toFlow()
.map {
val data = it.dataAssertNoErrors
data.chapters.nodes.map { it.chapterFragment.toChapter() }
}
}
override fun updateChapter(
chapterId: Long,
bookmarked: Boolean?,
read: Boolean?,
lastPageRead: Int?,
): Flow<Unit> {
return apolloClient.mutation(
UpdateChapterMutation(
chapterId.toInt(),
UpdateChapterPatchInput(
isBookmarked = Optional.presentIfNotNull(bookmarked),
isRead = Optional.presentIfNotNull(read),
lastPageRead = Optional.presentIfNotNull(lastPageRead)
),
)
)
.toFlow()
.map {
it.dataAssertNoErrors
}
}
override fun updateChapters(
chapterIds: List<Long>,
bookmarked: Boolean?,
read: Boolean?,
lastPageRead: Int?,
): Flow<Unit> {
return apolloClient.mutation(
UpdateChaptersMutation(
chapterIds.map { it.toInt() },
UpdateChapterPatchInput(
isBookmarked = Optional.presentIfNotNull(bookmarked),
isRead = Optional.presentIfNotNull(read),
lastPageRead = Optional.presentIfNotNull(lastPageRead)
),
)
)
.toFlow()
.map {
it.dataAssertNoErrors
}
}
override fun deleteDownloadedChapter(
chapterId: Long,
): Flow<Unit> {
return apolloClient.mutation(
DeleteDownloadedChapterMutation(
chapterId.toInt(),
)
)
.toFlow()
.map {
it.dataAssertNoErrors
}
}
override fun deleteDownloadedChapters(
chapterIds: List<Long>,
): Flow<Unit> {
return apolloClient.mutation(
DeleteDownloadedChaptersMutation(
chapterIds.map { it.toInt() },
)
)
.toFlow()
.map {
it.dataAssertNoErrors
}
}
override fun updateChapterMeta(
chapterId: Long,
key: String,
value: String,
): Flow<Unit> {
return apolloClient.mutation(
UpdateChapterMetaMutation(
chapterId.toInt(),
key,
value,
)
)
.toFlow()
.map {
it.dataAssertNoErrors
}
}
override fun fetchChapters(
mangaId: Long,
): Flow<List<Chapter>> {
return apolloClient.mutation(
FetchChaptersMutation(
mangaId.toInt(),
)
)
.toFlow()
.map {
val chapters = it.dataAssertNoErrors
chapters.fetchChapters.chapters.map { it.chapterFragment.toChapter() }
}
}
override fun getPages(chapterId: Long): Flow<List<String>> {
return apolloClient.mutation(
FetchChapterPagesMutation(
chapterId.toInt(),
)
)
.toFlow()
.map {
val chapters = it.dataAssertNoErrors
chapters.fetchChapterPages.pages
}
}
override fun getPage(
url: String,
block: HttpRequestBuilder.() -> Unit,
): Flow<ByteArray> {
val realUrl = Url("$serverUrl$url")
return flow { http.get(realUrl, block).readBytes() }
}
companion object {
internal fun ChapterFragment.toChapter(): Chapter {
return Chapter(
id = id.toLong(),
url = url,
name = name,
uploadDate = uploadDate,
chapterNumber = chapterNumber.toFloat(),
scanlator = scanlator,
mangaId = mangaId.toLong(),
read = isRead,
bookmarked = isBookmarked,
lastPageRead = lastPageRead,
index = sourceOrder,
fetchedAt = fetchedAt,
realUrl = realUrl,
pageCount = pageCount,
lastReadAt = lastPageRead,
downloaded = isDownloaded,
meta = ChapterMeta(
juiPageOffset = meta.find { it.key == "juiPageOffset" }?.value?.toIntOrNull() ?: 0
)
)
}
}
}

View File

@@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
@@ -35,12 +34,7 @@ class ServerListeners
) )
val mangaListener = _mangaListener.asSharedFlow() val mangaListener = _mangaListener.asSharedFlow()
private val _chapterIndexesListener = MutableSharedFlow<Pair<Long, List<Int>?>>( private val _chapterIdsListener = MutableSharedFlow<List<Long>>(
extraBufferCapacity = Channel.UNLIMITED,
)
val chapterIndexesListener = _chapterIndexesListener.asSharedFlow()
private val _chapterIdsListener = MutableSharedFlow<Pair<Long?, List<Long>>>(
extraBufferCapacity = Channel.UNLIMITED, extraBufferCapacity = Channel.UNLIMITED,
) )
val chapterIdsListener = _chapterIdsListener.asSharedFlow() val chapterIdsListener = _chapterIdsListener.asSharedFlow()
@@ -91,58 +85,32 @@ class ServerListeners
fun <T> combineChapters( fun <T> combineChapters(
flow: Flow<T>, flow: Flow<T>,
indexPredate: (suspend (Long, List<Int>?) -> Boolean)? = null, idPredate: (suspend (List<Long>) -> Boolean)? = null,
idPredate: (suspend (Long?, List<Long>) -> Boolean)? = null,
): Flow<T> { ): Flow<T> {
val indexListener = if (indexPredate != null) {
_chapterIndexesListener.filter { indexPredate(it.first, it.second) }.startWith(Unit)
} else {
_chapterIndexesListener.startWith(Unit)
}
val idsListener = if (idPredate != null) { val idsListener = if (idPredate != null) {
_chapterIdsListener.filter { idPredate(it.first, it.second) }.startWith(Unit) _chapterIdsListener.filter { idPredate(it) }.startWith(Unit)
} else { } else {
_chapterIdsListener.startWith(Unit) _chapterIdsListener.startWith(Unit)
} }
return combine(indexListener, idsListener) { _, _ -> } return idsListener
.buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) .buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
.flatMapLatest { flow } .flatMapLatest { flow }
} }
fun updateChapters( fun updateChapters(
mangaId: Long,
chapterIndexes: List<Int>,
) {
scope.launch {
_chapterIndexesListener.emit(mangaId to chapterIndexes.ifEmpty { null })
}
}
fun updateChapters(
mangaId: Long,
vararg chapterIndexes: Int,
) {
scope.launch {
_chapterIndexesListener.emit(mangaId to chapterIndexes.toList().ifEmpty { null })
}
}
fun updateChapters(
mangaId: Long?,
chapterIds: List<Long>, chapterIds: List<Long>,
) { ) {
scope.launch { scope.launch {
_chapterIdsListener.emit(mangaId to chapterIds) _chapterIdsListener.emit(chapterIds)
} }
} }
fun updateChapters( fun updateChapters(
mangaId: Long?,
vararg chapterIds: Long, vararg chapterIds: Long,
) { ) {
scope.launch { scope.launch {
_chapterIdsListener.emit(mangaId to chapterIds.toList()) _chapterIdsListener.emit(chapterIds.toList())
} }
} }

View File

@@ -1,256 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.ServerListeners
import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.model.ChapterBatchEditInput
import ca.gosyer.jui.domain.chapter.model.ChapterChange
import ca.gosyer.jui.domain.chapter.model.MangaChapterBatchEditInput
import ca.gosyer.jui.domain.chapter.service.ChapterRepositoryOld
import ca.gosyer.jui.domain.manga.model.Manga
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
import kotlin.jvm.JvmName
class BatchUpdateChapter
@Inject
constructor(
private val chapterRepositoryOld: ChapterRepositoryOld,
private val serverListeners: ServerListeners,
) {
@JvmName("awaitChapters")
suspend fun await(
mangaId: Long,
chapters: List<Chapter>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(mangaId, chapters, isRead, isBookmarked, lastPageRead, delete)
.catch {
onError(it)
log.warn(it) { "Failed to update multiple chapters of $mangaId" }
}
.collect()
suspend fun await(
mangaId: Long,
chapterIds: List<Long>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(mangaId, chapterIds, isRead, isBookmarked, lastPageRead, delete)
.catch {
onError(it)
log.warn(it) { "Failed to update multiple chapters of $mangaId" }
}
.collect()
@JvmName("awaitChapters")
suspend fun await(
manga: Manga,
chapters: List<Chapter>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(manga, chapters, isRead, isBookmarked, lastPageRead, delete)
.catch {
onError(it)
log.warn(it) { "Failed to update multiple chapters of ${manga.title}(${manga.id})" }
}
.collect()
suspend fun await(
manga: Manga,
chapterIds: List<Long>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(manga, chapterIds, isRead, isBookmarked, lastPageRead, delete)
.catch {
onError(it)
log.warn(it) { "Failed to update multiple chapters of ${manga.title}(${manga.id})" }
}
.collect()
@JvmName("awaitChapters")
suspend fun await(
chapters: List<Chapter>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapters, isRead, isBookmarked, lastPageRead, delete)
.catch {
onError(it)
log.warn(it) { "Failed to update multiple chapters" }
}
.collect()
suspend fun await(
chapterIds: List<Long>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapterIds, isRead, isBookmarked, lastPageRead, delete)
.catch {
onError(it)
log.warn(it) { "Failed to update update multiple chapters" }
}
.collect()
@JvmName("asFlowChapters")
fun asFlow(
mangaId: Long,
chapters: List<Chapter>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
) = getFlow(
mangaId = mangaId,
chapterIds = chapters.map { it.id },
isRead = isRead,
isBookmarked = isBookmarked,
lastPageRead = lastPageRead,
delete = delete,
)
fun asFlow(
mangaId: Long,
chapterIds: List<Long>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
) = getFlow(
mangaId = mangaId,
chapterIds = chapterIds,
isRead = isRead,
isBookmarked = isBookmarked,
lastPageRead = lastPageRead,
delete = delete,
)
@JvmName("asFlowChapters")
fun asFlow(
manga: Manga,
chapters: List<Chapter>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
) = getFlow(
mangaId = manga.id,
chapterIds = chapters.map { it.id },
isRead = isRead,
isBookmarked = isBookmarked,
lastPageRead = lastPageRead,
delete = delete,
)
fun asFlow(
manga: Manga,
chapterIds: List<Long>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
) = getFlow(
mangaId = manga.id,
chapterIds = chapterIds,
isRead = isRead,
isBookmarked = isBookmarked,
lastPageRead = lastPageRead,
delete = delete,
)
@JvmName("asFlowChapters")
fun asFlow(
chapters: List<Chapter>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
) = getFlow(
mangaId = null,
chapterIds = chapters.map { it.id },
isRead = isRead,
isBookmarked = isBookmarked,
lastPageRead = lastPageRead,
delete = delete,
)
fun asFlow(
chapterIds: List<Long>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
) = getFlow(
mangaId = null,
chapterIds = chapterIds,
isRead = isRead,
isBookmarked = isBookmarked,
lastPageRead = lastPageRead,
delete = delete,
)
private fun getFlow(
mangaId: Long?,
chapterIds: List<Long>,
isRead: Boolean? = null,
isBookmarked: Boolean? = null,
lastPageRead: Int? = null,
delete: Boolean? = null,
) = if (mangaId != null) {
chapterRepositoryOld.batchUpdateChapter(
mangaId,
MangaChapterBatchEditInput(
chapterIds = chapterIds,
change = ChapterChange(
isRead = isRead,
isBookmarked = isBookmarked,
lastPageRead = lastPageRead,
delete = delete,
),
),
)
} else {
chapterRepositoryOld.batchUpdateChapter(
ChapterBatchEditInput(
chapterIds = chapterIds,
change = ChapterChange(
isRead = isRead,
isBookmarked = isBookmarked,
lastPageRead = lastPageRead,
delete = delete,
),
),
)
}.onEach {
serverListeners.updateChapters(mangaId, chapterIds)
}
companion object {
private val log = logging()
}
}

View File

@@ -8,42 +8,31 @@ package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.ServerListeners
import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepositoryOld import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import ca.gosyer.jui.domain.manga.model.Manga
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import me.tatarka.inject.annotations.Inject import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging import org.lighthousegames.logging.logging
import kotlin.jvm.JvmName
class DeleteChapterDownload class DeleteChapterDownload
@Inject @Inject
constructor( constructor(
private val chapterRepositoryOld: ChapterRepositoryOld, private val chapterRepository: ChapterRepository,
private val serverListeners: ServerListeners, private val serverListeners: ServerListeners,
) { ) {
suspend fun await( suspend fun await(
mangaId: Long, chapterId: Long,
index: Int,
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(mangaId, index) ) = asFlow(chapterId)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to delete chapter download for $index of $mangaId" } log.warn(it) { "Failed to delete chapter download for $chapterId" }
}
.collect()
suspend fun await(
manga: Manga,
index: Int,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(manga, index)
.catch {
onError(it)
log.warn(it) { "Failed to delete chapter download for $index of ${manga.title}(${manga.id})" }
} }
.collect() .collect()
@JvmName("awaitChapter")
suspend fun await( suspend fun await(
chapter: Chapter, chapter: Chapter,
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
@@ -54,21 +43,46 @@ class DeleteChapterDownload
} }
.collect() .collect()
fun asFlow( suspend fun await(
mangaId: Long, chapterIds: List<Long>,
index: Int, onError: suspend (Throwable) -> Unit = {},
) = chapterRepositoryOld.deleteChapterDownload(mangaId, index) ) = asFlow(chapterIds)
.onEach { serverListeners.updateChapters(mangaId, index) } .catch {
onError(it)
log.warn(it) { "Failed to delete chapter download for $chapterIds" }
}
.collect()
@JvmName("awaitChapters")
suspend fun await(
chapters: List<Chapter>,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapters)
.catch {
onError(it)
log.warn(it) { "Failed to delete chapter download for ${chapters.joinToString { it.id.toString() }}" }
}
.collect()
fun asFlow( fun asFlow(
manga: Manga, chapterId: Long,
index: Int, ) = chapterRepository.deleteDownloadedChapter(chapterId)
) = chapterRepositoryOld.deleteChapterDownload(manga.id, index) .onEach { serverListeners.updateChapters(chapterId) }
.onEach { serverListeners.updateChapters(manga.id, index) }
@JvmName("asFlowChapter")
fun asFlow(chapter: Chapter) = fun asFlow(chapter: Chapter) =
chapterRepositoryOld.deleteChapterDownload(chapter.mangaId, chapter.index) chapterRepository.deleteDownloadedChapter(chapter.id)
.onEach { serverListeners.updateChapters(chapter.mangaId, chapter.index) } .onEach { serverListeners.updateChapters(chapter.id) }
fun asFlow(
chapterIds: List<Long>,
) = chapterRepository.deleteDownloadedChapters(chapterIds)
.onEach { serverListeners.updateChapters(chapterIds) }
@JvmName("asFlowChapters")
fun asFlow(chapter: List<Chapter>) =
chapterRepository.deleteDownloadedChapters(chapter.map { it.id })
.onEach { serverListeners.updateChapters(chapter.map { it.id }) }
companion object { companion object {
private val log = logging() private val log = logging()

View File

@@ -8,8 +8,7 @@ package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.ServerListeners
import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepositoryOld import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import ca.gosyer.jui.domain.manga.model.Manga
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull import kotlinx.coroutines.flow.singleOrNull
import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.take
@@ -19,30 +18,17 @@ import org.lighthousegames.logging.logging
class GetChapter class GetChapter
@Inject @Inject
constructor( constructor(
private val chapterRepositoryOld: ChapterRepositoryOld, private val chapterRepository: ChapterRepository,
private val serverListeners: ServerListeners, private val serverListeners: ServerListeners,
) { ) {
suspend fun await( suspend fun await(
mangaId: Long, chapterId: Long,
index: Int,
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(mangaId, index) ) = asFlow(chapterId)
.take(1) .take(1)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to get chapter $index for $mangaId" } log.warn(it) { "Failed to get chapter $chapterId" }
}
.singleOrNull()
suspend fun await(
manga: Manga,
index: Int,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(manga, index)
.take(1)
.catch {
onError(it)
log.warn(it) { "Failed to get chapter $index for ${manga.title}(${manga.id})" }
} }
.singleOrNull() .singleOrNull()
@@ -58,34 +44,16 @@ class GetChapter
.singleOrNull() .singleOrNull()
fun asFlow( fun asFlow(
mangaId: Long, chapterId: Long,
index: Int,
) = serverListeners.combineChapters( ) = serverListeners.combineChapters(
chapterRepositoryOld.getChapter(mangaId, index), chapterRepository.getChapter(chapterId),
indexPredate = { id, chapterIndexes -> idPredate = { ids -> chapterId in ids },
id == mangaId && (chapterIndexes == null || index in chapterIndexes)
},
idPredate = { id, _ -> id == mangaId },
)
fun asFlow(
manga: Manga,
index: Int,
) = serverListeners.combineChapters(
chapterRepositoryOld.getChapter(manga.id, index),
indexPredate = { id, chapterIndexes ->
id == manga.id && (chapterIndexes == null || index in chapterIndexes)
},
idPredate = { id, _ -> id == manga.id },
) )
fun asFlow(chapter: Chapter) = fun asFlow(chapter: Chapter) =
serverListeners.combineChapters( serverListeners.combineChapters(
chapterRepositoryOld.getChapter(chapter.mangaId, chapter.index), chapterRepository.getChapter(chapter.id),
indexPredate = { id, chapterIndexes -> idPredate = { ids -> chapter.id in ids },
id == chapter.mangaId && (chapterIndexes == null || chapter.index in chapterIndexes)
},
idPredate = { id, _ -> id == chapter.mangaId },
) )
companion object { companion object {

View File

@@ -1,84 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepositoryOld
import ca.gosyer.jui.domain.manga.model.Manga
import io.ktor.client.request.HttpRequestBuilder
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class GetChapterPage
@Inject
constructor(
private val chapterRepositoryOld: ChapterRepositoryOld,
) {
suspend fun await(
mangaId: Long,
index: Int,
pageNum: Int,
block: HttpRequestBuilder.() -> Unit,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(mangaId, index, pageNum, block)
.catch {
onError(it)
log.warn(it) { "Failed to get page $pageNum for chapter $index for $mangaId" }
}
.singleOrNull()
suspend fun await(
manga: Manga,
index: Int,
pageNum: Int,
block: HttpRequestBuilder.() -> Unit,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(manga, index, pageNum, block)
.catch {
onError(it)
log.warn(it) { "Failed to get page $pageNum for chapter $index for ${manga.title}(${manga.id})" }
}
.singleOrNull()
suspend fun await(
chapter: Chapter,
pageNum: Int,
block: HttpRequestBuilder.() -> Unit,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapter, pageNum, block)
.catch {
onError(it)
log.warn(it) { "Failed to get page $pageNum for chapter ${chapter.index} for ${chapter.mangaId}" }
}
.singleOrNull()
fun asFlow(
mangaId: Long,
index: Int,
pageNum: Int,
block: HttpRequestBuilder.() -> Unit,
) = chapterRepositoryOld.getPage(mangaId, index, pageNum, block)
fun asFlow(
manga: Manga,
index: Int,
pageNum: Int,
block: HttpRequestBuilder.() -> Unit,
) = chapterRepositoryOld.getPage(manga.id, index, pageNum, block)
fun asFlow(
chapter: Chapter,
pageNum: Int,
block: HttpRequestBuilder.() -> Unit,
) = chapterRepositoryOld.getPage(chapter.mangaId, chapter.index, pageNum, block)
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,54 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import io.ktor.client.request.HttpRequestBuilder
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class GetChapterPages
@Inject
constructor(
private val chapterRepository: ChapterRepository,
) {
suspend fun await(
chapterId: Long,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapterId)
.catch {
onError(it)
log.warn(it) { "Failed to get pages for $chapterId" }
}
.singleOrNull()
suspend fun await(
url: String,
onError: suspend (Throwable) -> Unit = {},
block: HttpRequestBuilder.() -> Unit,
) = asFlow(url, block)
.catch {
onError(it)
log.warn(it) { "Failed to get page $url" }
}
.singleOrNull()
fun asFlow(
chapterId: Long,
) = chapterRepository.getPages(chapterId)
fun asFlow(
url: String,
block: HttpRequestBuilder.() -> Unit,
) = chapterRepository.getPage(url, block)
companion object {
private val log = logging()
}
}

View File

@@ -7,7 +7,7 @@
package ca.gosyer.jui.domain.chapter.interactor package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.ServerListeners
import ca.gosyer.jui.domain.chapter.service.ChapterRepositoryOld import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import ca.gosyer.jui.domain.manga.model.Manga import ca.gosyer.jui.domain.manga.model.Manga
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull import kotlinx.coroutines.flow.singleOrNull
@@ -18,7 +18,7 @@ import org.lighthousegames.logging.logging
class GetChapters class GetChapters
@Inject @Inject
constructor( constructor(
private val chapterRepositoryOld: ChapterRepositoryOld, private val chapterRepository: ChapterRepository,
private val serverListeners: ServerListeners, private val serverListeners: ServerListeners,
) { ) {
suspend fun await( suspend fun await(
@@ -45,16 +45,14 @@ class GetChapters
fun asFlow(mangaId: Long) = fun asFlow(mangaId: Long) =
serverListeners.combineChapters( serverListeners.combineChapters(
chapterRepositoryOld.getChapters(mangaId), chapterRepository.getChapters(mangaId),
indexPredate = { id, _ -> id == mangaId }, idPredate = { ids -> false }, // todo
idPredate = { id, _ -> id == mangaId },
) )
fun asFlow(manga: Manga) = fun asFlow(manga: Manga) =
serverListeners.combineChapters( serverListeners.combineChapters(
chapterRepositoryOld.getChapters(manga.id), chapterRepository.getChapters(manga.id),
indexPredate = { id, _ -> id == manga.id }, idPredate = { ids -> false }, // todo
idPredate = { id, _ -> id == manga.id },
) )
companion object { companion object {

View File

@@ -7,7 +7,7 @@
package ca.gosyer.jui.domain.chapter.interactor package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.ServerListeners
import ca.gosyer.jui.domain.chapter.service.ChapterRepositoryOld import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import ca.gosyer.jui.domain.manga.model.Manga import ca.gosyer.jui.domain.manga.model.Manga
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -18,7 +18,7 @@ import org.lighthousegames.logging.logging
class RefreshChapters class RefreshChapters
@Inject @Inject
constructor( constructor(
private val chapterRepositoryOld: ChapterRepositoryOld, private val chapterRepository: ChapterRepository,
private val serverListeners: ServerListeners, private val serverListeners: ServerListeners,
) { ) {
suspend fun await( suspend fun await(
@@ -42,11 +42,11 @@ class RefreshChapters
.singleOrNull() .singleOrNull()
fun asFlow(mangaId: Long) = fun asFlow(mangaId: Long) =
chapterRepositoryOld.getChapters(mangaId, true) chapterRepository.fetchChapters(mangaId)
.onEach { serverListeners.updateChapters(mangaId) } .onEach { serverListeners.updateChapters(mangaId) }
fun asFlow(manga: Manga) = fun asFlow(manga: Manga) =
chapterRepositoryOld.getChapters(manga.id, true) chapterRepository.fetchChapters(manga.id)
.onEach { serverListeners.updateChapters(manga.id) } .onEach { serverListeners.updateChapters(manga.id) }
companion object { companion object {

View File

@@ -0,0 +1,130 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.ServerListeners
import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
import kotlin.jvm.JvmName
class UpdateChapter
@Inject
constructor(
private val chapterRepository: ChapterRepository,
private val serverListeners: ServerListeners,
) {
suspend fun await(
chapterId: Long,
bookmarked: Boolean? = null,
read: Boolean? = null,
lastPageRead: Int? = null,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapterId, bookmarked, read, lastPageRead)
.catch {
onError(it)
log.warn(it) { "Failed to update chapter bookmark for chapter $chapterId" }
}
.collect()
suspend fun await(
chapter: Chapter,
bookmarked: Boolean? = null,
read: Boolean? = null,
lastPageRead: Int? = null,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapter, bookmarked, read, lastPageRead)
.catch {
onError(it)
log.warn(it) { "Failed to update chapter bookmark for chapter ${chapter.index} of ${chapter.mangaId}" }
}
.collect()
suspend fun await(
chapterIds: List<Long>,
bookmarked: Boolean? = null,
read: Boolean? = null,
lastPageRead: Int? = null,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapterIds, bookmarked, read, lastPageRead)
.catch {
onError(it)
log.warn(it) { "Failed to update chapter bookmark for chapters $chapterIds" }
}
.collect()
@JvmName("awaitChapters")
suspend fun await(
chapters: List<Chapter>,
bookmarked: Boolean? = null,
read: Boolean? = null,
lastPageRead: Int? = null,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapters, bookmarked, read, lastPageRead)
.catch {
onError(it)
log.warn(it) { "Failed to update chapter bookmark for chapters ${chapters.joinToString { it.id.toString() }}" }
}
.collect()
fun asFlow(
chapterId: Long,
bookmarked: Boolean? = null,
read: Boolean? = null,
lastPageRead: Int? = null,
) = chapterRepository.updateChapter(
chapterId = chapterId,
bookmarked = bookmarked,
read = read,
lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(chapterId) }
fun asFlow(
chapter: Chapter,
bookmarked: Boolean? = null,
read: Boolean? = null,
lastPageRead: Int? = null
) = chapterRepository.updateChapter(
chapterId = chapter.id,
bookmarked = bookmarked,
read = read,
lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(chapter.id) }
fun asFlow(
chapterIds: List<Long>,
bookmarked: Boolean? = null,
read: Boolean? = null,
lastPageRead: Int? = null,
) = chapterRepository.updateChapters(
chapterIds = chapterIds,
bookmarked = bookmarked,
read = read,
lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(chapterIds) }
@JvmName("asFlowChapters")
fun asFlow(
chapters: List<Chapter>,
bookmarked: Boolean? = null,
read: Boolean? = null,
lastPageRead: Int? = null
) = chapterRepository.updateChapters(
chapterIds = chapters.map { it.id },
bookmarked = bookmarked,
read = read,
lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(chapters.map { it.id }) }
companion object {
private val log = logging()
}
}

View File

@@ -1,92 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.ServerListeners
import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepositoryOld
import ca.gosyer.jui.domain.manga.model.Manga
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class UpdateChapterBookmarked
@Inject
constructor(
private val chapterRepositoryOld: ChapterRepositoryOld,
private val serverListeners: ServerListeners,
) {
suspend fun await(
mangaId: Long,
index: Int,
bookmarked: Boolean,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(mangaId, index, bookmarked)
.catch {
onError(it)
log.warn(it) { "Failed to update chapter bookmark for chapter $index of $mangaId" }
}
.collect()
suspend fun await(
manga: Manga,
index: Int,
bookmarked: Boolean,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(manga, index, bookmarked)
.catch {
onError(it)
log.warn(it) { "Failed to update chapter bookmark for chapter $index of ${manga.title}(${manga.id})" }
}
.collect()
suspend fun await(
chapter: Chapter,
bookmarked: Boolean,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapter, bookmarked)
.catch {
onError(it)
log.warn(it) { "Failed to update chapter bookmark for chapter ${chapter.index} of ${chapter.mangaId}" }
}
.collect()
fun asFlow(
mangaId: Long,
index: Int,
bookmarked: Boolean,
) = chapterRepositoryOld.updateChapter(
mangaId = mangaId,
chapterIndex = index,
bookmarked = bookmarked,
).onEach { serverListeners.updateChapters(mangaId, index) }
fun asFlow(
manga: Manga,
index: Int,
bookmarked: Boolean,
) = chapterRepositoryOld.updateChapter(
mangaId = manga.id,
chapterIndex = index,
bookmarked = bookmarked,
).onEach { serverListeners.updateChapters(manga.id, index) }
fun asFlow(
chapter: Chapter,
bookmarked: Boolean,
) = chapterRepositoryOld.updateChapter(
mangaId = chapter.mangaId,
chapterIndex = chapter.index,
bookmarked = bookmarked,
).onEach { serverListeners.updateChapters(chapter.mangaId, chapter.index) }
companion object {
private val log = logging()
}
}

View File

@@ -8,8 +8,7 @@ package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.ServerListeners
import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepositoryOld import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import ca.gosyer.jui.domain.manga.model.Manga
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -19,30 +18,17 @@ import org.lighthousegames.logging.logging
class UpdateChapterLastPageRead class UpdateChapterLastPageRead
@Inject @Inject
constructor( constructor(
private val chapterRepositoryOld: ChapterRepositoryOld, private val chapterRepository: ChapterRepository,
private val serverListeners: ServerListeners, private val serverListeners: ServerListeners,
) { ) {
suspend fun await( suspend fun await(
mangaId: Long, chapterId: Long,
index: Int,
lastPageRead: Int, lastPageRead: Int,
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(mangaId, index, lastPageRead) ) = asFlow(chapterId, lastPageRead)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to update chapter last page read for chapter $index of $mangaId" } log.warn(it) { "Failed to update chapter last page read for chapter $chapterId" }
}
.collect()
suspend fun await(
manga: Manga,
index: Int,
lastPageRead: Int,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(manga, index, lastPageRead)
.catch {
onError(it)
log.warn(it) { "Failed to update chapter last page read for chapter $index of ${manga.title}(${manga.id})" }
} }
.collect() .collect()
@@ -58,33 +44,20 @@ class UpdateChapterLastPageRead
.collect() .collect()
fun asFlow( fun asFlow(
mangaId: Long, chapterId: Long,
index: Int,
lastPageRead: Int, lastPageRead: Int,
) = chapterRepositoryOld.updateChapter( ) = chapterRepository.updateChapter(
mangaId = mangaId, chapterId = chapterId,
chapterIndex = index,
lastPageRead = lastPageRead, lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(mangaId, index) } ).onEach { serverListeners.updateChapters(chapterId) }
fun asFlow(
manga: Manga,
index: Int,
lastPageRead: Int,
) = chapterRepositoryOld.updateChapter(
mangaId = manga.id,
chapterIndex = index,
lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(manga.id, index) }
fun asFlow( fun asFlow(
chapter: Chapter, chapter: Chapter,
lastPageRead: Int, lastPageRead: Int,
) = chapterRepositoryOld.updateChapter( ) = chapterRepository.updateChapter(
mangaId = chapter.mangaId, chapterId = chapter.id,
chapterIndex = chapter.index,
lastPageRead = lastPageRead, lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(chapter.mangaId, chapter.index) } ).onEach { serverListeners.updateChapters(chapter.id) }
companion object { companion object {
private val log = logging() private val log = logging()

View File

@@ -1,85 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.ServerListeners
import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepositoryOld
import ca.gosyer.jui.domain.manga.model.Manga
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class UpdateChapterMarkPreviousRead
@Inject
constructor(
private val chapterRepositoryOld: ChapterRepositoryOld,
private val serverListeners: ServerListeners,
) {
suspend fun await(
mangaId: Long,
index: Int,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(mangaId, index)
.catch {
onError(it)
log.warn(it) { "Failed to update chapter read status for chapter $index of $mangaId" }
}
.collect()
suspend fun await(
manga: Manga,
index: Int,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(manga, index)
.catch {
onError(it)
log.warn(it) { "Failed to update chapter read status for chapter $index of ${manga.title}(${manga.id})" }
}
.collect()
suspend fun await(
chapter: Chapter,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapter)
.catch {
onError(it)
log.warn(it) { "Failed to update chapter read status for chapter ${chapter.index} of ${chapter.mangaId}" }
}
.collect()
fun asFlow(
mangaId: Long,
index: Int,
) = chapterRepositoryOld.updateChapter(
mangaId = mangaId,
chapterIndex = index,
markPreviousRead = true,
).onEach { serverListeners.updateChapters(mangaId, index) }
fun asFlow(
manga: Manga,
index: Int,
) = chapterRepositoryOld.updateChapter(
mangaId = manga.id,
chapterIndex = index,
markPreviousRead = true,
).onEach { serverListeners.updateChapters(manga.id, index) }
fun asFlow(chapter: Chapter) =
chapterRepositoryOld.updateChapter(
mangaId = chapter.mangaId,
chapterIndex = chapter.index,
markPreviousRead = true,
).onEach { serverListeners.updateChapters(chapter.mangaId, chapter.index) }
companion object {
private val log = logging()
}
}

View File

@@ -8,7 +8,7 @@ package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.ServerListeners
import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepositoryOld import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
@@ -18,7 +18,7 @@ import org.lighthousegames.logging.logging
class UpdateChapterMeta class UpdateChapterMeta
@Inject @Inject
constructor( constructor(
private val chapterRepositoryOld: ChapterRepositoryOld, private val chapterRepository: ChapterRepository,
private val serverListeners: ServerListeners, private val serverListeners: ServerListeners,
) { ) {
suspend fun await( suspend fun await(
@@ -37,13 +37,12 @@ class UpdateChapterMeta
pageOffset: Int = chapter.meta.juiPageOffset, pageOffset: Int = chapter.meta.juiPageOffset,
) = flow { ) = flow {
if (pageOffset != chapter.meta.juiPageOffset) { if (pageOffset != chapter.meta.juiPageOffset) {
chapterRepositoryOld.updateChapterMeta( chapterRepository.updateChapterMeta(
chapter.mangaId, chapter.id,
chapter.index,
"juiPageOffset", "juiPageOffset",
pageOffset.toString(), pageOffset.toString(),
).collect() ).collect()
serverListeners.updateChapters(chapter.mangaId, chapter.index) serverListeners.updateChapters(chapter.id)
} }
emit(Unit) emit(Unit)
} }

View File

@@ -8,8 +8,7 @@ package ca.gosyer.jui.domain.chapter.interactor
import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.ServerListeners
import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepositoryOld import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import ca.gosyer.jui.domain.manga.model.Manga
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -19,30 +18,17 @@ import org.lighthousegames.logging.logging
class UpdateChapterRead class UpdateChapterRead
@Inject @Inject
constructor( constructor(
private val chapterRepositoryOld: ChapterRepositoryOld, private val chapterRepository: ChapterRepository,
private val serverListeners: ServerListeners, private val serverListeners: ServerListeners,
) { ) {
suspend fun await( suspend fun await(
mangaId: Long, chapterId: Long,
index: Int,
read: Boolean, read: Boolean,
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(mangaId, index, read) ) = asFlow(chapterId, read)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to update chapter read status for chapter $index of $mangaId" } log.warn(it) { "Failed to update chapter read status for chapter $chapterId" }
}
.collect()
suspend fun await(
manga: Manga,
index: Int,
read: Boolean,
onError: suspend (Throwable) -> Unit = {},
) = asFlow(manga, index, read)
.catch {
onError(it)
log.warn(it) { "Failed to update chapter read status for chapter $index of ${manga.title}(${manga.id})" }
} }
.collect() .collect()
@@ -58,33 +44,28 @@ class UpdateChapterRead
.collect() .collect()
fun asFlow( fun asFlow(
mangaId: Long, chapterId: Long,
index: Int,
read: Boolean, read: Boolean,
) = chapterRepositoryOld.updateChapter( ) = chapterRepository.updateChapter(
mangaId = mangaId, chapterId = chapterId,
chapterIndex = index,
read = read, read = read,
).onEach { serverListeners.updateChapters(mangaId, index) } ).onEach { serverListeners.updateChapters(chapterId) }
fun asFlow( fun asFlow(
manga: Manga, chapterIds: List<Long>,
index: Int,
read: Boolean, read: Boolean,
) = chapterRepositoryOld.updateChapter( ) = chapterRepository.updateChapters(
mangaId = manga.id, chapterIds = chapterIds,
chapterIndex = index,
read = read, read = read,
).onEach { serverListeners.updateChapters(manga.id, index) } ).onEach { serverListeners.updateChapters(chapterIds) }
fun asFlow( fun asFlow(
chapter: Chapter, chapter: Chapter,
read: Boolean, read: Boolean,
) = chapterRepositoryOld.updateChapter( ) = chapterRepository.updateChapter(
mangaId = chapter.mangaId, chapterId = chapter.id,
chapterIndex = chapter.index,
read = read, read = read,
).onEach { serverListeners.updateChapters(chapter.mangaId, chapter.index) } ).onEach { serverListeners.updateChapters(chapter.id) }
companion object { companion object {
private val log = logging() private val log = logging()

View File

@@ -25,7 +25,6 @@ data class Chapter(
val index: Int, val index: Int,
val fetchedAt: Long, val fetchedAt: Long,
val realUrl: String?, val realUrl: String?,
val chapterCount: Int?,
val pageCount: Int?, val pageCount: Int?,
val lastReadAt: Int?, val lastReadAt: Int?,
val downloaded: Boolean, val downloaded: Boolean,

View File

@@ -0,0 +1,56 @@
package ca.gosyer.jui.domain.chapter.service
import ca.gosyer.jui.domain.chapter.model.Chapter
import io.ktor.client.request.HttpRequestBuilder
import kotlinx.coroutines.flow.Flow
interface ChapterRepository {
fun getChapter(
chapterId: Long,
): Flow<Chapter>
fun getChapters(
mangaId: Long,
): Flow<List<Chapter>>
fun updateChapter(
chapterId: Long,
bookmarked: Boolean? = null,
read: Boolean? = null,
lastPageRead: Int? = null,
): Flow<Unit>
fun updateChapters(
chapterIds: List<Long>,
bookmarked: Boolean? = null,
read: Boolean? = null,
lastPageRead: Int? = null,
): Flow<Unit>
fun deleteDownloadedChapter(
chapterId: Long,
): Flow<Unit>
fun deleteDownloadedChapters(
chapterIds: List<Long>,
): Flow<Unit>
fun updateChapterMeta(
chapterId: Long,
key: String,
value: String,
): Flow<Unit>
fun fetchChapters(
mangaId: Long,
): Flow<List<Chapter>>
fun getPages(
chapterId: Long,
): Flow<List<String>>
fun getPage(
url: String,
block: HttpRequestBuilder.() -> Unit,
): Flow<ByteArray>
}

View File

@@ -81,7 +81,7 @@ class UpdatesPager
updates.filterIsInstance<Updates.Update>().map { it.manga.id } updates.filterIsInstance<Updates.Update>().map { it.manga.id }
}.stateIn(this, SharingStarted.Eagerly, emptyList()) }.stateIn(this, SharingStarted.Eagerly, emptyList())
private val chapterIds = foldedUpdates.map { updates -> private val chapterIds = foldedUpdates.map { updates ->
updates.filterIsInstance<Updates.Update>().map { Triple(it.manga.id, it.chapter.index, it.chapter.id) } updates.filterIsInstance<Updates.Update>().map { it.chapter.id }
}.stateIn(this, SharingStarted.Eagerly, emptyList()) }.stateIn(this, SharingStarted.Eagerly, emptyList())
private val changedManga = serverListeners.mangaListener.runningFold(emptyMap<Long, Manga>()) { manga, updatedMangaIds -> private val changedManga = serverListeners.mangaListener.runningFold(emptyMap<Long, Manga>()) { manga, updatedMangaIds ->
@@ -97,36 +97,12 @@ class UpdatesPager
private val changedChapters = MutableStateFlow(emptyMap<Long, Chapter>()) private val changedChapters = MutableStateFlow(emptyMap<Long, Chapter>())
init { init {
serverListeners.chapterIndexesListener
.onEach { (mangaId, chapterIndexes) ->
if (chapterIndexes == null) {
val chapters = coroutineScope {
foldedUpdates.value.filterIsInstance<Updates.Update>().filter { it.manga.id == mangaId }.map {
async {
getChapter.await(it.manga.id, it.chapter.index)
}
}.awaitAll().filterNotNull().associateBy { it.id }
}
changedChapters.update { it + chapters }
} else {
val chapters = coroutineScope {
chapterIndexes.mapNotNull { index -> chapterIds.value.find { it.first == mangaId && it.second == index } }
.map {
async {
getChapter.await(it.first, it.second)
}
}.awaitAll().filterNotNull().associateBy { it.id }
}
changedChapters.update { it + chapters }
}
}
.launchIn(this)
serverListeners.chapterIdsListener serverListeners.chapterIdsListener
.onEach { (_, updatedChapterIds) -> .onEach { updatedChapterIds ->
val chapters = coroutineScope { val chapters = coroutineScope {
updatedChapterIds.mapNotNull { id -> chapterIds.value.find { it.third == id } }.map { updatedChapterIds.mapNotNull { id -> chapterIds.value.find { it == id } }.map {
async { async {
getChapter.await(it.first, it.second) getChapter.await(it)
} }
}.awaitAll().filterNotNull().associateBy { it.id } }.awaitAll().filterNotNull().associateBy { it.id }
} }

View File

@@ -16,12 +16,12 @@ actual class ReaderLauncher(
private val context: Context, private val context: Context,
) { ) {
actual fun launch( actual fun launch(
chapterIndex: Int, chapterId: Long,
mangaId: Long, mangaId: Long,
) { ) {
Intent(context, Class.forName("ca.gosyer.jui.android.ReaderActivity")).apply { Intent(context, Class.forName("ca.gosyer.jui.android.ReaderActivity")).apply {
putExtra("manga", mangaId) putExtra("manga", mangaId)
putExtra("chapter", chapterIndex) putExtra("chapter", chapterId)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
}.let(context::startActivity) }.let(context::startActivity)
} }

View File

@@ -12,11 +12,10 @@ import ca.gosyer.jui.domain.category.interactor.GetCategories
import ca.gosyer.jui.domain.category.interactor.GetMangaCategories import ca.gosyer.jui.domain.category.interactor.GetMangaCategories
import ca.gosyer.jui.domain.category.interactor.RemoveMangaFromCategory import ca.gosyer.jui.domain.category.interactor.RemoveMangaFromCategory
import ca.gosyer.jui.domain.category.model.Category import ca.gosyer.jui.domain.category.model.Category
import ca.gosyer.jui.domain.chapter.interactor.BatchUpdateChapter
import ca.gosyer.jui.domain.chapter.interactor.DeleteChapterDownload import ca.gosyer.jui.domain.chapter.interactor.DeleteChapterDownload
import ca.gosyer.jui.domain.chapter.interactor.GetChapters import ca.gosyer.jui.domain.chapter.interactor.GetChapters
import ca.gosyer.jui.domain.chapter.interactor.RefreshChapters import ca.gosyer.jui.domain.chapter.interactor.RefreshChapters
import ca.gosyer.jui.domain.chapter.interactor.UpdateChapterMarkPreviousRead import ca.gosyer.jui.domain.chapter.interactor.UpdateChapter
import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.download.interactor.BatchChapterDownload import ca.gosyer.jui.domain.download.interactor.BatchChapterDownload
import ca.gosyer.jui.domain.download.interactor.QueueChapterDownload import ca.gosyer.jui.domain.download.interactor.QueueChapterDownload
@@ -63,8 +62,7 @@ class MangaScreenViewModel
private val refreshManga: RefreshManga, private val refreshManga: RefreshManga,
private val getChapters: GetChapters, private val getChapters: GetChapters,
private val refreshChapters: RefreshChapters, private val refreshChapters: RefreshChapters,
private val batchUpdateChapter: BatchUpdateChapter, private val updateChapter: UpdateChapter,
private val updateChapterMarkPreviousRead: UpdateChapterMarkPreviousRead,
private val queueChapterDownload: QueueChapterDownload, private val queueChapterDownload: QueueChapterDownload,
private val stopChapterDownload: StopChapterDownload, private val stopChapterDownload: StopChapterDownload,
private val deleteChapterDownload: DeleteChapterDownload, private val deleteChapterDownload: DeleteChapterDownload,
@@ -253,8 +251,8 @@ class MangaScreenViewModel
read: Boolean, read: Boolean,
) { ) {
scope.launch { scope.launch {
manga.value?.let { manga -> manga.value?.let {
batchUpdateChapter.await(manga, chapterIds, isRead = read, onError = { toast(it.message.orEmpty()) }) updateChapter.await(chapterIds, read = read, onError = { toast(it.message.orEmpty()) })
_selectedIds.value = persistentListOf() _selectedIds.value = persistentListOf()
} }
} }
@@ -269,8 +267,8 @@ class MangaScreenViewModel
bookmark: Boolean, bookmark: Boolean,
) { ) {
scope.launch { scope.launch {
manga.value?.let { manga -> manga.value?.let {
batchUpdateChapter.await(manga, chapterIds, isBookmarked = bookmark, onError = { toast(it.message.orEmpty()) }) updateChapter.await(chapterIds, bookmarked = bookmark, onError = { toast(it.message.orEmpty()) })
_selectedIds.value = persistentListOf() _selectedIds.value = persistentListOf()
} }
} }
@@ -282,8 +280,11 @@ class MangaScreenViewModel
fun markPreviousRead(index: Int) { fun markPreviousRead(index: Int) {
scope.launch { scope.launch {
manga.value?.let { manga -> manga.value?.let {
updateChapterMarkPreviousRead.await(manga, index, onError = { toast(it.message.orEmpty()) }) val chapters = chapters.value
.sortedBy { it.chapter.index }
.subList(0, index).map{it.chapter.id} // todo test
updateChapter.await(chapters, read = true, onError = { toast(it.message.orEmpty()) })
_selectedIds.value = persistentListOf() _selectedIds.value = persistentListOf()
} }
} }
@@ -298,9 +299,8 @@ class MangaScreenViewModel
fun deleteDownload(id: Long?) { fun deleteDownload(id: Long?) {
scope.launch { scope.launch {
if (id == null) { if (id == null) {
val manga = _manga.value ?: return@launch
val chapterIds = _selectedIds.value val chapterIds = _selectedIds.value
batchUpdateChapter.await(manga, chapterIds, delete = true, onError = { toast(it.message.orEmpty()) }) deleteChapterDownload.await(chapterIds, onError = { toast(it.message.orEmpty()) })
selectedItems.value.forEach { selectedItems.value.forEach {
it.setNotDownloaded() it.setNotDownloaded()
} }

View File

@@ -58,7 +58,7 @@ expect fun Modifier.chapterItemModifier(
fun ChapterItem( fun ChapterItem(
chapterDownload: ChapterDownloadItem, chapterDownload: ChapterDownloadItem,
format: (Instant) -> String, format: (Instant) -> String,
onClick: (Int) -> Unit, onClick: (Long) -> Unit,
markRead: (Long) -> Unit, markRead: (Long) -> Unit,
markUnread: (Long) -> Unit, markUnread: (Long) -> Unit,
bookmarkChapter: (Long) -> Unit, bookmarkChapter: (Long) -> Unit,
@@ -78,7 +78,7 @@ fun ChapterItem(
.height(70.dp) .height(70.dp)
.selectedBackground(isSelected) .selectedBackground(isSelected)
.chapterItemModifier( .chapterItemModifier(
onClick = { onClick(chapter.index) }, onClick = { onClick(chapter.id) },
markRead = { markRead(chapter.id) }.takeUnless { chapter.read }, markRead = { markRead(chapter.id) }.takeUnless { chapter.read },
markUnread = { markUnread(chapter.id) }.takeIf { chapter.read }, markUnread = { markUnread(chapter.id) }.takeIf { chapter.read },
bookmarkChapter = { bookmarkChapter(chapter.id) }.takeUnless { chapter.bookmarked }, bookmarkChapter = { bookmarkChapter(chapter.id) }.takeUnless { chapter.bookmarked },

View File

@@ -228,11 +228,9 @@ fun MangaScreenContent(
onClick = if (inActionMode) { onClick = if (inActionMode) {
{ {
if (chapter.isSelected.value) { if (chapter.isSelected.value) {
onUnselectChapter( onUnselectChapter(it)
chapter.chapter.id,
)
} else { } else {
onSelectChapter(chapter.chapter.id) onSelectChapter(it)
} }
} }
} else { } else {

View File

@@ -6,6 +6,7 @@
package ca.gosyer.jui.ui.reader package ca.gosyer.jui.ui.reader
import ca.gosyer.jui.domain.chapter.interactor.GetChapterPages
import ca.gosyer.jui.domain.reader.service.ReaderPreferences import ca.gosyer.jui.domain.reader.service.ReaderPreferences
import ca.gosyer.jui.domain.server.Http import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.ui.base.image.BitmapDecoderFactory import ca.gosyer.jui.ui.base.image.BitmapDecoderFactory
@@ -26,6 +27,7 @@ class ChapterLoader(
private val http: Http, private val http: Http,
private val chapterCache: DiskCache, private val chapterCache: DiskCache,
private val bitmapDecoderFactory: BitmapDecoderFactory, private val bitmapDecoderFactory: BitmapDecoderFactory,
private val getChapterPages: GetChapterPages,
) { ) {
fun loadChapter(chapter: ReaderChapter): StateFlow<PagesState> { fun loadChapter(chapter: ReaderChapter): StateFlow<PagesState> {
if (chapterIsReady(chapter)) { if (chapterIsReady(chapter)) {
@@ -34,7 +36,7 @@ class ChapterLoader(
chapter.state = ReaderChapter.State.Loading chapter.state = ReaderChapter.State.Loading
log.debug { "Loading pages for ${chapter.chapter.name}" } log.debug { "Loading pages for ${chapter.chapter.name}" }
val loader = TachideskPageLoader(chapter, readerPreferences, http, chapterCache, bitmapDecoderFactory) val loader = TachideskPageLoader(chapter, readerPreferences, http, chapterCache, bitmapDecoderFactory, getChapterPages)
val pages = loader.getPages() val pages = loader.getPages()

View File

@@ -101,7 +101,7 @@ import kotlinx.coroutines.launch
expect class ReaderLauncher { expect class ReaderLauncher {
fun launch( fun launch(
chapterIndex: Int, chapterId: Long,
mangaId: Long, mangaId: Long,
) )
@@ -114,12 +114,12 @@ expect fun rememberReaderLauncher(): ReaderLauncher
@Composable @Composable
fun ReaderMenu( fun ReaderMenu(
chapterIndex: Int, chapterId: Long,
mangaId: Long, mangaId: Long,
onCloseRequest: () -> Unit, onCloseRequest: () -> Unit,
) { ) {
val viewModels = LocalViewModels.current val viewModels = LocalViewModels.current
val vm = remember { viewModels.readerViewModel(ReaderMenuViewModel.Params(chapterIndex, mangaId)) } val vm = remember { viewModels.readerViewModel(ReaderMenuViewModel.Params(chapterId, mangaId)) }
DisposableEffect(vm) { DisposableEffect(vm) {
onDispose(vm::onDispose) onDispose(vm::onDispose)
} }

View File

@@ -9,11 +9,10 @@ package ca.gosyer.jui.ui.reader
import ca.gosyer.jui.core.lang.launchDefault import ca.gosyer.jui.core.lang.launchDefault
import ca.gosyer.jui.core.prefs.getAsFlow import ca.gosyer.jui.core.prefs.getAsFlow
import ca.gosyer.jui.domain.chapter.interactor.GetChapter import ca.gosyer.jui.domain.chapter.interactor.GetChapter
import ca.gosyer.jui.domain.chapter.interactor.GetChapterPage import ca.gosyer.jui.domain.chapter.interactor.GetChapterPages
import ca.gosyer.jui.domain.chapter.interactor.GetChapters import ca.gosyer.jui.domain.chapter.interactor.GetChapters
import ca.gosyer.jui.domain.chapter.interactor.UpdateChapterLastPageRead import ca.gosyer.jui.domain.chapter.interactor.UpdateChapter
import ca.gosyer.jui.domain.chapter.interactor.UpdateChapterMeta import ca.gosyer.jui.domain.chapter.interactor.UpdateChapterMeta
import ca.gosyer.jui.domain.chapter.interactor.UpdateChapterRead
import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.manga.interactor.GetManga import ca.gosyer.jui.domain.manga.interactor.GetManga
import ca.gosyer.jui.domain.manga.interactor.UpdateMangaMeta import ca.gosyer.jui.domain.manga.interactor.UpdateMangaMeta
@@ -77,9 +76,8 @@ class ReaderMenuViewModel
private val getManga: GetManga, private val getManga: GetManga,
private val getChapters: GetChapters, private val getChapters: GetChapters,
private val getChapter: GetChapter, private val getChapter: GetChapter,
private val getChapterPage: GetChapterPage, private val getChapterPages: GetChapterPages,
private val updateChapterRead: UpdateChapterRead, private val updateChapter: UpdateChapter,
private val updateChapterLastPageRead: UpdateChapterLastPageRead,
private val updateMangaMeta: UpdateMangaMeta, private val updateMangaMeta: UpdateMangaMeta,
private val updateChapterMeta: UpdateChapterMeta, private val updateChapterMeta: UpdateChapterMeta,
private val chapterCache: ChapterCache, private val chapterCache: ChapterCache,
@@ -157,6 +155,7 @@ class ReaderMenuViewModel
http = http, http = http,
chapterCache = chapterCache, chapterCache = chapterCache,
bitmapDecoderFactory = BitmapDecoderFactory(contextWrapper), bitmapDecoderFactory = BitmapDecoderFactory(contextWrapper),
getChapterPages = getChapterPages,
) )
init { init {
@@ -167,7 +166,7 @@ class ReaderMenuViewModel
scope.launchDefault { scope.launchDefault {
runCatching { runCatching {
initManga(params.mangaId) initManga(params.mangaId)
initChapters(params.mangaId, params.chapterIndex) initChapters(params.mangaId, params.chapterId)
} }
} }
} }
@@ -179,11 +178,13 @@ class ReaderMenuViewModel
.collectLatest { page -> .collectLatest { page ->
page.chapter.pageLoader?.loadPage(page) page.chapter.pageLoader?.loadPage(page)
if (page.chapter == chapter.value) { if (page.chapter == chapter.value) {
if ((page.index + 1) >= page.chapter.chapter.pageCount!!) { val pages = page.chapter.pages.value as? PagesState.Success
?: return@collectLatest
if ((page.index2 + 1) >= pages.pages.size) {
markChapterRead(page.chapter) markChapterRead(page.chapter)
} }
val nextChapter = nextChapter.value val nextChapter = nextChapter.value
if (nextChapter != null && (page.index + 1) >= (page.chapter.chapter.pageCount!! - 5)) { if (nextChapter != null && (page.index2 + 1) >= ((pages.pages.size - 5).coerceAtLeast(1))) {
requestPreloadChapter(nextChapter) requestPreloadChapter(nextChapter)
} }
} else { } else {
@@ -191,10 +192,10 @@ class ReaderMenuViewModel
val nextChapter = nextChapter.value val nextChapter = nextChapter.value
if (page.chapter == previousChapter) { if (page.chapter == previousChapter) {
viewerChapters.value = viewerChapters.value.movePrev() viewerChapters.value = viewerChapters.value.movePrev()
initChapters(params.mangaId, page.chapter.chapter.index, fromMenuButton = false) initChapters(params.mangaId, page.chapter.chapter.id, fromMenuButton = false)
} else if (page.chapter == nextChapter) { } else if (page.chapter == nextChapter) {
viewerChapters.value = viewerChapters.value.moveNext() viewerChapters.value = viewerChapters.value.moveNext()
initChapters(params.mangaId, page.chapter.chapter.index, fromMenuButton = false) initChapters(params.mangaId, page.chapter.chapter.id, fromMenuButton = false)
} }
} }
} }
@@ -253,7 +254,7 @@ class ReaderMenuViewModel
} }
fun retry(page: ReaderPage) { fun retry(page: ReaderPage) {
log.info { "Retrying ${page.index}" } log.info { "Retrying ${page.index2}" }
chapter.value?.pageLoader?.retryPage(page) chapter.value?.pageLoader?.retryPage(page)
} }
@@ -277,7 +278,7 @@ class ReaderMenuViewModel
_state.value = ReaderChapter.State.Wait _state.value = ReaderChapter.State.Wait
sendProgress() sendProgress()
viewerChapters.value = viewerChapters.value.movePrev() viewerChapters.value = viewerChapters.value.movePrev()
initChapters(params.mangaId, prevChapter.chapter.index, fromMenuButton = true) initChapters(params.mangaId, prevChapter.chapter.id, fromMenuButton = true)
} catch (e: Exception) { } catch (e: Exception) {
log.warn(e) { "Error loading prev chapter" } log.warn(e) { "Error loading prev chapter" }
} }
@@ -291,7 +292,7 @@ class ReaderMenuViewModel
_state.value = ReaderChapter.State.Wait _state.value = ReaderChapter.State.Wait
sendProgress() sendProgress()
viewerChapters.value = viewerChapters.value.moveNext() viewerChapters.value = viewerChapters.value.moveNext()
initChapters(params.mangaId, nextChapter.chapter.index, fromMenuButton = true) initChapters(params.mangaId, nextChapter.chapter.id, fromMenuButton = true)
} catch (e: Exception) { } catch (e: Exception) {
log.warn(e) { "Error loading next chapter" } log.warn(e) { "Error loading next chapter" }
} }
@@ -313,52 +314,45 @@ class ReaderMenuViewModel
private suspend fun initChapters( private suspend fun initChapters(
mangaId: Long, mangaId: Long,
chapterIndex: Int, chapterId: Long,
fromMenuButton: Boolean = true, fromMenuButton: Boolean = true,
) { ) {
log.debug { "Loading chapter index $chapterIndex" } log.debug { "Loading chapter index $chapterId" }
val (chapter, pages) = coroutineScope { val (chapter, pages) = coroutineScope {
val getCurrentChapter = async { val chapters = getChapters.asFlow(mangaId)
val chapter = getReaderChapter(chapterIndex) ?: return@async null .take(1)
val pages = loader.loadChapter(chapter) .catch {
viewerChapters.update { it.copy(currChapter = chapter) } _state.value = ReaderChapter.State.Error(it)
chapter to pages log.warn(it) { "Error getting chapters for $mangaId" }
}
.singleOrNull()
?: return@coroutineScope null
val chapter = chapters.find { it.id == chapterId }
?.let { ReaderChapter(it) }
?: return@coroutineScope null
val pages = loader.loadChapter(chapter)
viewerChapters.update { it.copy(currChapter = chapter) }
if (viewerChapters.value.nextChapter == null) {
val nextChapter = chapters.find { it.index == chapter.chapter.index + 1 }
if (nextChapter != null) {
val nextReaderChapter = ReaderChapter(nextChapter)
viewerChapters.update { it.copy(nextChapter = nextReaderChapter) }
} else {
viewerChapters.update { it.copy(nextChapter = null) }
}
} }
val getAdjacentChapters = async { if (viewerChapters.value.prevChapter == null) {
val chapters = getChapters.await( val prevChapter = chapters.find { it.index == chapter.chapter.index - 1 }
mangaId, if (prevChapter != null) {
onError = { /* TODO: 2022-07-01 Error toast */ }, val prevReaderChapter = ReaderChapter(prevChapter)
).orEmpty() viewerChapters.update { it.copy(prevChapter = prevReaderChapter) }
} else {
val nextChapter = async { viewerChapters.update { it.copy(prevChapter = null) }
if (viewerChapters.value.nextChapter == null) {
val nextChapter = chapters.find { it.index == chapterIndex + 1 }
if (nextChapter != null) {
val nextReaderChapter = getReaderChapter(nextChapter.index)
viewerChapters.update { it.copy(nextChapter = nextReaderChapter) }
} else {
viewerChapters.update { it.copy(nextChapter = null) }
}
}
} }
val prevChapter = async {
if (viewerChapters.value.prevChapter == null) {
val prevChapter = chapters.find { it.index == chapterIndex - 1 }
if (prevChapter != null) {
val prevReaderChapter = getReaderChapter(prevChapter.index)
viewerChapters.update { it.copy(prevChapter = prevReaderChapter) }
} else {
viewerChapters.update { it.copy(prevChapter = null) }
}
}
}
nextChapter.await()
prevChapter.await()
} }
chapter to pages
getAdjacentChapters.await()
getCurrentChapter.await()
} ?: return } ?: return
if (fromMenuButton) { if (fromMenuButton) {
@@ -386,18 +380,6 @@ class ReaderMenuViewModel
} }
} }
private suspend fun getReaderChapter(chapterIndex: Int): ReaderChapter? {
return ReaderChapter(
getChapter.asFlow(params.mangaId, chapterIndex)
.take(1)
.catch {
_state.value = ReaderChapter.State.Error(it)
log.warn(it) { "Error getting chapter $chapterIndex" }
}
.singleOrNull() ?: return null,
)
}
fun requestPreloadChapter(chapter: ReaderChapter) { fun requestPreloadChapter(chapter: ReaderChapter) {
if (chapter.state != ReaderChapter.State.Wait && chapter.state !is ReaderChapter.State.Error) { if (chapter.state != ReaderChapter.State.Wait && chapter.state !is ReaderChapter.State.Error) {
return return
@@ -408,19 +390,19 @@ class ReaderMenuViewModel
private fun markChapterRead(chapter: ReaderChapter) { private fun markChapterRead(chapter: ReaderChapter) {
scope.launch { scope.launch {
updateChapterRead.await(chapter.chapter, read = true, onError = { toast(it.message.orEmpty()) }) updateChapter.await(chapter.chapter, read = true, onError = { toast(it.message.orEmpty()) })
} }
} }
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
fun sendProgress( fun sendProgress(
chapter: Chapter? = this.chapter.value?.chapter, chapter: Chapter? = this.chapter.value?.chapter,
lastPageRead: Int = (currentPage.value as? ReaderPage)?.index ?: 0, lastPageRead: Int = (currentPage.value as? ReaderPage)?.index2 ?: 0,
) { ) {
chapter ?: return chapter ?: return
if (chapter.read) return if (chapter.read) return
GlobalScope.launch { GlobalScope.launch {
updateChapterLastPageRead.await( updateChapter.await(
chapter, chapter,
lastPageRead = lastPageRead, lastPageRead = lastPageRead,
onError = { toast(it.message.orEmpty()) }, onError = { toast(it.message.orEmpty()) },
@@ -448,7 +430,7 @@ class ReaderMenuViewModel
} }
data class Params( data class Params(
val chapterIndex: Int, val chapterId: Long,
val mangaId: Long, val mangaId: Long,
) )

View File

@@ -42,7 +42,9 @@ import androidx.compose.material.icons.rounded.ChevronLeft
import androidx.compose.material.icons.rounded.SkipNext import androidx.compose.material.icons.rounded.SkipNext
import androidx.compose.material.icons.rounded.SkipPrevious import androidx.compose.material.icons.rounded.SkipPrevious
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -56,6 +58,7 @@ import ca.gosyer.jui.core.util.replace
import ca.gosyer.jui.domain.manga.model.MangaMeta import ca.gosyer.jui.domain.manga.model.MangaMeta
import ca.gosyer.jui.domain.reader.model.Direction import ca.gosyer.jui.domain.reader.model.Direction
import ca.gosyer.jui.i18n.MR import ca.gosyer.jui.i18n.MR
import ca.gosyer.jui.ui.reader.loader.PagesState
import ca.gosyer.jui.ui.reader.model.ReaderChapter import ca.gosyer.jui.ui.reader.model.ReaderChapter
import ca.gosyer.jui.ui.reader.model.ReaderItem import ca.gosyer.jui.ui.reader.model.ReaderItem
import ca.gosyer.jui.uicore.components.AroundLayout import ca.gosyer.jui.uicore.components.AroundLayout
@@ -82,7 +85,6 @@ fun ReaderSideMenu(
) { ) {
Surface(Modifier.fillMaxHeight().width(260.dp)) { Surface(Modifier.fillMaxHeight().width(260.dp)) {
Column(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize()) {
val pageCount = chapter.chapter.pageCount!!
ReaderMenuToolbar(onCloseSideMenuClicked = onCloseSideMenuClicked) ReaderMenuToolbar(onCloseSideMenuClicked = onCloseSideMenuClicked)
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
ReaderModeSetting( ReaderModeSetting(
@@ -91,6 +93,12 @@ fun ReaderSideMenu(
onSetReaderMode = onSetReaderMode, onSetReaderMode = onSetReaderMode,
) )
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
val chapterPages by key(chapter.chapter.id) { chapter.pages.collectAsState() }
val pageCount = when (chapterPages) {
PagesState.Empty -> null
PagesState.Loading -> null
is PagesState.Success -> (chapterPages as? PagesState.Success)?.pages?.size
}
ReaderProgressSlider( ReaderProgressSlider(
pages = pages, pages = pages,
currentPage = currentPage, currentPage = currentPage,
@@ -183,6 +191,12 @@ fun ReaderExpandBottomMenu(
shape = CircleShape, shape = CircleShape,
backgroundColor = MaterialTheme.colors.surface.copy(alpha = 0.5F), backgroundColor = MaterialTheme.colors.surface.copy(alpha = 0.5F),
) { ) {
val chapterPages by key(chapter.chapter.id) { chapter.pages.collectAsState() }
val pageCount = when (chapterPages) {
PagesState.Empty -> null
PagesState.Loading -> null
is PagesState.Success -> (chapterPages as? PagesState.Success)?.pages?.size
}
AroundLayout( AroundLayout(
Modifier.padding(horizontal = 8.dp), Modifier.padding(horizontal = 8.dp),
startLayout = { startLayout = {
@@ -190,7 +204,7 @@ fun ReaderExpandBottomMenu(
val text = if (!isRtL) { val text = if (!isRtL) {
pages.indexOf(currentPage) pages.indexOf(currentPage)
} else { } else {
chapter.chapter.pageCount!! pageCount ?: "1"
}.toString() }.toString()
Text(text, fontSize = 15.sp) Text(text, fontSize = 15.sp)
} }
@@ -200,7 +214,7 @@ fun ReaderExpandBottomMenu(
val text = if (isRtL) { val text = if (isRtL) {
pages.indexOf(currentPage) pages.indexOf(currentPage)
} else { } else {
chapter.chapter.pageCount!! pageCount ?: "1"
}.toString() }.toString()
Text(text, fontSize = 15.sp) Text(text, fontSize = 15.sp)
} }
@@ -212,7 +226,7 @@ fun ReaderExpandBottomMenu(
.padding(horizontal = 4.dp), .padding(horizontal = 4.dp),
pages = pages, pages = pages,
currentPage = currentPage, currentPage = currentPage,
pageCount = chapter.chapter.pageCount!!, pageCount = pageCount,
onNewPageClicked = navigate, onNewPageClicked = navigate,
isRtL = isRtL, isRtL = isRtL,
) )
@@ -278,7 +292,7 @@ private fun ReaderProgressSlider(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
pages: ImmutableList<ReaderItem>, pages: ImmutableList<ReaderItem>,
currentPage: ReaderItem?, currentPage: ReaderItem?,
pageCount: Int, pageCount: Int?,
onNewPageClicked: (Int) -> Unit, onNewPageClicked: (Int) -> Unit,
isRtL: Boolean, isRtL: Boolean,
) { ) {
@@ -295,9 +309,10 @@ private fun ReaderProgressSlider(
onNewPageClicked(it.roundToInt()) onNewPageClicked(it.roundToInt())
} }
}, },
valueRange = 0F..pageCount.toFloat(), valueRange = 0F..(pageCount ?: 1).toFloat(),
steps = pageCount, steps = (pageCount ?: 1),
onValueChangeFinished = { isValueChanging = false }, onValueChangeFinished = { isValueChanging = false },
enabled = pageCount != null,
modifier = modifier.let { modifier = modifier.let {
if (isRtL) { if (isRtL) {
it then Modifier.rotate(180F) it then Modifier.rotate(180F)

View File

@@ -8,15 +8,16 @@ package ca.gosyer.jui.ui.reader.loader
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import ca.gosyer.jui.core.io.SYSTEM import ca.gosyer.jui.core.io.SYSTEM
import ca.gosyer.jui.core.io.source
import ca.gosyer.jui.core.lang.PriorityChannel import ca.gosyer.jui.core.lang.PriorityChannel
import ca.gosyer.jui.core.lang.throwIfCancellation import ca.gosyer.jui.core.lang.throwIfCancellation
import ca.gosyer.jui.domain.chapter.interactor.GetChapterPages
import ca.gosyer.jui.domain.reader.service.ReaderPreferences import ca.gosyer.jui.domain.reader.service.ReaderPreferences
import ca.gosyer.jui.domain.server.Http import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.ui.base.image.BitmapDecoderFactory import ca.gosyer.jui.ui.base.image.BitmapDecoderFactory
import ca.gosyer.jui.ui.base.model.StableHolder import ca.gosyer.jui.ui.base.model.StableHolder
import ca.gosyer.jui.ui.reader.model.ReaderChapter import ca.gosyer.jui.ui.reader.model.ReaderChapter
import ca.gosyer.jui.ui.reader.model.ReaderPage import ca.gosyer.jui.ui.reader.model.ReaderPage
import ca.gosyer.jui.ui.util.lang.toSource
import cafe.adriel.voyager.core.concurrent.AtomicInt32 import cafe.adriel.voyager.core.concurrent.AtomicInt32
import com.seiko.imageloader.asImageBitmap import com.seiko.imageloader.asImageBitmap
import com.seiko.imageloader.cache.disk.DiskCache import com.seiko.imageloader.cache.disk.DiskCache
@@ -25,9 +26,6 @@ import com.seiko.imageloader.model.DataSource
import com.seiko.imageloader.model.ImageResult import com.seiko.imageloader.model.ImageResult
import com.seiko.imageloader.option.Options import com.seiko.imageloader.option.Options
import io.ktor.client.plugins.onDownload import io.ktor.client.plugins.onDownload
import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsChannel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO import kotlinx.coroutines.IO
@@ -39,6 +37,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@@ -57,6 +56,7 @@ class TachideskPageLoader(
private val http: Http, private val http: Http,
private val chapterCache: DiskCache, private val chapterCache: DiskCache,
private val bitmapDecoderFactory: BitmapDecoderFactory, private val bitmapDecoderFactory: BitmapDecoderFactory,
private val getChapterPages: GetChapterPages,
) : PageLoader() { ) : PageLoader() {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
@@ -109,15 +109,15 @@ class TachideskPageLoader(
} }
private suspend fun fetchImage(page: ReaderPage) { private suspend fun fetchImage(page: ReaderPage) {
log.debug { "Loading page ${page.index}" } log.debug { "Loading page ${page.index2}" }
flow { flow {
val response = http.get("api/v1/manga/${chapter.chapter.mangaId}/chapter/${chapter.chapter.index}/page/${page.index}") { val response = getChapterPages.asFlow(page.url) {
onDownload { bytesSentTotal, contentLength -> onDownload { bytesSentTotal, contentLength ->
page.progress.value = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(1.0F) page.progress.value = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(1.0F)
} }
} }
emit(response) emitAll(response)
} }
.onEach { .onEach {
putImageInCache(it, page) putImageInCache(it, page)
@@ -129,21 +129,21 @@ class TachideskPageLoader(
page.bitmap.value = StableHolder(null) page.bitmap.value = StableHolder(null)
page.status.value = ReaderPage.Status.ERROR page.status.value = ReaderPage.Status.ERROR
page.error.value = it.message page.error.value = it.message
log.warn(it) { "Failed to get page ${page.index} for chapter ${chapter.chapter.index} for ${chapter.chapter.mangaId}" } log.warn(it) { "Failed to get page ${page.index2} for chapter ${chapter.chapter.index} for ${chapter.chapter.mangaId}" }
} }
.flowOn(Dispatchers.IO) .flowOn(Dispatchers.IO)
.collect() .collect()
} }
private suspend fun putImageInCache( private suspend fun putImageInCache(
response: HttpResponse, response: ByteArray,
page: ReaderPage, page: ReaderPage,
) { ) {
val editor = chapterCache.openEditor(page.cacheKey) val editor = chapterCache.openEditor(page.cacheKey)
?: throw Exception("Couldn't open cache") ?: throw Exception("Couldn't open cache")
try { try {
FileSystem.SYSTEM.write(editor.data) { FileSystem.SYSTEM.write(editor.data) {
response.bodyAsChannel().toSource().use { response.source().use {
writeAll(it) writeAll(it)
} }
} }
@@ -192,7 +192,7 @@ class TachideskPageLoader(
currentPage: ReaderPage, currentPage: ReaderPage,
amount: Int, amount: Int,
): List<PriorityPage> { ): List<PriorityPage> {
val pageIndex = currentPage.index val pageIndex = currentPage.index2
val pages = (currentPage.chapter.pages.value as? PagesState.Success)?.pages ?: return emptyList() val pages = (currentPage.chapter.pages.value as? PagesState.Success)?.pages ?: return emptyList()
if (pageIndex >= pages.lastIndex) return emptyList() if (pageIndex >= pages.lastIndex) return emptyList()
@@ -214,14 +214,15 @@ class TachideskPageLoader(
override fun getPages(): StateFlow<PagesState> { override fun getPages(): StateFlow<PagesState> {
scope.launch { scope.launch {
if (pagesFlow.value != PagesState.Loading) return@launch if (pagesFlow.value != PagesState.Loading) return@launch
val pageRange = chapter.chapter.pageCount?.let { 0..it.minus(1) } val pages = getChapterPages.await(chapter.chapter.id)
pagesFlow.value = if (pageRange == null || pageRange.isEmpty()) { pagesFlow.value = if (pages.isNullOrEmpty()) {
PagesState.Empty PagesState.Empty
} else { } else {
PagesState.Success( PagesState.Success(
pageRange.map { pages.mapIndexed { index, url ->
ReaderPage( ReaderPage(
index = it, url = url,
index2 = index,
bitmap = MutableStateFlow(StableHolder(null)), bitmap = MutableStateFlow(StableHolder(null)),
bitmapInfo = MutableStateFlow(null), bitmapInfo = MutableStateFlow(null),
progress = MutableStateFlow(0.0F), progress = MutableStateFlow(0.0F),
@@ -295,7 +296,7 @@ class TachideskPageLoader(
} }
private val ReaderPage.cacheKey private val ReaderPage.cacheKey
get() = "${chapter.chapter.mangaId}-${chapter.chapter.index}-$index" get() = "${chapter.chapter.id}-$url"
private fun DiskCache.Snapshot.source(): BufferedSource = FileSystem.SYSTEM.source(data).buffer() private fun DiskCache.Snapshot.source(): BufferedSource = FileSystem.SYSTEM.source(data).buffer()

View File

@@ -14,7 +14,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
@Immutable @Immutable
data class ReaderPage( data class ReaderPage(
val index: Int, val url: String,
val index2: Int,
val bitmap: MutableStateFlow<StableHolder<(suspend () -> ImageDecodeState)?>>, val bitmap: MutableStateFlow<StableHolder<(suspend () -> ImageDecodeState)?>>,
val bitmapInfo: MutableStateFlow<BitmapInfo?>, val bitmapInfo: MutableStateFlow<BitmapInfo?>,
val progress: MutableStateFlow<Float>, val progress: MutableStateFlow<Float>,

View File

@@ -126,7 +126,7 @@ fun ContinuousReader(
} }
fun retry(index: Int) { fun retry(index: Int) {
pages.find { it is ReaderPage && it.index == index }?.let { retry(it as ReaderPage) } pages.find { it is ReaderPage && it.index2 == index }?.let { retry(it as ReaderPage) }
} }
when (direction.isVertical) { when (direction.isVertical) {
@@ -199,7 +199,7 @@ private fun LazyListScope.items(
pages, pages,
key = { key = {
when (it) { when (it) {
is ReaderPage -> it.chapter.chapter.index to it.index is ReaderPage -> it.chapter.chapter.index to it.index2
is ReaderPageSeparator -> it.previousChapter?.chapter?.index to it.nextChapter?.chapter?.index is ReaderPageSeparator -> it.previousChapter?.chapter?.index to it.nextChapter?.chapter?.index
} }
}, },
@@ -207,7 +207,7 @@ private fun LazyListScope.items(
when (image) { when (image) {
is ReaderPage -> Box(modifier, contentAlignment = Alignment.Center) { is ReaderPage -> Box(modifier, contentAlignment = Alignment.Center) {
ReaderImage( ReaderImage(
imageIndex = image.index, imageIndex = image.index2,
drawableHolder = image.bitmap.collectAsState().value, drawableHolder = image.bitmap.collectAsState().value,
bitmapInfo = image.bitmapInfo.collectAsState().value, bitmapInfo = image.bitmapInfo.collectAsState().value,
progress = image.progress.collectAsState().value, progress = image.progress.collectAsState().value,

View File

@@ -84,7 +84,7 @@ fun PagerReader(
val modifier = parentModifier then Modifier.fillMaxSize() val modifier = parentModifier then Modifier.fillMaxSize()
fun retry(index: Int) { fun retry(index: Int) {
pages.find { it is ReaderPage && it.index == index }?.let { retry(it as ReaderPage) } pages.find { it is ReaderPage && it.index2 == index }?.let { retry(it as ReaderPage) }
} }
if (direction.isVertical) { if (direction.isVertical) {
@@ -95,7 +95,7 @@ fun PagerReader(
modifier = modifier, modifier = modifier,
key = { key = {
when (val page = pages.getOrNull(it)) { when (val page = pages.getOrNull(it)) {
is ReaderPage -> page.chapter.chapter.index to page.index is ReaderPage -> page.chapter.chapter.index to page.index2
is ReaderPageSeparator -> page.previousChapter?.chapter?.index to page.nextChapter?.chapter?.index is ReaderPageSeparator -> page.previousChapter?.chapter?.index to page.nextChapter?.chapter?.index
else -> it else -> it
} }
@@ -118,7 +118,7 @@ fun PagerReader(
modifier = modifier, modifier = modifier,
key = { key = {
when (val page = pages.getOrNull(it)) { when (val page = pages.getOrNull(it)) {
is ReaderPage -> page.chapter.chapter.index to page.index is ReaderPage -> page.chapter.chapter.index to page.index2
is ReaderPageSeparator -> page.previousChapter?.chapter?.index to page.nextChapter?.chapter?.index is ReaderPageSeparator -> page.previousChapter?.chapter?.index to page.nextChapter?.chapter?.index
else -> it else -> it
} }
@@ -148,7 +148,7 @@ fun HandlePager(
when (val image = pages[page]) { when (val image = pages[page]) {
is ReaderPage -> { is ReaderPage -> {
ReaderImage( ReaderImage(
imageIndex = image.index, imageIndex = image.index2,
drawableHolder = image.bitmap.collectAsState().value, drawableHolder = image.bitmap.collectAsState().value,
bitmapInfo = image.bitmapInfo.collectAsState().value, bitmapInfo = image.bitmapInfo.collectAsState().value,
progress = image.progress.collectAsState().value, progress = image.progress.collectAsState().value,

View File

@@ -7,8 +7,8 @@
package ca.gosyer.jui.ui.updates package ca.gosyer.jui.ui.updates
import ca.gosyer.jui.core.lang.launchDefault import ca.gosyer.jui.core.lang.launchDefault
import ca.gosyer.jui.domain.chapter.interactor.BatchUpdateChapter
import ca.gosyer.jui.domain.chapter.interactor.DeleteChapterDownload import ca.gosyer.jui.domain.chapter.interactor.DeleteChapterDownload
import ca.gosyer.jui.domain.chapter.interactor.UpdateChapter
import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.download.interactor.BatchChapterDownload import ca.gosyer.jui.domain.download.interactor.BatchChapterDownload
import ca.gosyer.jui.domain.download.interactor.QueueChapterDownload import ca.gosyer.jui.domain.download.interactor.QueueChapterDownload
@@ -47,7 +47,7 @@ class UpdatesScreenViewModel
private val stopChapterDownload: StopChapterDownload, private val stopChapterDownload: StopChapterDownload,
private val deleteChapterDownload: DeleteChapterDownload, private val deleteChapterDownload: DeleteChapterDownload,
private val getRecentUpdates: GetRecentUpdates, private val getRecentUpdates: GetRecentUpdates,
private val batchUpdateChapter: BatchUpdateChapter, private val updateChapter: UpdateChapter,
private val batchChapterDownload: BatchChapterDownload, private val batchChapterDownload: BatchChapterDownload,
private val updateLibrary: UpdateLibrary, private val updateLibrary: UpdateLibrary,
private val updatesPager: UpdatesPager, private val updatesPager: UpdatesPager,
@@ -119,7 +119,7 @@ class UpdatesScreenViewModel
read: Boolean, read: Boolean,
) { ) {
scope.launch { scope.launch {
batchUpdateChapter.await(chapterIds, isRead = read, onError = { toast(it.message.orEmpty()) }) updateChapter.await(chapterIds, read = read, onError = { toast(it.message.orEmpty()) })
_selectedIds.value = persistentListOf() _selectedIds.value = persistentListOf()
} }
} }
@@ -133,7 +133,7 @@ class UpdatesScreenViewModel
bookmark: Boolean, bookmark: Boolean,
) { ) {
scope.launch { scope.launch {
batchUpdateChapter.await(chapterIds, isBookmarked = bookmark, onError = { toast(it.message.orEmpty()) }) updateChapter.await(chapterIds, bookmarked = bookmark, onError = { toast(it.message.orEmpty()) })
_selectedIds.value = persistentListOf() _selectedIds.value = persistentListOf()
} }
} }
@@ -158,7 +158,7 @@ class UpdatesScreenViewModel
scope.launchDefault { scope.launchDefault {
if (chapter == null) { if (chapter == null) {
val selectedIds = _selectedIds.value val selectedIds = _selectedIds.value
batchUpdateChapter.await(selectedIds, delete = true, onError = { toast(it.message.orEmpty()) }) deleteChapterDownload.await(selectedIds, onError = { toast(it.message.orEmpty()) })
selectedItems.value.forEach { selectedItems.value.forEach {
it.setNotDownloaded() it.setNotDownloaded()
} }

View File

@@ -77,7 +77,7 @@ fun UpdatesScreenContent(
inActionMode: Boolean, inActionMode: Boolean,
selectedItems: ImmutableList<ChapterDownloadItem>, selectedItems: ImmutableList<ChapterDownloadItem>,
loadNextPage: () -> Unit, loadNextPage: () -> Unit,
openChapter: (index: Int, mangaId: Long) -> Unit, openChapter: (id: Long, mangaId: Long) -> Unit,
openManga: (Long) -> Unit, openManga: (Long) -> Unit,
markRead: (Long?) -> Unit, markRead: (Long?) -> Unit,
markUnread: (Long?) -> Unit, markUnread: (Long?) -> Unit,
@@ -201,7 +201,7 @@ fun UpdatesScreenContent(
} }
} }
} else { } else {
{ openChapter(chapter.index, manga.id) } { openChapter(chapter.id, manga.id) }
}, },
markRead = markRead, markRead = markRead,
markUnread = markUnread, markUnread = markUnread,

View File

@@ -24,13 +24,13 @@ import ca.gosyer.jui.ui.util.lang.launchApplication
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
actual class ReaderLauncher { actual class ReaderLauncher {
private var isOpen by mutableStateOf<Pair<Int, Long>?>(null) private var isOpen by mutableStateOf<Pair<Long, Long>?>(null)
actual fun launch( actual fun launch(
chapterIndex: Int, chapterId: Long,
mangaId: Long, mangaId: Long,
) { ) {
isOpen = chapterIndex to mangaId isOpen = chapterId to mangaId
} }
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
@@ -38,7 +38,7 @@ actual class ReaderLauncher {
actual fun Reader() { actual fun Reader() {
val localParams = currentCompositionLocalContext val localParams = currentCompositionLocalContext
DisposableEffect(isOpen) { DisposableEffect(isOpen) {
isOpen?.let { (chapterIndex, mangaId) -> isOpen?.let { (chapterId, mangaId) ->
launchApplication { launchApplication {
val windowState = rememberWindowState( val windowState = rememberWindowState(
position = WindowPosition.Aligned(Alignment.Center), position = WindowPosition.Aligned(Alignment.Center),
@@ -52,7 +52,7 @@ actual class ReaderLauncher {
state = windowState, state = windowState,
) { ) {
ReaderMenu( ReaderMenu(
chapterIndex = chapterIndex, chapterId = chapterId,
mangaId = mangaId, mangaId = mangaId,
onCloseRequest = ::exitApplication, onCloseRequest = ::exitApplication,
) )

View File

@@ -14,14 +14,14 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
class ReaderScreen( class ReaderScreen(
val chapterIndex: Int, val chapterId: Long,
val mangaId: Long, val mangaId: Long,
) : Screen { ) : Screen {
@Composable @Composable
override fun Content() { override fun Content() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
ReaderMenu( ReaderMenu(
chapterIndex, chapterId,
mangaId, mangaId,
navigator::pop, navigator::pop,
) )
@@ -32,10 +32,10 @@ actual class ReaderLauncher(
private val navigator: Navigator?, private val navigator: Navigator?,
) { ) {
actual fun launch( actual fun launch(
chapterIndex: Int, chapterId: Long,
mangaId: Long, mangaId: Long,
) { ) {
navigator?.push(ReaderScreen(chapterIndex, mangaId)) navigator?.push(ReaderScreen(chapterId, mangaId))
} }
@Composable @Composable