diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index a58e85c1..258dedb2 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -6,9 +6,9 @@ object Config { // Tachidesk-Server version const val tachideskVersion = "v0.6.5" // Match this to the Tachidesk-Server commit count - const val serverCode = 1148 + const val serverCode = 1156 const val preview = true - const val previewCommit = "2195c3df765c3e1e435595d9edbec8ad3590bf46" + const val previewCommit = "67e09e2e1d452e041c46a334f1b473f38c5fc25b" val desktopJvmTarget = JavaVersion.VERSION_17 val androidJvmTarget = JavaVersion.VERSION_11 diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/interactor/BatchUpdateChapter.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/interactor/BatchUpdateChapter.kt new file mode 100644 index 00000000..def0731d --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/interactor/BatchUpdateChapter.kt @@ -0,0 +1,250 @@ +/* + * 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.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.ChapterRepository +import ca.gosyer.jui.domain.manga.model.Manga +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import me.tatarka.inject.annotations.Inject +import org.lighthousegames.logging.logging +import kotlin.jvm.JvmName + +class BatchUpdateChapter @Inject constructor(private val chapterRepository: ChapterRepository) { + + @JvmName("awaitChapters") + suspend fun await( + mangaId: Long, + chapters: List, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + isRead: Boolean? = null, + isBookmarked: Boolean? = null, + lastPageRead: Int? = null, + delete: Boolean? = null + ) = if (mangaId != null) { + chapterRepository.batchUpdateChapter( + mangaId, + MangaChapterBatchEditInput( + chapterIds = chapterIds, + change = ChapterChange( + isRead = isRead, + isBookmarked = isBookmarked, + lastPageRead = lastPageRead, + delete = delete + ) + ) + ) + } else { + chapterRepository.batchUpdateChapter( + ChapterBatchEditInput( + chapterIds = chapterIds, + change = ChapterChange( + isRead = isRead, + isBookmarked = isBookmarked, + lastPageRead = lastPageRead, + delete = delete + ) + ) + ) + } + + companion object { + private val log = logging() + } +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/model/ChapterBatchEditInput.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/model/ChapterBatchEditInput.kt new file mode 100644 index 00000000..90ec4227 --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/model/ChapterBatchEditInput.kt @@ -0,0 +1,15 @@ +/* + * 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.model + +import kotlinx.serialization.Serializable + +@Serializable +data class ChapterBatchEditInput( + val chapterIds: List? = null, + val change: ChapterChange? +) \ No newline at end of file diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/model/ChapterChange.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/model/ChapterChange.kt new file mode 100644 index 00000000..72453b0f --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/model/ChapterChange.kt @@ -0,0 +1,17 @@ +/* + * 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.model + +import kotlinx.serialization.Serializable + +@Serializable +data class ChapterChange( + val isRead: Boolean? = null, + val isBookmarked: Boolean? = null, + val lastPageRead: Int? = null, + val delete: Boolean? = null +) \ No newline at end of file diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/model/MangaChapterBatchEditInput.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/model/MangaChapterBatchEditInput.kt new file mode 100644 index 00000000..d0346c85 --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/model/MangaChapterBatchEditInput.kt @@ -0,0 +1,16 @@ +/* + * 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.model + +import kotlinx.serialization.Serializable + +@Serializable +data class MangaChapterBatchEditInput( + val chapterIds: List? = null, + val chapterIndexes: List? = null, + val change: ChapterChange? +) \ No newline at end of file diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/service/ChapterRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/service/ChapterRepository.kt index 6ccc639d..f88fc8b1 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/service/ChapterRepository.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/chapter/service/ChapterRepository.kt @@ -7,11 +7,16 @@ package ca.gosyer.jui.domain.chapter.service import ca.gosyer.jui.domain.chapter.model.Chapter +import ca.gosyer.jui.domain.chapter.model.ChapterBatchEditInput +import ca.gosyer.jui.domain.chapter.model.MangaChapterBatchEditInput +import de.jensklingenberg.ktorfit.http.Body import de.jensklingenberg.ktorfit.http.DELETE import de.jensklingenberg.ktorfit.http.Field import de.jensklingenberg.ktorfit.http.FormUrlEncoded import de.jensklingenberg.ktorfit.http.GET +import de.jensklingenberg.ktorfit.http.Headers import de.jensklingenberg.ktorfit.http.PATCH +import de.jensklingenberg.ktorfit.http.POST import de.jensklingenberg.ktorfit.http.Path import de.jensklingenberg.ktorfit.http.Query import de.jensklingenberg.ktorfit.http.ReqBuilder @@ -44,6 +49,19 @@ interface ChapterRepository { @Field("markPrevRead") markPreviousRead: Boolean? = null ): Flow + @POST("api/v1/manga/{mangaId}/chapter/batch") + @Headers("Content-Type: application/json") + fun batchUpdateChapter( + @Path("mangaId") mangaId: Long, + @Body input: MangaChapterBatchEditInput + ): Flow + + @POST("api/v1/chapter/batch") + @Headers("Content-Type: application/json") + fun batchUpdateChapter( + @Body input: ChapterBatchEditInput + ): Flow + @GET("api/v1/manga/{mangaId}/chapter/{chapterIndex}/page/{pageNum}") fun getPage( @Path("mangaId") mangaId: Long, diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/GetMangaFull.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/GetMangaFull.kt new file mode 100644 index 00000000..c129991b --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/GetMangaFull.kt @@ -0,0 +1,39 @@ +/* + * 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.manga.interactor + +import ca.gosyer.jui.domain.manga.model.Manga +import ca.gosyer.jui.domain.manga.service.MangaRepository +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.singleOrNull +import me.tatarka.inject.annotations.Inject +import org.lighthousegames.logging.logging + +class GetMangaFull @Inject constructor(private val mangaRepository: MangaRepository) { + + suspend fun await(mangaId: Long, onError: suspend (Throwable) -> Unit = {}) = asFlow(mangaId) + .catch { + onError(it) + log.warn(it) { "Failed to get full manga $mangaId" } + } + .singleOrNull() + + suspend fun await(manga: Manga, onError: suspend (Throwable) -> Unit = {}) = asFlow(manga) + .catch { + onError(it) + log.warn(it) { "Failed to get full manga ${manga.title}(${manga.id})" } + } + .singleOrNull() + + fun asFlow(mangaId: Long) = mangaRepository.getMangaFull(mangaId) + + fun asFlow(manga: Manga) = mangaRepository.getMangaFull(manga.id) + + companion object { + private val log = logging() + } +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/RefreshMangaFull.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/RefreshMangaFull.kt new file mode 100644 index 00000000..a3f720bb --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/RefreshMangaFull.kt @@ -0,0 +1,39 @@ +/* + * 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.manga.interactor + +import ca.gosyer.jui.domain.manga.model.Manga +import ca.gosyer.jui.domain.manga.service.MangaRepository +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.singleOrNull +import me.tatarka.inject.annotations.Inject +import org.lighthousegames.logging.logging + +class RefreshMangaFull @Inject constructor(private val mangaRepository: MangaRepository) { + + suspend fun await(mangaId: Long, onError: suspend (Throwable) -> Unit = {}) = asFlow(mangaId) + .catch { + onError(it) + log.warn(it) { "Failed to refresh full manga $mangaId" } + } + .singleOrNull() + + suspend fun await(manga: Manga, onError: suspend (Throwable) -> Unit = {}) = asFlow(manga) + .catch { + onError(it) + log.warn(it) { "Failed to refresh full manga ${manga.title}(${manga.id})" } + } + .singleOrNull() + + fun asFlow(mangaId: Long) = mangaRepository.getMangaFull(mangaId, true) + + fun asFlow(manga: Manga) = mangaRepository.getMangaFull(manga.id, true) + + companion object { + private val log = logging() + } +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/model/Manga.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/model/Manga.kt index db1c31f1..658f45bd 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/model/Manga.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/model/Manga.kt @@ -8,6 +8,7 @@ package ca.gosyer.jui.domain.manga.model import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable +import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.source.model.Source import ca.gosyer.jui.i18n.MR import dev.icerock.moko.resources.StringResource @@ -33,14 +34,15 @@ data class Manga( val freshData: Boolean, val meta: MangaMeta, val realUrl: String?, - val lastFetchedAt: Long? = 0, // todo remove default - val chaptersLastFetchedAt: Long? = 0, // todo remove default + val lastFetchedAt: Long?, + val chaptersLastFetchedAt: Long?, val inLibraryAt: Long, val unreadCount: Int?, val downloadCount: Int?, - val chapterCount: Int? = null, // todo remove default - val age: Long? = null, // todo remove default - val chaptersAge: Long? = null // todo remove default + val chapterCount: Int?, + var lastChapterRead: Chapter?, + val age: Long?, + val chaptersAge: Long? ) @Serializable diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/service/MangaRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/service/MangaRepository.kt index e3302fad..fac4661f 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/service/MangaRepository.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/service/MangaRepository.kt @@ -26,6 +26,12 @@ interface MangaRepository { @Query("onlineFetch") refresh: Boolean = false ): Flow + @GET("api/v1/manga/{mangaId}/full") + fun getMangaFull( + @Path("mangaId") mangaId: Long, + @Query("onlineFetch") refresh: Boolean = false + ): Flow + @GET("api/v1/manga/{mangaId}/thumbnail") fun getMangaThumbnail( @Path("mangaId") mangaId: Long, diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/chapter/ChapterDownloadButtons.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/chapter/ChapterDownloadButtons.kt index 32b7542f..69d7e0a1 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/chapter/ChapterDownloadButtons.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/chapter/ChapterDownloadButtons.kt @@ -87,6 +87,10 @@ data class ChapterDownloadItem( _downloadState.value = ChapterDownloadState.NotDownloaded } + fun setNotDownloaded() { + _downloadState.value = ChapterDownloadState.NotDownloaded + } + fun isSelected(selectedItems: List): Boolean { return (chapter.id in selectedItems).also { _isSelected.value = it } } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/MangaScreenViewModel.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/MangaScreenViewModel.kt index 4cde2732..3a973b9c 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/MangaScreenViewModel.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/MangaScreenViewModel.kt @@ -13,12 +13,11 @@ import ca.gosyer.jui.domain.category.interactor.GetCategories import ca.gosyer.jui.domain.category.interactor.GetMangaCategories import ca.gosyer.jui.domain.category.interactor.RemoveMangaFromCategory 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.GetChapters import ca.gosyer.jui.domain.chapter.interactor.RefreshChapters -import ca.gosyer.jui.domain.chapter.interactor.UpdateChapterBookmarked import ca.gosyer.jui.domain.chapter.interactor.UpdateChapterMarkPreviousRead -import ca.gosyer.jui.domain.chapter.interactor.UpdateChapterRead import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.download.interactor.BatchChapterDownload import ca.gosyer.jui.domain.download.interactor.QueueChapterDownload @@ -60,8 +59,7 @@ class MangaScreenViewModel @Inject constructor( private val refreshManga: RefreshManga, private val getChapters: GetChapters, private val refreshChapters: RefreshChapters, - private val updateChapterRead: UpdateChapterRead, - private val updateChapterBookmarked: UpdateChapterBookmarked, + private val batchUpdateChapter: BatchUpdateChapter, private val updateChapterMarkPreviousRead: UpdateChapterMarkPreviousRead, private val queueChapterDownload: QueueChapterDownload, private val stopChapterDownload: StopChapterDownload, @@ -229,35 +227,29 @@ class MangaScreenViewModel @Inject constructor( } } - private fun findChapter(index: Int) = chapters.value.find { it.chapter.index == index }?.chapter - - private fun setRead(index: Int, read: Boolean) { - val chapter = findChapter(index) ?: return - if (chapter.read == read) return + private fun setRead(chapterIds: List, read: Boolean) { scope.launch { manga.value?.let { manga -> - updateChapterRead.await(manga, index, read = read, onError = { toast(it.message.orEmpty()) }) + batchUpdateChapter.await(manga, chapterIds, isRead = read, onError = { toast(it.message.orEmpty()) }) refreshChaptersAsync(manga.id).await() - _selectedIds.value = _selectedIds.value.minus(chapter.id).toImmutableList() + _selectedIds.value = persistentListOf() } } } - fun markRead(index: Int) = setRead(index, true) - fun markUnread(index: Int) = setRead(index, false) + fun markRead(id: Long?) = setRead(listOfNotNull(id).ifEmpty { _selectedIds.value }, true) + fun markUnread(id: Long?) = setRead(listOfNotNull(id).ifEmpty { _selectedIds.value }, false) - private fun setBookmarked(index: Int, bookmark: Boolean) { - val chapter = findChapter(index) ?: return - if (chapter.bookmarked == bookmark) return + private fun setBookmarked(chapterIds: List, bookmark: Boolean) { scope.launch { manga.value?.let { manga -> - updateChapterBookmarked.await(manga, index, bookmarked = bookmark, onError = { toast(it.message.orEmpty()) }) + batchUpdateChapter.await(manga, chapterIds, isBookmarked = bookmark, onError = { toast(it.message.orEmpty()) }) refreshChaptersAsync(manga.id).await() - _selectedIds.value = _selectedIds.value.minus(chapter.id).toImmutableList() + _selectedIds.value = persistentListOf() } } } - fun bookmarkChapter(index: Int) = setBookmarked(index, true) - fun unBookmarkChapter(index: Int) = setBookmarked(index, false) + fun bookmarkChapter(id: Long?) = setBookmarked(listOfNotNull(id).ifEmpty { _selectedIds.value }, true) + fun unBookmarkChapter(id: Long?) = setBookmarked(listOfNotNull(id).ifEmpty { _selectedIds.value }, false) fun markPreviousRead(index: Int) { scope.launch { @@ -275,10 +267,20 @@ class MangaScreenViewModel @Inject constructor( } } - fun deleteDownload(index: Int) { + fun deleteDownload(id: Long?) { scope.launch { - chapters.value.find { it.chapter.index == index } - ?.deleteDownload(deleteChapterDownload) + if (id == null) { + val manga = _manga.value ?: return@launch + val chapterIds = _selectedIds.value + batchUpdateChapter.await(manga, chapterIds, delete = true, onError = { toast(it.message.orEmpty()) }) + chapterIds.forEach { id -> + chapters.value.find { it.chapter.id == id }?.setNotDownloaded() + } + _selectedIds.value = persistentListOf() + } else { + chapters.value.find { it.chapter.id == id } + ?.deleteDownload(deleteChapterDownload) + } } } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/ChapterItem.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/ChapterItem.kt index 792fe3d0..1e94dacf 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/ChapterItem.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/ChapterItem.kt @@ -59,14 +59,14 @@ fun ChapterItem( chapterDownload: ChapterDownloadItem, format: (Instant) -> String, onClick: (Int) -> Unit, - markRead: (Int) -> Unit, - markUnread: (Int) -> Unit, - bookmarkChapter: (Int) -> Unit, - unBookmarkChapter: (Int) -> Unit, + markRead: (Long) -> Unit, + markUnread: (Long) -> Unit, + bookmarkChapter: (Long) -> Unit, + unBookmarkChapter: (Long) -> Unit, markPreviousAsRead: (Int) -> Unit, onClickDownload: (Int) -> Unit, onClickStopDownload: (Int) -> Unit, - onClickDeleteChapter: (Int) -> Unit, + onClickDeleteChapter: (Long) -> Unit, onSelectChapter: (Int) -> Unit, onUnselectChapter: (Int) -> Unit ) { @@ -79,10 +79,10 @@ fun ChapterItem( .selectedBackground(isSelected) .chapterItemModifier( onClick = { onClick(chapter.index) }, - markRead = { markRead(chapter.index) }.takeUnless { chapter.read }, - markUnread = { markUnread(chapter.index) }.takeIf { chapter.read }, - bookmarkChapter = { bookmarkChapter(chapter.index) }.takeUnless { chapter.bookmarked }, - unBookmarkChapter = { unBookmarkChapter(chapter.index) }.takeIf { chapter.bookmarked }, + markRead = { markRead(chapter.id) }.takeUnless { chapter.read }, + markUnread = { markUnread(chapter.id) }.takeIf { chapter.read }, + bookmarkChapter = { bookmarkChapter(chapter.id) }.takeUnless { chapter.bookmarked }, + unBookmarkChapter = { unBookmarkChapter(chapter.id) }.takeIf { chapter.bookmarked }, markPreviousAsRead = { markPreviousAsRead(chapter.index) }, onSelectChapter = { onSelectChapter(chapter.index) }.takeUnless { chapterDownload.isSelected.value }, onUnselectChapter = { onUnselectChapter(chapter.index) }.takeIf { chapterDownload.isSelected.value } @@ -157,7 +157,7 @@ fun ChapterItem( chapterDownload, { onClickDownload(it.index) }, { onClickStopDownload(it.index) }, - { onClickDeleteChapter(it.index) } + { onClickDeleteChapter(it.id) } ) } } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/MangaScreenContent.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/MangaScreenContent.kt index b10b0a81..3a0b637a 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/MangaScreenContent.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/MangaScreenContent.kt @@ -97,13 +97,13 @@ fun MangaScreenContent( downloadNext: (Int) -> Unit, downloadUnread: () -> Unit, downloadAll: () -> Unit, - markRead: (Int) -> Unit, - markUnread: (Int) -> Unit, - bookmarkChapter: (Int) -> Unit, - unBookmarkChapter: (Int) -> Unit, + markRead: (Long?) -> Unit, + markUnread: (Long?) -> Unit, + bookmarkChapter: (Long?) -> Unit, + unBookmarkChapter: (Long?) -> Unit, markPreviousRead: (Int) -> Unit, downloadChapter: (Int) -> Unit, - deleteDownload: (Int) -> Unit, + deleteDownload: (Long?) -> Unit, stopDownloadingChapter: (Int) -> Unit, onSelectChapter: (Int) -> Unit, onUnselectChapter: (Int) -> Unit, @@ -178,12 +178,12 @@ fun MangaScreenContent( visible = inActionMode, items = getBottomActionItems( selectedItems = selectedItems, - markRead = markRead, - markUnread = markUnread, - bookmarkChapter = bookmarkChapter, - unBookmarkChapter = unBookmarkChapter, + markRead = { markRead(null) }, + markUnread = { markUnread(null) }, + bookmarkChapter = { bookmarkChapter(null) }, + unBookmarkChapter = { unBookmarkChapter(null) }, markPreviousAsRead = markPreviousRead, - deleteChapter = deleteDownload, + deleteChapter = { deleteDownload(null) }, downloadChapters = downloadChapters ) ) @@ -366,35 +366,35 @@ private fun getActionModeActionItems( @Stable private fun getBottomActionItems( selectedItems: ImmutableList, - markRead: (Int) -> Unit, - markUnread: (Int) -> Unit, - bookmarkChapter: (Int) -> Unit, - unBookmarkChapter: (Int) -> Unit, + markRead: () -> Unit, + markUnread: () -> Unit, + bookmarkChapter: () -> Unit, + unBookmarkChapter: () -> Unit, markPreviousAsRead: (Int) -> Unit, - deleteChapter: (Int) -> Unit, + deleteChapter: () -> Unit, downloadChapters: () -> Unit ): ImmutableList { return listOfNotNull( BottomActionItem( name = stringResource(MR.strings.action_bookmark), icon = Icons.Rounded.BookmarkAdd, - onClick = { bookmarkChapter(selectedItems.first().chapter.index) } - ).takeIf { selectedItems.fastAny { !it.chapter.bookmarked } && selectedItems.size == 1 }, + onClick = bookmarkChapter + ).takeIf { selectedItems.fastAny { !it.chapter.bookmarked } }, BottomActionItem( name = stringResource(MR.strings.action_remove_bookmark), icon = Icons.Rounded.BookmarkRemove, - onClick = { unBookmarkChapter(selectedItems.first().chapter.index) } - ).takeIf { selectedItems.fastAny { it.chapter.bookmarked } && selectedItems.size == 1 }, + onClick = unBookmarkChapter + ).takeIf { selectedItems.fastAny { it.chapter.bookmarked } }, BottomActionItem( name = stringResource(MR.strings.action_mark_as_read), icon = Icons.Rounded.DoneAll, - onClick = { markRead(selectedItems.first().chapter.index) } - ).takeIf { selectedItems.fastAny { !it.chapter.read } && selectedItems.size == 1 }, + onClick = markRead + ).takeIf { selectedItems.fastAny { !it.chapter.read } }, BottomActionItem( name = stringResource(MR.strings.action_mark_as_unread), icon = Icons.Rounded.RemoveDone, - onClick = { markUnread(selectedItems.first().chapter.index) } - ).takeIf { selectedItems.fastAny { it.chapter.read } && selectedItems.size == 1 }, + onClick = markUnread + ).takeIf { selectedItems.fastAny { it.chapter.read } }, BottomActionItem( name = stringResource(MR.strings.action_mark_previous_read), icon = JuiAssets.DonePrev, @@ -408,7 +408,7 @@ private fun getBottomActionItems( BottomActionItem( name = stringResource(MR.strings.action_delete), icon = Icons.Rounded.Delete, - onClick = { deleteChapter(selectedItems.first().chapter.index) } - ).takeIf { selectedItems.fastAny { it.downloadState.value == ChapterDownloadState.Downloaded } && selectedItems.size == 1 } + onClick = deleteChapter + ).takeIf { selectedItems.fastAny { it.downloadState.value == ChapterDownloadState.Downloaded } } ).toImmutableList() }