mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-21 12:02:33 +01:00
Rewrite Tachidesk interactions
- Fix crashing on android when things fail to load - Improve error handling
This commit is contained in:
@@ -7,6 +7,8 @@
|
|||||||
package ca.gosyer.data.models
|
package ca.gosyer.data.models
|
||||||
|
|
||||||
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -28,13 +30,15 @@ data class Chapter(
|
|||||||
val downloaded: Boolean,
|
val downloaded: Boolean,
|
||||||
val meta: ChapterMeta
|
val meta: ChapterMeta
|
||||||
) {
|
) {
|
||||||
suspend fun updateRemote(
|
fun updateRemote(
|
||||||
chapterHandler: ChapterInteractionHandler,
|
chapterHandler: ChapterInteractionHandler,
|
||||||
pageOffset: Int = meta.juiPageOffset
|
pageOffset: Int = meta.juiPageOffset
|
||||||
) {
|
) = flow {
|
||||||
if (pageOffset != meta.juiPageOffset) {
|
if (pageOffset != meta.juiPageOffset) {
|
||||||
chapterHandler.updateChapterMeta(this, "juiPageOffset", pageOffset.toString())
|
chapterHandler.updateChapterMeta(this@Chapter, "juiPageOffset", pageOffset.toString())
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
emit(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ package ca.gosyer.data.models
|
|||||||
import ca.gosyer.data.server.interactions.MangaInteractionHandler
|
import ca.gosyer.data.server.interactions.MangaInteractionHandler
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
@@ -37,10 +39,12 @@ data class Manga(
|
|||||||
suspend fun updateRemote(
|
suspend fun updateRemote(
|
||||||
mangaHandler: MangaInteractionHandler,
|
mangaHandler: MangaInteractionHandler,
|
||||||
readerMode: String = meta.juiReaderMode
|
readerMode: String = meta.juiReaderMode
|
||||||
) {
|
) = flow {
|
||||||
if (readerMode != meta.juiReaderMode) {
|
if (readerMode != meta.juiReaderMode) {
|
||||||
mangaHandler.updateMangaMeta(this, "juiReaderMode", readerMode)
|
mangaHandler.updateMangaMeta(this@Manga, "juiReaderMode", readerMode)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
emit(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.data.server.interactions
|
package ca.gosyer.data.server.interactions
|
||||||
|
|
||||||
import ca.gosyer.core.lang.withIOContext
|
|
||||||
import ca.gosyer.data.models.BackupValidationResult
|
import ca.gosyer.data.models.BackupValidationResult
|
||||||
import ca.gosyer.data.server.Http
|
import ca.gosyer.data.server.Http
|
||||||
import ca.gosyer.data.server.ServerPreferences
|
import ca.gosyer.data.server.ServerPreferences
|
||||||
@@ -21,6 +20,9 @@ import io.ktor.client.statement.HttpResponse
|
|||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
import io.ktor.http.Headers
|
import io.ktor.http.Headers
|
||||||
import io.ktor.http.HttpHeaders
|
import io.ktor.http.HttpHeaders
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
import okio.FileSystem
|
import okio.FileSystem
|
||||||
import okio.Path
|
import okio.Path
|
||||||
@@ -31,8 +33,8 @@ class BackupInteractionHandler @Inject constructor(
|
|||||||
serverPreferences: ServerPreferences
|
serverPreferences: ServerPreferences
|
||||||
) : BaseInteractionHandler(client, serverPreferences) {
|
) : BaseInteractionHandler(client, serverPreferences) {
|
||||||
|
|
||||||
suspend fun importBackupFile(file: Path, block: HttpRequestBuilder.() -> Unit = {}) = withIOContext {
|
fun importBackupFile(file: Path, block: HttpRequestBuilder.() -> Unit = {}) = flow {
|
||||||
client.submitFormWithBinaryData<HttpResponse>(
|
val response = client.submitFormWithBinaryData<HttpResponse>(
|
||||||
serverUrl + backupFileImportRequest(),
|
serverUrl + backupFileImportRequest(),
|
||||||
formData = formData {
|
formData = formData {
|
||||||
append(
|
append(
|
||||||
@@ -45,10 +47,11 @@ class BackupInteractionHandler @Inject constructor(
|
|||||||
},
|
},
|
||||||
block = block
|
block = block
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun validateBackupFile(file: Path, block: HttpRequestBuilder.() -> Unit = {}) = withIOContext {
|
fun validateBackupFile(file: Path, block: HttpRequestBuilder.() -> Unit = {}) = flow {
|
||||||
client.submitFormWithBinaryData<BackupValidationResult>(
|
val response = client.submitFormWithBinaryData<BackupValidationResult>(
|
||||||
serverUrl + validateBackupFileRequest(),
|
serverUrl + validateBackupFileRequest(),
|
||||||
formData = formData {
|
formData = formData {
|
||||||
append(
|
append(
|
||||||
@@ -61,12 +64,14 @@ class BackupInteractionHandler @Inject constructor(
|
|||||||
},
|
},
|
||||||
block = block
|
block = block
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun exportBackupFile(block: HttpRequestBuilder.() -> Unit = {}) = withIOContext {
|
fun exportBackupFile(block: HttpRequestBuilder.() -> Unit = {}) = flow {
|
||||||
client.get<HttpResponse>(
|
val response = client.get<HttpResponse>(
|
||||||
serverUrl + backupFileExportRequest(),
|
serverUrl + backupFileExportRequest(),
|
||||||
block
|
block
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.data.server.interactions
|
package ca.gosyer.data.server.interactions
|
||||||
|
|
||||||
import ca.gosyer.core.lang.withIOContext
|
|
||||||
import ca.gosyer.data.models.Category
|
import ca.gosyer.data.models.Category
|
||||||
import ca.gosyer.data.models.Manga
|
import ca.gosyer.data.models.Manga
|
||||||
import ca.gosyer.data.server.Http
|
import ca.gosyer.data.server.Http
|
||||||
@@ -26,6 +25,9 @@ import io.ktor.client.request.get
|
|||||||
import io.ktor.client.statement.HttpResponse
|
import io.ktor.client.statement.HttpResponse
|
||||||
import io.ktor.http.HttpMethod
|
import io.ktor.http.HttpMethod
|
||||||
import io.ktor.http.Parameters
|
import io.ktor.http.Parameters
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class CategoryInteractionHandler @Inject constructor(
|
class CategoryInteractionHandler @Inject constructor(
|
||||||
@@ -33,53 +35,58 @@ class CategoryInteractionHandler @Inject constructor(
|
|||||||
serverPreferences: ServerPreferences
|
serverPreferences: ServerPreferences
|
||||||
) : BaseInteractionHandler(client, serverPreferences) {
|
) : BaseInteractionHandler(client, serverPreferences) {
|
||||||
|
|
||||||
suspend fun getMangaCategories(mangaId: Long) = withIOContext {
|
fun getMangaCategories(mangaId: Long) = flow {
|
||||||
client.get<List<Category>>(
|
val response = client.get<List<Category>>(
|
||||||
serverUrl + getMangaCategoriesQuery(mangaId)
|
serverUrl + getMangaCategoriesQuery(mangaId)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getMangaCategories(manga: Manga) = getMangaCategories(manga.id)
|
fun getMangaCategories(manga: Manga) = getMangaCategories(manga.id)
|
||||||
|
|
||||||
suspend fun addMangaToCategory(mangaId: Long, categoryId: Long) = withIOContext {
|
fun addMangaToCategory(mangaId: Long, categoryId: Long) = flow {
|
||||||
client.get<HttpResponse>(
|
val response = client.get<HttpResponse>(
|
||||||
serverUrl + addMangaToCategoryQuery(mangaId, categoryId)
|
serverUrl + addMangaToCategoryQuery(mangaId, categoryId)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
suspend fun addMangaToCategory(manga: Manga, category: Category) = addMangaToCategory(manga.id, category.id)
|
}.flowOn(Dispatchers.IO)
|
||||||
suspend fun addMangaToCategory(manga: Manga, categoryId: Long) = addMangaToCategory(manga.id, categoryId)
|
fun addMangaToCategory(manga: Manga, category: Category) = addMangaToCategory(manga.id, category.id)
|
||||||
suspend fun addMangaToCategory(mangaId: Long, category: Category) = addMangaToCategory(mangaId, category.id)
|
fun addMangaToCategory(manga: Manga, categoryId: Long) = addMangaToCategory(manga.id, categoryId)
|
||||||
|
fun addMangaToCategory(mangaId: Long, category: Category) = addMangaToCategory(mangaId, category.id)
|
||||||
|
|
||||||
suspend fun removeMangaFromCategory(mangaId: Long, categoryId: Long) = withIOContext {
|
fun removeMangaFromCategory(mangaId: Long, categoryId: Long) = flow {
|
||||||
client.delete<HttpResponse>(
|
val response = client.delete<HttpResponse>(
|
||||||
serverUrl + removeMangaFromCategoryRequest(mangaId, categoryId)
|
serverUrl + removeMangaFromCategoryRequest(mangaId, categoryId)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
suspend fun removeMangaFromCategory(manga: Manga, category: Category) = removeMangaFromCategory(manga.id, category.id)
|
}.flowOn(Dispatchers.IO)
|
||||||
suspend fun removeMangaFromCategory(manga: Manga, categoryId: Long) = removeMangaFromCategory(manga.id, categoryId)
|
fun removeMangaFromCategory(manga: Manga, category: Category) = removeMangaFromCategory(manga.id, category.id)
|
||||||
suspend fun removeMangaFromCategory(mangaId: Long, category: Category) = removeMangaFromCategory(mangaId, category.id)
|
fun removeMangaFromCategory(manga: Manga, categoryId: Long) = removeMangaFromCategory(manga.id, categoryId)
|
||||||
|
fun removeMangaFromCategory(mangaId: Long, category: Category) = removeMangaFromCategory(mangaId, category.id)
|
||||||
|
|
||||||
suspend fun getCategories(dropDefault: Boolean = false) = withIOContext {
|
fun getCategories(dropDefault: Boolean = false) = flow {
|
||||||
client.get<List<Category>>(
|
val response = client.get<List<Category>>(
|
||||||
serverUrl + getCategoriesQuery()
|
serverUrl + getCategoriesQuery()
|
||||||
).let { categories ->
|
).let { categories ->
|
||||||
if (dropDefault) {
|
if (dropDefault) {
|
||||||
categories.filterNot { it.name.equals("default", true) }
|
categories.filterNot { it.name.equals("default", true) }
|
||||||
} else categories
|
} else categories
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun createCategory(name: String) = withIOContext {
|
fun createCategory(name: String) = flow {
|
||||||
client.submitForm<HttpResponse>(
|
val response = client.submitForm<HttpResponse>(
|
||||||
serverUrl + createCategoryRequest(),
|
serverUrl + createCategoryRequest(),
|
||||||
formParameters = Parameters.build {
|
formParameters = Parameters.build {
|
||||||
append("name", name)
|
append("name", name)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun modifyCategory(categoryId: Long, name: String? = null, isLanding: Boolean? = null) = withIOContext {
|
fun modifyCategory(categoryId: Long, name: String? = null, isLanding: Boolean? = null) = flow {
|
||||||
client.submitForm<HttpResponse>(
|
val response = client.submitForm<HttpResponse>(
|
||||||
serverUrl + categoryModifyRequest(categoryId),
|
serverUrl + categoryModifyRequest(categoryId),
|
||||||
formParameters = Parameters.build {
|
formParameters = Parameters.build {
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
@@ -92,11 +99,12 @@ class CategoryInteractionHandler @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
method = HttpMethod.Patch
|
method = HttpMethod.Patch
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
suspend fun modifyCategory(category: Category, name: String? = null, isLanding: Boolean? = null) = modifyCategory(category.id, name, isLanding)
|
}.flowOn(Dispatchers.IO)
|
||||||
|
fun modifyCategory(category: Category, name: String? = null, isLanding: Boolean? = null) = modifyCategory(category.id, name, isLanding)
|
||||||
|
|
||||||
suspend fun reorderCategory(to: Int, from: Int) = withIOContext {
|
fun reorderCategory(to: Int, from: Int) = flow {
|
||||||
client.submitForm<HttpResponse>(
|
val response = client.submitForm<HttpResponse>(
|
||||||
serverUrl + categoryReorderRequest(),
|
serverUrl + categoryReorderRequest(),
|
||||||
formParameters = Parameters.build {
|
formParameters = Parameters.build {
|
||||||
append("to", to.toString())
|
append("to", to.toString())
|
||||||
@@ -105,19 +113,22 @@ class CategoryInteractionHandler @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
method = HttpMethod.Patch
|
method = HttpMethod.Patch
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun deleteCategory(categoryId: Long) = withIOContext {
|
fun deleteCategory(categoryId: Long) = flow {
|
||||||
client.delete<HttpResponse>(
|
val response = client.delete<HttpResponse>(
|
||||||
serverUrl + categoryDeleteRequest(categoryId)
|
serverUrl + categoryDeleteRequest(categoryId)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
suspend fun deleteCategory(category: Category) = deleteCategory(category.id)
|
}.flowOn(Dispatchers.IO)
|
||||||
|
fun deleteCategory(category: Category) = deleteCategory(category.id)
|
||||||
|
|
||||||
suspend fun getMangaFromCategory(categoryId: Long) = withIOContext {
|
fun getMangaFromCategory(categoryId: Long) = flow {
|
||||||
client.get<List<Manga>>(
|
val response = client.get<List<Manga>>(
|
||||||
serverUrl + getMangaInCategoryQuery(categoryId)
|
serverUrl + getMangaInCategoryQuery(categoryId)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
suspend fun getMangaFromCategory(category: Category) = getMangaFromCategory(category.id)
|
}.flowOn(Dispatchers.IO)
|
||||||
|
fun getMangaFromCategory(category: Category) = getMangaFromCategory(category.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.data.server.interactions
|
package ca.gosyer.data.server.interactions
|
||||||
|
|
||||||
import ca.gosyer.core.lang.withIOContext
|
|
||||||
import ca.gosyer.data.models.Chapter
|
import ca.gosyer.data.models.Chapter
|
||||||
import ca.gosyer.data.models.Manga
|
import ca.gosyer.data.models.Manga
|
||||||
import ca.gosyer.data.server.Http
|
import ca.gosyer.data.server.Http
|
||||||
@@ -28,6 +27,9 @@ import io.ktor.client.statement.HttpResponse
|
|||||||
import io.ktor.http.HttpMethod
|
import io.ktor.http.HttpMethod
|
||||||
import io.ktor.http.Parameters
|
import io.ktor.http.Parameters
|
||||||
import io.ktor.utils.io.ByteReadChannel
|
import io.ktor.utils.io.ByteReadChannel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class ChapterInteractionHandler @Inject constructor(
|
class ChapterInteractionHandler @Inject constructor(
|
||||||
@@ -35,8 +37,8 @@ class ChapterInteractionHandler @Inject constructor(
|
|||||||
serverPreferences: ServerPreferences
|
serverPreferences: ServerPreferences
|
||||||
) : BaseInteractionHandler(client, serverPreferences) {
|
) : BaseInteractionHandler(client, serverPreferences) {
|
||||||
|
|
||||||
suspend fun getChapters(mangaId: Long, refresh: Boolean = false) = withIOContext {
|
fun getChapters(mangaId: Long, refresh: Boolean = false) = flow {
|
||||||
client.get<List<Chapter>>(
|
val response = client.get<List<Chapter>>(
|
||||||
serverUrl + getMangaChaptersQuery(mangaId)
|
serverUrl + getMangaChaptersQuery(mangaId)
|
||||||
) {
|
) {
|
||||||
url {
|
url {
|
||||||
@@ -45,31 +47,33 @@ class ChapterInteractionHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getChapters(manga: Manga, refresh: Boolean = false) = getChapters(manga.id, refresh)
|
fun getChapters(manga: Manga, refresh: Boolean = false) = getChapters(manga.id, refresh)
|
||||||
|
|
||||||
suspend fun getChapter(mangaId: Long, chapterIndex: Int) = withIOContext {
|
fun getChapter(mangaId: Long, chapterIndex: Int) = flow {
|
||||||
client.get<Chapter>(
|
val response = client.get<Chapter>(
|
||||||
serverUrl + getChapterQuery(mangaId, chapterIndex)
|
serverUrl + getChapterQuery(mangaId, chapterIndex)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getChapter(chapter: Chapter) = getChapter(chapter.mangaId, chapter.index)
|
fun getChapter(chapter: Chapter) = getChapter(chapter.mangaId, chapter.index)
|
||||||
|
|
||||||
suspend fun getChapter(manga: Manga, chapterIndex: Int) = getChapter(manga.id, chapterIndex)
|
fun getChapter(manga: Manga, chapterIndex: Int) = getChapter(manga.id, chapterIndex)
|
||||||
|
|
||||||
suspend fun getChapter(manga: Manga, chapter: Chapter) = getChapter(manga.id, chapter.index)
|
fun getChapter(manga: Manga, chapter: Chapter) = getChapter(manga.id, chapter.index)
|
||||||
|
|
||||||
suspend fun updateChapter(
|
fun updateChapter(
|
||||||
mangaId: Long,
|
mangaId: Long,
|
||||||
chapterIndex: Int,
|
chapterIndex: Int,
|
||||||
read: Boolean? = null,
|
read: Boolean? = null,
|
||||||
bookmarked: Boolean? = null,
|
bookmarked: Boolean? = null,
|
||||||
lastPageRead: Int? = null,
|
lastPageRead: Int? = null,
|
||||||
markPreviousRead: Boolean? = null
|
markPreviousRead: Boolean? = null
|
||||||
) = withIOContext {
|
) = flow {
|
||||||
client.submitForm<HttpResponse>(
|
val response = client.submitForm<HttpResponse>(
|
||||||
serverUrl + updateChapterRequest(mangaId, chapterIndex),
|
serverUrl + updateChapterRequest(mangaId, chapterIndex),
|
||||||
formParameters = Parameters.build {
|
formParameters = Parameters.build {
|
||||||
if (read != null) {
|
if (read != null) {
|
||||||
@@ -88,9 +92,10 @@ class ChapterInteractionHandler @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
method = HttpMethod.Patch
|
method = HttpMethod.Patch
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun updateChapter(
|
fun updateChapter(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
chapterIndex: Int,
|
chapterIndex: Int,
|
||||||
read: Boolean? = null,
|
read: Boolean? = null,
|
||||||
@@ -106,7 +111,7 @@ class ChapterInteractionHandler @Inject constructor(
|
|||||||
markPreviousRead
|
markPreviousRead
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun updateChapter(
|
fun updateChapter(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
chapter: Chapter,
|
chapter: Chapter,
|
||||||
read: Boolean? = null,
|
read: Boolean? = null,
|
||||||
@@ -122,57 +127,61 @@ class ChapterInteractionHandler @Inject constructor(
|
|||||||
markPreviousRead
|
markPreviousRead
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun getPage(mangaId: Long, chapterIndex: Int, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = withIOContext {
|
fun getPage(mangaId: Long, chapterIndex: Int, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = flow {
|
||||||
client.get<ByteReadChannel>(
|
val response = client.get<ByteReadChannel>(
|
||||||
serverUrl + getPageQuery(mangaId, chapterIndex, pageNum),
|
serverUrl + getPageQuery(mangaId, chapterIndex, pageNum),
|
||||||
block
|
block
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getPage(chapter: Chapter, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = getPage(chapter.mangaId, chapter.index, pageNum, block)
|
fun getPage(chapter: Chapter, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = getPage(chapter.mangaId, chapter.index, pageNum, block)
|
||||||
|
|
||||||
suspend fun getPage(manga: Manga, chapterIndex: Int, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = getPage(manga.id, chapterIndex, pageNum, block)
|
fun getPage(manga: Manga, chapterIndex: Int, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = getPage(manga.id, chapterIndex, pageNum, block)
|
||||||
|
|
||||||
suspend fun getPage(manga: Manga, chapter: Chapter, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = getPage(manga.id, chapter.index, pageNum, block)
|
fun getPage(manga: Manga, chapter: Chapter, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = getPage(manga.id, chapter.index, pageNum, block)
|
||||||
|
|
||||||
suspend fun deleteChapterDownload(mangaId: Long, chapterIndex: Int) = withIOContext {
|
fun deleteChapterDownload(mangaId: Long, chapterIndex: Int) = flow {
|
||||||
client.delete<HttpResponse>(
|
val response = client.delete<HttpResponse>(
|
||||||
serverUrl + deleteDownloadedChapterRequest(mangaId, chapterIndex)
|
serverUrl + deleteDownloadedChapterRequest(mangaId, chapterIndex)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun deleteChapterDownload(chapter: Chapter) = deleteChapterDownload(chapter.mangaId, chapter.index)
|
fun deleteChapterDownload(chapter: Chapter) = deleteChapterDownload(chapter.mangaId, chapter.index)
|
||||||
|
|
||||||
suspend fun deleteChapterDownload(manga: Manga, chapterIndex: Int) = deleteChapterDownload(manga.id, chapterIndex)
|
fun deleteChapterDownload(manga: Manga, chapterIndex: Int) = deleteChapterDownload(manga.id, chapterIndex)
|
||||||
|
|
||||||
suspend fun deleteChapterDownload(manga: Manga, chapter: Chapter) = deleteChapterDownload(manga.id, chapter.index)
|
fun deleteChapterDownload(manga: Manga, chapter: Chapter) = deleteChapterDownload(manga.id, chapter.index)
|
||||||
|
|
||||||
suspend fun queueChapterDownload(mangaId: Long, chapterIndex: Int) = withIOContext {
|
fun queueChapterDownload(mangaId: Long, chapterIndex: Int) = flow {
|
||||||
client.get<HttpResponse>(
|
val response = client.get<HttpResponse>(
|
||||||
serverUrl + queueDownloadChapterRequest(mangaId, chapterIndex)
|
serverUrl + queueDownloadChapterRequest(mangaId, chapterIndex)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun queueChapterDownload(chapter: Chapter) = queueChapterDownload(chapter.mangaId, chapter.index)
|
fun queueChapterDownload(chapter: Chapter) = queueChapterDownload(chapter.mangaId, chapter.index)
|
||||||
|
|
||||||
suspend fun queueChapterDownload(manga: Manga, chapterIndex: Int) = queueChapterDownload(manga.id, chapterIndex)
|
fun queueChapterDownload(manga: Manga, chapterIndex: Int) = queueChapterDownload(manga.id, chapterIndex)
|
||||||
|
|
||||||
suspend fun queueChapterDownload(manga: Manga, chapter: Chapter) = queueChapterDownload(manga.id, chapter.index)
|
fun queueChapterDownload(manga: Manga, chapter: Chapter) = queueChapterDownload(manga.id, chapter.index)
|
||||||
|
|
||||||
suspend fun stopChapterDownload(mangaId: Long, chapterIndex: Int) = withIOContext {
|
fun stopChapterDownload(mangaId: Long, chapterIndex: Int) = flow {
|
||||||
client.delete<HttpResponse>(
|
val response = client.delete<HttpResponse>(
|
||||||
serverUrl + stopDownloadingChapterRequest(mangaId, chapterIndex)
|
serverUrl + stopDownloadingChapterRequest(mangaId, chapterIndex)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun stopChapterDownload(chapter: Chapter) = stopChapterDownload(chapter.mangaId, chapter.index)
|
fun stopChapterDownload(chapter: Chapter) = stopChapterDownload(chapter.mangaId, chapter.index)
|
||||||
|
|
||||||
suspend fun stopChapterDownload(manga: Manga, chapterIndex: Int) = stopChapterDownload(manga.id, chapterIndex)
|
fun stopChapterDownload(manga: Manga, chapterIndex: Int) = stopChapterDownload(manga.id, chapterIndex)
|
||||||
|
|
||||||
suspend fun stopChapterDownload(manga: Manga, chapter: Chapter) = stopChapterDownload(manga.id, chapter.index)
|
fun stopChapterDownload(manga: Manga, chapter: Chapter) = stopChapterDownload(manga.id, chapter.index)
|
||||||
|
|
||||||
suspend fun updateChapterMeta(mangaId: Long, chapterIndex: Int, key: String, value: String) = withIOContext {
|
fun updateChapterMeta(mangaId: Long, chapterIndex: Int, key: String, value: String) = flow {
|
||||||
client.submitForm<HttpResponse>(
|
val response = client.submitForm<HttpResponse>(
|
||||||
serverUrl + updateChapterMetaRequest(mangaId, chapterIndex),
|
serverUrl + updateChapterMetaRequest(mangaId, chapterIndex),
|
||||||
formParameters = Parameters.build {
|
formParameters = Parameters.build {
|
||||||
append("key", key)
|
append("key", key)
|
||||||
@@ -181,11 +190,12 @@ class ChapterInteractionHandler @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
method = HttpMethod.Patch
|
method = HttpMethod.Patch
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun updateChapterMeta(chapter: Chapter, key: String, value: String) = updateChapterMeta(chapter.mangaId, chapter.index, key, value)
|
fun updateChapterMeta(chapter: Chapter, key: String, value: String) = updateChapterMeta(chapter.mangaId, chapter.index, key, value)
|
||||||
|
|
||||||
suspend fun updateChapterMeta(manga: Manga, chapterIndex: Int, key: String, value: String) = updateChapterMeta(manga.id, chapterIndex, key, value)
|
fun updateChapterMeta(manga: Manga, chapterIndex: Int, key: String, value: String) = updateChapterMeta(manga.id, chapterIndex, key, value)
|
||||||
|
|
||||||
suspend fun updateChapterMeta(manga: Manga, chapter: Chapter, key: String, value: String) = updateChapterMeta(manga.id, chapter.index, key, value)
|
fun updateChapterMeta(manga: Manga, chapter: Chapter, key: String, value: String) = updateChapterMeta(manga.id, chapter.index, key, value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.data.server.interactions
|
package ca.gosyer.data.server.interactions
|
||||||
|
|
||||||
import ca.gosyer.core.lang.withIOContext
|
|
||||||
import ca.gosyer.data.server.Http
|
import ca.gosyer.data.server.Http
|
||||||
import ca.gosyer.data.server.ServerPreferences
|
import ca.gosyer.data.server.ServerPreferences
|
||||||
import ca.gosyer.data.server.requests.downloadsClearRequest
|
import ca.gosyer.data.server.requests.downloadsClearRequest
|
||||||
@@ -14,6 +13,9 @@ import ca.gosyer.data.server.requests.downloadsStartRequest
|
|||||||
import ca.gosyer.data.server.requests.downloadsStopRequest
|
import ca.gosyer.data.server.requests.downloadsStopRequest
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.HttpResponse
|
import io.ktor.client.statement.HttpResponse
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class DownloadInteractionHandler @Inject constructor(
|
class DownloadInteractionHandler @Inject constructor(
|
||||||
@@ -21,21 +23,24 @@ class DownloadInteractionHandler @Inject constructor(
|
|||||||
serverPreferences: ServerPreferences
|
serverPreferences: ServerPreferences
|
||||||
) : BaseInteractionHandler(client, serverPreferences) {
|
) : BaseInteractionHandler(client, serverPreferences) {
|
||||||
|
|
||||||
suspend fun startDownloading() = withIOContext {
|
fun startDownloading() = flow {
|
||||||
client.get<HttpResponse>(
|
val response = client.get<HttpResponse>(
|
||||||
serverUrl + downloadsStartRequest()
|
serverUrl + downloadsStartRequest()
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun stopDownloading() = withIOContext {
|
fun stopDownloading() = flow {
|
||||||
client.get<HttpResponse>(
|
val response = client.get<HttpResponse>(
|
||||||
serverUrl + downloadsStopRequest()
|
serverUrl + downloadsStopRequest()
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun clearDownloadQueue() = withIOContext {
|
fun clearDownloadQueue() = flow {
|
||||||
client.get<HttpResponse>(
|
val response = client.get<HttpResponse>(
|
||||||
serverUrl + downloadsClearRequest()
|
serverUrl + downloadsClearRequest()
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.data.server.interactions
|
package ca.gosyer.data.server.interactions
|
||||||
|
|
||||||
import ca.gosyer.core.lang.withIOContext
|
|
||||||
import ca.gosyer.data.models.Extension
|
import ca.gosyer.data.models.Extension
|
||||||
import ca.gosyer.data.server.Http
|
import ca.gosyer.data.server.Http
|
||||||
import ca.gosyer.data.server.ServerPreferences
|
import ca.gosyer.data.server.ServerPreferences
|
||||||
@@ -19,6 +18,9 @@ import io.ktor.client.request.HttpRequestBuilder
|
|||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.HttpResponse
|
import io.ktor.client.statement.HttpResponse
|
||||||
import io.ktor.utils.io.ByteReadChannel
|
import io.ktor.utils.io.ByteReadChannel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class ExtensionInteractionHandler @Inject constructor(
|
class ExtensionInteractionHandler @Inject constructor(
|
||||||
@@ -26,34 +28,39 @@ class ExtensionInteractionHandler @Inject constructor(
|
|||||||
serverPreferences: ServerPreferences
|
serverPreferences: ServerPreferences
|
||||||
) : BaseInteractionHandler(client, serverPreferences) {
|
) : BaseInteractionHandler(client, serverPreferences) {
|
||||||
|
|
||||||
suspend fun getExtensionList() = withIOContext {
|
fun getExtensionList() = flow {
|
||||||
client.get<List<Extension>>(
|
val response = client.get<List<Extension>>(
|
||||||
serverUrl + extensionListQuery()
|
serverUrl + extensionListQuery()
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun installExtension(extension: Extension) = withIOContext {
|
fun installExtension(extension: Extension) = flow {
|
||||||
client.get<HttpResponse>(
|
val response = client.get<HttpResponse>(
|
||||||
serverUrl + apkInstallQuery(extension.pkgName)
|
serverUrl + apkInstallQuery(extension.pkgName)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun updateExtension(extension: Extension) = withIOContext {
|
fun updateExtension(extension: Extension) = flow {
|
||||||
client.get<HttpResponse>(
|
val response = client.get<HttpResponse>(
|
||||||
serverUrl + apkUpdateQuery(extension.pkgName)
|
serverUrl + apkUpdateQuery(extension.pkgName)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun uninstallExtension(extension: Extension) = withIOContext {
|
fun uninstallExtension(extension: Extension) = flow {
|
||||||
client.get<HttpResponse>(
|
val response = client.get<HttpResponse>(
|
||||||
serverUrl + apkUninstallQuery(extension.pkgName)
|
serverUrl + apkUninstallQuery(extension.pkgName)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getApkIcon(extension: Extension, block: HttpRequestBuilder.() -> Unit) = withIOContext {
|
fun getApkIcon(extension: Extension, block: HttpRequestBuilder.() -> Unit) = flow {
|
||||||
client.get<ByteReadChannel>(
|
val response = client.get<ByteReadChannel>(
|
||||||
serverUrl + apkIconQuery(extension.apkName),
|
serverUrl + apkIconQuery(extension.apkName),
|
||||||
block
|
block
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.data.server.interactions
|
package ca.gosyer.data.server.interactions
|
||||||
|
|
||||||
import ca.gosyer.core.lang.withIOContext
|
|
||||||
import ca.gosyer.data.models.Manga
|
import ca.gosyer.data.models.Manga
|
||||||
import ca.gosyer.data.server.Http
|
import ca.gosyer.data.server.Http
|
||||||
import ca.gosyer.data.server.ServerPreferences
|
import ca.gosyer.data.server.ServerPreferences
|
||||||
@@ -15,6 +14,9 @@ import ca.gosyer.data.server.requests.removeMangaFromLibraryRequest
|
|||||||
import io.ktor.client.request.delete
|
import io.ktor.client.request.delete
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.HttpResponse
|
import io.ktor.client.statement.HttpResponse
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class LibraryInteractionHandler @Inject constructor(
|
class LibraryInteractionHandler @Inject constructor(
|
||||||
@@ -22,19 +24,21 @@ class LibraryInteractionHandler @Inject constructor(
|
|||||||
serverPreferences: ServerPreferences
|
serverPreferences: ServerPreferences
|
||||||
) : BaseInteractionHandler(client, serverPreferences) {
|
) : BaseInteractionHandler(client, serverPreferences) {
|
||||||
|
|
||||||
suspend fun addMangaToLibrary(mangaId: Long) = withIOContext {
|
fun addMangaToLibrary(mangaId: Long) = flow {
|
||||||
client.get<HttpResponse>(
|
val response = client.get<HttpResponse>(
|
||||||
serverUrl + addMangaToLibraryQuery(mangaId)
|
serverUrl + addMangaToLibraryQuery(mangaId)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun addMangaToLibrary(manga: Manga) = addMangaToLibrary(manga.id)
|
fun addMangaToLibrary(manga: Manga) = addMangaToLibrary(manga.id)
|
||||||
|
|
||||||
suspend fun removeMangaFromLibrary(mangaId: Long) = withIOContext {
|
fun removeMangaFromLibrary(mangaId: Long) = flow {
|
||||||
client.delete<HttpResponse>(
|
val response = client.delete<HttpResponse>(
|
||||||
serverUrl + removeMangaFromLibraryRequest(mangaId)
|
serverUrl + removeMangaFromLibraryRequest(mangaId)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun removeMangaFromLibrary(manga: Manga) = removeMangaFromLibrary(manga.id)
|
fun removeMangaFromLibrary(manga: Manga) = removeMangaFromLibrary(manga.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.data.server.interactions
|
package ca.gosyer.data.server.interactions
|
||||||
|
|
||||||
import ca.gosyer.core.lang.withIOContext
|
|
||||||
import ca.gosyer.data.models.Manga
|
import ca.gosyer.data.models.Manga
|
||||||
import ca.gosyer.data.server.Http
|
import ca.gosyer.data.server.Http
|
||||||
import ca.gosyer.data.server.ServerPreferences
|
import ca.gosyer.data.server.ServerPreferences
|
||||||
@@ -21,6 +20,9 @@ import io.ktor.client.statement.HttpResponse
|
|||||||
import io.ktor.http.HttpMethod
|
import io.ktor.http.HttpMethod
|
||||||
import io.ktor.http.Parameters
|
import io.ktor.http.Parameters
|
||||||
import io.ktor.utils.io.ByteReadChannel
|
import io.ktor.utils.io.ByteReadChannel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class MangaInteractionHandler @Inject constructor(
|
class MangaInteractionHandler @Inject constructor(
|
||||||
@@ -28,8 +30,8 @@ class MangaInteractionHandler @Inject constructor(
|
|||||||
serverPreferences: ServerPreferences
|
serverPreferences: ServerPreferences
|
||||||
) : BaseInteractionHandler(client, serverPreferences) {
|
) : BaseInteractionHandler(client, serverPreferences) {
|
||||||
|
|
||||||
suspend fun getManga(mangaId: Long, refresh: Boolean = false) = withIOContext {
|
fun getManga(mangaId: Long, refresh: Boolean = false) = flow {
|
||||||
client.get<Manga>(
|
val response = client.get<Manga>(
|
||||||
serverUrl + mangaQuery(mangaId)
|
serverUrl + mangaQuery(mangaId)
|
||||||
) {
|
) {
|
||||||
url {
|
url {
|
||||||
@@ -38,19 +40,21 @@ class MangaInteractionHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getManga(manga: Manga, refresh: Boolean = false) = getManga(manga.id, refresh)
|
fun getManga(manga: Manga, refresh: Boolean = false) = getManga(manga.id, refresh)
|
||||||
|
|
||||||
suspend fun getMangaThumbnail(mangaId: Long, block: HttpRequestBuilder.() -> Unit) = withIOContext {
|
fun getMangaThumbnail(mangaId: Long, block: HttpRequestBuilder.() -> Unit) = flow {
|
||||||
client.get<ByteReadChannel>(
|
val response = client.get<ByteReadChannel>(
|
||||||
serverUrl + mangaThumbnailQuery(mangaId),
|
serverUrl + mangaThumbnailQuery(mangaId),
|
||||||
block
|
block
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun updateMangaMeta(mangaId: Long, key: String, value: String) = withIOContext {
|
fun updateMangaMeta(mangaId: Long, key: String, value: String) = flow {
|
||||||
client.submitForm<HttpResponse>(
|
val response = client.submitForm<HttpResponse>(
|
||||||
serverUrl + updateMangaMetaRequest(mangaId),
|
serverUrl + updateMangaMetaRequest(mangaId),
|
||||||
formParameters = Parameters.build {
|
formParameters = Parameters.build {
|
||||||
append("key", key)
|
append("key", key)
|
||||||
@@ -59,7 +63,8 @@ class MangaInteractionHandler @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
method = HttpMethod.Patch
|
method = HttpMethod.Patch
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun updateMangaMeta(manga: Manga, key: String, value: String) = updateMangaMeta(manga.id, key, value)
|
fun updateMangaMeta(manga: Manga, key: String, value: String) = updateMangaMeta(manga.id, key, value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.data.server.interactions
|
package ca.gosyer.data.server.interactions
|
||||||
|
|
||||||
import ca.gosyer.core.lang.withIOContext
|
|
||||||
import ca.gosyer.data.models.MangaPage
|
import ca.gosyer.data.models.MangaPage
|
||||||
import ca.gosyer.data.models.Source
|
import ca.gosyer.data.models.Source
|
||||||
import ca.gosyer.data.models.sourcefilters.SourceFilter
|
import ca.gosyer.data.models.sourcefilters.SourceFilter
|
||||||
@@ -31,6 +30,9 @@ import io.ktor.client.request.post
|
|||||||
import io.ktor.client.statement.HttpResponse
|
import io.ktor.client.statement.HttpResponse
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
import io.ktor.http.contentType
|
import io.ktor.http.contentType
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
@@ -40,45 +42,49 @@ class SourceInteractionHandler @Inject constructor(
|
|||||||
serverPreferences: ServerPreferences
|
serverPreferences: ServerPreferences
|
||||||
) : BaseInteractionHandler(client, serverPreferences) {
|
) : BaseInteractionHandler(client, serverPreferences) {
|
||||||
|
|
||||||
suspend fun getSourceList() = withIOContext {
|
fun getSourceList() = flow {
|
||||||
client.get<List<Source>>(
|
val response = client.get<List<Source>>(
|
||||||
serverUrl + sourceListQuery()
|
serverUrl + sourceListQuery()
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getSourceInfo(sourceId: Long) = withIOContext {
|
fun getSourceInfo(sourceId: Long) = flow {
|
||||||
client.get<Source>(
|
val response = client.get<Source>(
|
||||||
serverUrl + sourceInfoQuery(sourceId)
|
serverUrl + sourceInfoQuery(sourceId)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getSourceInfo(source: Source) = getSourceInfo(source.id)
|
fun getSourceInfo(source: Source) = getSourceInfo(source.id)
|
||||||
|
|
||||||
suspend fun getPopularManga(sourceId: Long, pageNum: Int) = withIOContext {
|
fun getPopularManga(sourceId: Long, pageNum: Int) = flow {
|
||||||
client.get<MangaPage>(
|
val response = client.get<MangaPage>(
|
||||||
serverUrl + sourcePopularQuery(sourceId, pageNum)
|
serverUrl + sourcePopularQuery(sourceId, pageNum)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getPopularManga(source: Source, pageNum: Int) = getPopularManga(
|
fun getPopularManga(source: Source, pageNum: Int) = getPopularManga(
|
||||||
source.id,
|
source.id,
|
||||||
pageNum
|
pageNum
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun getLatestManga(sourceId: Long, pageNum: Int) = withIOContext {
|
fun getLatestManga(sourceId: Long, pageNum: Int) = flow {
|
||||||
client.get<MangaPage>(
|
val response = client.get<MangaPage>(
|
||||||
serverUrl + sourceLatestQuery(sourceId, pageNum)
|
serverUrl + sourceLatestQuery(sourceId, pageNum)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getLatestManga(source: Source, pageNum: Int) = getLatestManga(
|
fun getLatestManga(source: Source, pageNum: Int) = getLatestManga(
|
||||||
source.id,
|
source.id,
|
||||||
pageNum
|
pageNum
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: 2021-03-14
|
// TODO: 2021-03-14
|
||||||
suspend fun getGlobalSearchResults(searchTerm: String) = withIOContext {
|
fun getGlobalSearchResults(searchTerm: String) = flow {
|
||||||
client.get<HttpResponse>(
|
val response = client.get<HttpResponse>(
|
||||||
serverUrl + globalSearchQuery()
|
serverUrl + globalSearchQuery()
|
||||||
) {
|
) {
|
||||||
url {
|
url {
|
||||||
@@ -87,10 +93,11 @@ class SourceInteractionHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getSearchResults(sourceId: Long, searchTerm: String, pageNum: Int) = withIOContext {
|
fun getSearchResults(sourceId: Long, searchTerm: String, pageNum: Int) = flow {
|
||||||
client.get<MangaPage>(
|
val response = client.get<MangaPage>(
|
||||||
serverUrl + sourceSearchQuery(sourceId)
|
serverUrl + sourceSearchQuery(sourceId)
|
||||||
) {
|
) {
|
||||||
url {
|
url {
|
||||||
@@ -100,16 +107,17 @@ class SourceInteractionHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getSearchResults(source: Source, searchTerm: String, pageNum: Int) = getSearchResults(
|
fun getSearchResults(source: Source, searchTerm: String, pageNum: Int) = getSearchResults(
|
||||||
source.id,
|
source.id,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
pageNum
|
pageNum
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun getFilterList(sourceId: Long, reset: Boolean = false) = withIOContext {
|
fun getFilterList(sourceId: Long, reset: Boolean = false) = flow {
|
||||||
client.get<List<SourceFilter>>(
|
val response = client.get<List<SourceFilter>>(
|
||||||
serverUrl + getFilterListQuery(sourceId)
|
serverUrl + getFilterListQuery(sourceId)
|
||||||
) {
|
) {
|
||||||
url {
|
url {
|
||||||
@@ -118,25 +126,27 @@ class SourceInteractionHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getFilterList(source: Source, reset: Boolean = false) = getFilterList(source.id, reset)
|
fun getFilterList(source: Source, reset: Boolean = false) = getFilterList(source.id, reset)
|
||||||
|
|
||||||
suspend fun setFilter(sourceId: Long, sourceFilter: SourceFilterChange) = withIOContext {
|
fun setFilter(sourceId: Long, sourceFilter: SourceFilterChange) = flow {
|
||||||
client.post<HttpResponse>(
|
val response = client.post<HttpResponse>(
|
||||||
serverUrl + setFilterRequest(sourceId)
|
serverUrl + setFilterRequest(sourceId)
|
||||||
) {
|
) {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
body = sourceFilter
|
body = sourceFilter
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun setFilter(sourceId: Long, position: Int, value: Any) = setFilter(
|
fun setFilter(sourceId: Long, position: Int, value: Any) = setFilter(
|
||||||
sourceId,
|
sourceId,
|
||||||
SourceFilterChange(position, value)
|
SourceFilterChange(position, value)
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun setFilter(sourceId: Long, parentPosition: Int, childPosition: Int, value: Any) = setFilter(
|
fun setFilter(sourceId: Long, parentPosition: Int, childPosition: Int, value: Any) = setFilter(
|
||||||
sourceId,
|
sourceId,
|
||||||
SourceFilterChange(
|
SourceFilterChange(
|
||||||
parentPosition,
|
parentPosition,
|
||||||
@@ -144,24 +154,26 @@ class SourceInteractionHandler @Inject constructor(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun getSourceSettings(sourceId: Long) = withIOContext {
|
fun getSourceSettings(sourceId: Long) = flow {
|
||||||
client.get<List<SourcePreference>>(
|
val response = client.get<List<SourcePreference>>(
|
||||||
serverUrl + getSourceSettingsQuery(sourceId)
|
serverUrl + getSourceSettingsQuery(sourceId)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun getSourceSettings(source: Source) = getSourceSettings(source.id)
|
fun getSourceSettings(source: Source) = getSourceSettings(source.id)
|
||||||
|
|
||||||
suspend fun setSourceSetting(sourceId: Long, sourcePreference: SourcePreferenceChange) = withIOContext {
|
fun setSourceSetting(sourceId: Long, sourcePreference: SourcePreferenceChange) = flow {
|
||||||
client.post<HttpResponse>(
|
val response = client.post<HttpResponse>(
|
||||||
serverUrl + updateSourceSettingQuery(sourceId)
|
serverUrl + updateSourceSettingQuery(sourceId)
|
||||||
) {
|
) {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
body = sourcePreference
|
body = sourcePreference
|
||||||
}
|
}
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun setSourceSetting(sourceId: Long, position: Int, value: Any) = setSourceSetting(
|
fun setSourceSetting(sourceId: Long, position: Int, value: Any) = setSourceSetting(
|
||||||
sourceId,
|
sourceId,
|
||||||
SourcePreferenceChange(position, value)
|
SourcePreferenceChange(position, value)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.data.server.interactions
|
package ca.gosyer.data.server.interactions
|
||||||
|
|
||||||
import ca.gosyer.core.lang.withIOContext
|
|
||||||
import ca.gosyer.data.models.Category
|
import ca.gosyer.data.models.Category
|
||||||
import ca.gosyer.data.models.Updates
|
import ca.gosyer.data.models.Updates
|
||||||
import ca.gosyer.data.server.Http
|
import ca.gosyer.data.server.Http
|
||||||
@@ -18,6 +17,9 @@ import io.ktor.client.request.get
|
|||||||
import io.ktor.client.request.post
|
import io.ktor.client.request.post
|
||||||
import io.ktor.client.statement.HttpResponse
|
import io.ktor.client.statement.HttpResponse
|
||||||
import io.ktor.http.Parameters
|
import io.ktor.http.Parameters
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class UpdatesInteractionHandler @Inject constructor(
|
class UpdatesInteractionHandler @Inject constructor(
|
||||||
@@ -25,26 +27,29 @@ class UpdatesInteractionHandler @Inject constructor(
|
|||||||
serverPreferences: ServerPreferences
|
serverPreferences: ServerPreferences
|
||||||
) : BaseInteractionHandler(client, serverPreferences) {
|
) : BaseInteractionHandler(client, serverPreferences) {
|
||||||
|
|
||||||
suspend fun getRecentUpdates(pageNum: Int) = withIOContext {
|
fun getRecentUpdates(pageNum: Int) = flow {
|
||||||
client.get<Updates>(
|
val response = client.get<Updates>(
|
||||||
serverUrl + recentUpdatesQuery(pageNum)
|
serverUrl + recentUpdatesQuery(pageNum)
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun updateLibrary() = withIOContext {
|
fun updateLibrary() = flow {
|
||||||
client.post<HttpResponse>(
|
val response = client.post<HttpResponse>(
|
||||||
serverUrl + fetchUpdatesRequest()
|
serverUrl + fetchUpdatesRequest()
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun updateCategory(categoryId: Long) = withIOContext {
|
fun updateCategory(categoryId: Long) = flow {
|
||||||
client.submitForm<HttpResponse>(
|
val response = client.submitForm<HttpResponse>(
|
||||||
serverUrl + fetchUpdatesRequest(),
|
serverUrl + fetchUpdatesRequest(),
|
||||||
formParameters = Parameters.build {
|
formParameters = Parameters.build {
|
||||||
append("category", categoryId.toString())
|
append("category", categoryId.toString())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
emit(response)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun updateCategory(category: Category) = updateCategory(category.id)
|
fun updateCategory(category: Category) = updateCategory(category.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,11 @@ import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
|||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.uicore.components.DropdownIconButton
|
import ca.gosyer.uicore.components.DropdownIconButton
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
|
import io.ktor.client.statement.HttpResponse
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
data class ChapterDownloadItem(
|
data class ChapterDownloadItem(
|
||||||
val manga: Manga?,
|
val manga: Manga?,
|
||||||
@@ -71,14 +74,18 @@ data class ChapterDownloadItem(
|
|||||||
_downloadChapterFlow.value = downloadingChapter
|
_downloadChapterFlow.value = downloadingChapter
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteDownload(chapterHandler: ChapterInteractionHandler) {
|
fun deleteDownload(chapterHandler: ChapterInteractionHandler): Flow<HttpResponse> {
|
||||||
chapterHandler.deleteChapterDownload(chapter)
|
return chapterHandler.deleteChapterDownload(chapter)
|
||||||
_downloadState.value = ChapterDownloadState.NotDownloaded
|
.onEach {
|
||||||
|
_downloadState.value = ChapterDownloadState.NotDownloaded
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun stopDownloading(chapterHandler: ChapterInteractionHandler) {
|
fun stopDownloading(chapterHandler: ChapterInteractionHandler): Flow<HttpResponse> {
|
||||||
chapterHandler.stopChapterDownload(chapter)
|
return chapterHandler.stopChapterDownload(chapter)
|
||||||
_downloadState.value = ChapterDownloadState.NotDownloaded
|
.onEach {
|
||||||
|
_downloadState.value = ChapterDownloadState.NotDownloaded
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.categories
|
package ca.gosyer.ui.categories
|
||||||
|
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
|
||||||
import ca.gosyer.core.logging.CKLogger
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.data.models.Category
|
import ca.gosyer.data.models.Category
|
||||||
import ca.gosyer.data.server.interactions.CategoryInteractionHandler
|
import ca.gosyer.data.server.interactions.CategoryInteractionHandler
|
||||||
@@ -14,7 +13,11 @@ import ca.gosyer.uicore.vm.ContextWrapper
|
|||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class CategoriesScreenViewModel @Inject constructor(
|
class CategoriesScreenViewModel @Inject constructor(
|
||||||
@@ -32,21 +35,22 @@ class CategoriesScreenViewModel @Inject constructor(
|
|||||||
getCategories()
|
getCategories()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCategories() {
|
private fun getCategories() {
|
||||||
scope.launch {
|
_categories.value = emptyList()
|
||||||
_categories.value = emptyList()
|
_isLoading.value = true
|
||||||
_isLoading.value = true
|
categoryHandler.getCategories(true)
|
||||||
try {
|
.onEach {
|
||||||
_categories.value = categoryHandler.getCategories(true)
|
_categories.value = it
|
||||||
.sortedBy { it.order }
|
.sortedBy { it.order }
|
||||||
.also { originalCategories = it }
|
.also { originalCategories = it }
|
||||||
.map { it.toMenuCategory() }
|
.map { it.toMenuCategory() }
|
||||||
} catch (e: Exception) {
|
|
||||||
e.throwIfCancellation()
|
|
||||||
} finally {
|
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
.catch {
|
||||||
|
info(it) { "Error getting categories" }
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateRemoteCategories(manualUpdate: Boolean = false) {
|
suspend fun updateRemoteCategories(manualUpdate: Boolean = false) {
|
||||||
@@ -54,23 +58,47 @@ class CategoriesScreenViewModel @Inject constructor(
|
|||||||
val newCategories = categories.filter { it.id == null }
|
val newCategories = categories.filter { it.id == null }
|
||||||
newCategories.forEach {
|
newCategories.forEach {
|
||||||
categoryHandler.createCategory(it.name)
|
categoryHandler.createCategory(it.name)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error creating category" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
originalCategories.forEach { originalCategory ->
|
originalCategories.forEach { originalCategory ->
|
||||||
val category = categories.find { it.id == originalCategory.id }
|
val category = categories.find { it.id == originalCategory.id }
|
||||||
if (category == null) {
|
if (category == null) {
|
||||||
categoryHandler.deleteCategory(originalCategory)
|
categoryHandler.deleteCategory(originalCategory)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error deleting category $originalCategory" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
} else if (category.name != originalCategory.name) {
|
} else if (category.name != originalCategory.name) {
|
||||||
categoryHandler.modifyCategory(originalCategory, category.name)
|
categoryHandler.modifyCategory(originalCategory, category.name)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error modifying category $category" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var updatedCategories = categoryHandler.getCategories(true)
|
var updatedCategories = categoryHandler.getCategories(true)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error getting updated categories" }
|
||||||
|
}
|
||||||
|
.singleOrNull()
|
||||||
categories.forEach { category ->
|
categories.forEach { category ->
|
||||||
val updatedCategory = updatedCategories.find { it.id == category.id || it.name == category.name } ?: return@forEach
|
val updatedCategory = updatedCategories?.find { it.id == category.id || it.name == category.name } ?: return@forEach
|
||||||
if (category.order != updatedCategory.order) {
|
if (category.order != updatedCategory.order) {
|
||||||
debug { "${category.name}: ${updatedCategory.order} to ${category.order}" }
|
debug { "${category.name}: ${updatedCategory.order} to ${category.order}" }
|
||||||
categoryHandler.reorderCategory(category.order, updatedCategory.order)
|
categoryHandler.reorderCategory(category.order, updatedCategory.order)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error re-ordering categories" }
|
||||||
|
}
|
||||||
|
.singleOrNull()
|
||||||
}
|
}
|
||||||
updatedCategories = categoryHandler.getCategories(true)
|
updatedCategories = categoryHandler.getCategories(true)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error getting updated categories" }
|
||||||
|
}
|
||||||
|
.singleOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manualUpdate) {
|
if (manualUpdate) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.downloads
|
package ca.gosyer.ui.downloads
|
||||||
|
|
||||||
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.data.download.DownloadService
|
import ca.gosyer.data.download.DownloadService
|
||||||
import ca.gosyer.data.models.Chapter
|
import ca.gosyer.data.models.Chapter
|
||||||
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
||||||
@@ -14,7 +15,10 @@ import ca.gosyer.uicore.vm.ContextWrapper
|
|||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class DownloadsScreenViewModel @Inject constructor(
|
class DownloadsScreenViewModel @Inject constructor(
|
||||||
@@ -36,35 +40,53 @@ class DownloadsScreenViewModel @Inject constructor(
|
|||||||
val downloadQueue get() = downloadService.downloadQueue
|
val downloadQueue get() = downloadService.downloadQueue
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
scope.launch {
|
downloadsHandler.startDownloading()
|
||||||
downloadsHandler.startDownloading()
|
.catch {
|
||||||
}
|
info(it) { "Error starting download" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pause() {
|
fun pause() {
|
||||||
scope.launch {
|
downloadsHandler.stopDownloading()
|
||||||
downloadsHandler.stopDownloading()
|
.catch {
|
||||||
}
|
info(it) { "Error stopping download" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
scope.launch {
|
downloadsHandler.clearDownloadQueue()
|
||||||
downloadsHandler.clearDownloadQueue()
|
.catch {
|
||||||
}
|
info(it) { "Error clearing download" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopDownload(chapter: Chapter) {
|
fun stopDownload(chapter: Chapter) {
|
||||||
scope.launch {
|
chapterHandler.stopChapterDownload(chapter)
|
||||||
chapterHandler.stopChapterDownload(chapter)
|
.catch {
|
||||||
}
|
info(it) { "Error stop chapter download" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun moveToBottom(chapter: Chapter) {
|
fun moveToBottom(chapter: Chapter) {
|
||||||
scope.launch {
|
chapterHandler.stopChapterDownload(chapter)
|
||||||
chapterHandler.stopChapterDownload(chapter)
|
.onEach {
|
||||||
chapterHandler.queueChapterDownload(chapter)
|
chapterHandler.queueChapterDownload(chapter)
|
||||||
}
|
.catch {
|
||||||
|
info(it) { "Error adding download" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error stop chapter download" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restartDownloader() = downloadService.init()
|
fun restartDownloader() = downloadService.init()
|
||||||
|
|
||||||
|
private companion object : CKLogger({})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.extensions
|
package ca.gosyer.ui.extensions
|
||||||
|
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
|
||||||
import ca.gosyer.core.logging.CKLogger
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.data.extension.ExtensionPreferences
|
import ca.gosyer.data.extension.ExtensionPreferences
|
||||||
import ca.gosyer.data.models.Extension
|
import ca.gosyer.data.models.Extension
|
||||||
@@ -17,11 +16,13 @@ import ca.gosyer.uicore.vm.ViewModel
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@@ -55,57 +56,61 @@ class ExtensionsScreenViewModel @Inject constructor(
|
|||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scope.launch {
|
getExtensions()
|
||||||
getExtensions()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getExtensions() {
|
private fun getExtensions() {
|
||||||
try {
|
extensionHandler.getExtensionList()
|
||||||
_isLoading.value = true
|
.onEach {
|
||||||
extensionList.value = extensionHandler.getExtensionList()
|
extensionList.value = it
|
||||||
} catch (e: Exception) {
|
_isLoading.value = false
|
||||||
e.throwIfCancellation()
|
}
|
||||||
extensionList.value = emptyList()
|
.catch {
|
||||||
} finally {
|
info(it) { "Error getting extensions" }
|
||||||
_isLoading.value = false
|
emit(emptyList())
|
||||||
}
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun install(extension: Extension) {
|
fun install(extension: Extension) {
|
||||||
info { "Install clicked" }
|
info { "Install clicked" }
|
||||||
scope.launch {
|
extensionHandler.installExtension(extension)
|
||||||
try {
|
.onEach {
|
||||||
extensionHandler.installExtension(extension)
|
getExtensions()
|
||||||
} catch (e: Exception) {
|
|
||||||
e.throwIfCancellation()
|
|
||||||
}
|
}
|
||||||
getExtensions()
|
.catch {
|
||||||
}
|
info(it) { "Error installing extension ${extension.apkName}" }
|
||||||
|
getExtensions()
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(extension: Extension) {
|
fun update(extension: Extension) {
|
||||||
info { "Update clicked" }
|
info { "Update clicked" }
|
||||||
scope.launch {
|
extensionHandler.updateExtension(extension)
|
||||||
try {
|
.onEach {
|
||||||
extensionHandler.updateExtension(extension)
|
getExtensions()
|
||||||
} catch (e: Exception) {
|
|
||||||
e.throwIfCancellation()
|
|
||||||
}
|
}
|
||||||
getExtensions()
|
.catch {
|
||||||
}
|
info(it) { "Error updating extension ${extension.apkName}" }
|
||||||
|
getExtensions()
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun uninstall(extension: Extension) {
|
fun uninstall(extension: Extension) {
|
||||||
info { "Uninstall clicked" }
|
info { "Uninstall clicked" }
|
||||||
scope.launch {
|
extensionHandler.uninstallExtension(extension)
|
||||||
try {
|
.onEach {
|
||||||
extensionHandler.uninstallExtension(extension)
|
getExtensions()
|
||||||
} catch (e: Exception) {
|
|
||||||
e.throwIfCancellation()
|
|
||||||
}
|
}
|
||||||
getExtensions()
|
.catch {
|
||||||
}
|
info(it) { "Error uninstalling extension ${extension.apkName}" }
|
||||||
|
getExtensions()
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setEnabledLanguages(langs: Set<String>) {
|
fun setEnabledLanguages(langs: Set<String>) {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.library
|
package ca.gosyer.ui.library
|
||||||
|
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
|
||||||
import ca.gosyer.core.lang.withDefaultContext
|
import ca.gosyer.core.lang.withDefaultContext
|
||||||
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.data.library.LibraryPreferences
|
import ca.gosyer.data.library.LibraryPreferences
|
||||||
import ca.gosyer.data.models.Category
|
import ca.gosyer.data.models.Category
|
||||||
import ca.gosyer.data.models.Manga
|
import ca.gosyer.data.models.Manga
|
||||||
@@ -24,11 +24,13 @@ import kotlinx.coroutines.flow.asFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.buffer
|
import kotlinx.coroutines.flow.buffer
|
||||||
import kotlinx.coroutines.flow.cancellable
|
import kotlinx.coroutines.flow.cancellable
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.single
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
private typealias CategoryItems = Pair<MutableStateFlow<List<Manga>>, MutableStateFlow<List<Manga>>>
|
private typealias CategoryItems = Pair<MutableStateFlow<List<Manga>>, MutableStateFlow<List<Manga>>>
|
||||||
@@ -101,22 +103,22 @@ class LibraryScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getLibrary() {
|
private fun getLibrary() {
|
||||||
scope.launch {
|
_isLoading.value = true
|
||||||
_isLoading.value = true
|
categoryHandler.getCategories()
|
||||||
try {
|
.onEach { categories ->
|
||||||
val categories = categoryHandler.getCategories()
|
|
||||||
if (categories.isEmpty()) {
|
if (categories.isEmpty()) {
|
||||||
throw Exception("Library is empty")
|
throw Exception("Library is empty")
|
||||||
}
|
}
|
||||||
library.categories.value = categories.sortedBy { it.order }
|
library.categories.value = categories.sortedBy { it.order }
|
||||||
updateCategories(categories)
|
updateCategories(categories)
|
||||||
} catch (e: Exception) {
|
|
||||||
e.throwIfCancellation()
|
|
||||||
_error.value = e.message
|
|
||||||
} finally {
|
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
.catch {
|
||||||
|
_error.value = it.message
|
||||||
|
info(it) { "Error getting categories" }
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSelectedPage(page: Int) {
|
fun setSelectedPage(page: Int) {
|
||||||
@@ -129,9 +131,18 @@ class LibraryScreenViewModel @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun updateCategories(categories: List<Category>) {
|
private suspend fun updateCategories(categories: List<Category>) {
|
||||||
withDefaultContext {
|
withDefaultContext {
|
||||||
categories.map {
|
categories.map { category ->
|
||||||
async {
|
async {
|
||||||
library.mangaMap.setManga(query.value, it.id, categoryHandler.getMangaFromCategory(it))
|
library.mangaMap.setManga(
|
||||||
|
query.value,
|
||||||
|
category.id,
|
||||||
|
categoryHandler.getMangaFromCategory(category)
|
||||||
|
.catch {
|
||||||
|
info { "Error getting manga for category $category" }
|
||||||
|
emit(emptyList())
|
||||||
|
}
|
||||||
|
.single()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}.awaitAll()
|
}.awaitAll()
|
||||||
}
|
}
|
||||||
@@ -146,10 +157,14 @@ class LibraryScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun removeManga(mangaId: Long) {
|
fun removeManga(mangaId: Long) {
|
||||||
scope.launch {
|
libraryHandler.removeMangaFromLibrary(mangaId)
|
||||||
libraryHandler.removeMangaFromLibrary(mangaId)
|
.onEach {
|
||||||
updateCategories(getCategoriesToUpdate(mangaId))
|
updateCategories(getCategoriesToUpdate(mangaId))
|
||||||
}
|
}
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error removing manga from library" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateQuery(query: String) {
|
fun updateQuery(query: String) {
|
||||||
@@ -157,19 +172,20 @@ class LibraryScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateLibrary() {
|
fun updateLibrary() {
|
||||||
scope.launch {
|
updatesHandler.updateLibrary()
|
||||||
updatesHandler.updateLibrary()
|
.catch {
|
||||||
}
|
info(it) { "Error updating library" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateCategory(category: Category) {
|
fun updateCategory(category: Category) {
|
||||||
scope.launch {
|
updatesHandler.updateCategory(category)
|
||||||
updatesHandler.updateCategory(category)
|
.catch {
|
||||||
}
|
info(it) { "Error updating category" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
private companion object : CKLogger({})
|
||||||
const val QUERY_KEY = "query"
|
|
||||||
const val SELECTED_CATEGORY_KEY = "selected_category"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.manga
|
package ca.gosyer.ui.manga
|
||||||
|
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
|
||||||
import ca.gosyer.core.lang.withIOContext
|
import ca.gosyer.core.lang.withIOContext
|
||||||
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.data.download.DownloadService
|
import ca.gosyer.data.download.DownloadService
|
||||||
import ca.gosyer.data.models.Category
|
import ca.gosyer.data.models.Category
|
||||||
import ca.gosyer.data.models.Chapter
|
import ca.gosyer.data.models.Chapter
|
||||||
@@ -25,9 +25,13 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.single
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
@@ -86,9 +90,14 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.launch {
|
categoryHandler.getCategories(true)
|
||||||
_categories.value = categoryHandler.getCategories(true)
|
.onEach {
|
||||||
}
|
_categories.value = it
|
||||||
|
}
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error getting categories" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadManga() {
|
fun loadManga() {
|
||||||
@@ -124,22 +133,34 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun refreshMangaAsync(mangaId: Long, refresh: Boolean = false) = withIOContext {
|
private suspend fun refreshMangaAsync(mangaId: Long, refresh: Boolean = false) = withIOContext {
|
||||||
async {
|
async {
|
||||||
try {
|
mangaHandler.getManga(mangaId, refresh)
|
||||||
_manga.value = mangaHandler.getManga(mangaId, refresh)
|
.onEach {
|
||||||
_mangaCategories.value = categoryHandler.getMangaCategories(mangaId)
|
_manga.value = it
|
||||||
} catch (e: Exception) {
|
}
|
||||||
e.throwIfCancellation()
|
.catch {
|
||||||
}
|
info(it) { "Error getting manga" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
|
categoryHandler.getMangaCategories(mangaId)
|
||||||
|
.onEach {
|
||||||
|
_mangaCategories.value = it
|
||||||
|
}
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error getting manga" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refreshChaptersAsync(mangaId: Long, refresh: Boolean = false) = withIOContext {
|
private suspend fun refreshChaptersAsync(mangaId: Long, refresh: Boolean = false) = withIOContext {
|
||||||
async {
|
async {
|
||||||
try {
|
_chapters.value = chapterHandler.getChapters(mangaId, refresh)
|
||||||
_chapters.value = chapterHandler.getChapters(mangaId, refresh).toDownloadChapters()
|
.catch {
|
||||||
} catch (e: Exception) {
|
info(it) { "Error getting chapters" }
|
||||||
e.throwIfCancellation()
|
emit(emptyList())
|
||||||
}
|
}
|
||||||
|
.single()
|
||||||
|
.toDownloadChapters()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +169,10 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
manga.value?.let { manga ->
|
manga.value?.let { manga ->
|
||||||
if (manga.inLibrary) {
|
if (manga.inLibrary) {
|
||||||
libraryHandler.removeMangaFromLibrary(manga)
|
libraryHandler.removeMangaFromLibrary(manga)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error toggling favorite" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
refreshMangaAsync(manga.id).await()
|
refreshMangaAsync(manga.id).await()
|
||||||
} else {
|
} else {
|
||||||
if (categories.value.isEmpty()) {
|
if (categories.value.isEmpty()) {
|
||||||
@@ -166,12 +191,24 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
if (manga.inLibrary) {
|
if (manga.inLibrary) {
|
||||||
oldCategories.filterNot { it in categories }.forEach {
|
oldCategories.filterNot { it in categories }.forEach {
|
||||||
categoryHandler.removeMangaFromCategory(manga, it)
|
categoryHandler.removeMangaFromCategory(manga, it)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error removing manga from category" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
libraryHandler.addMangaToLibrary(manga)
|
libraryHandler.addMangaToLibrary(manga)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error Adding manga to library" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
categories.filterNot { it in oldCategories }.forEach {
|
categories.filterNot { it in oldCategories }.forEach {
|
||||||
categoryHandler.addMangaToCategory(manga, it)
|
categoryHandler.addMangaToCategory(manga, it)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error adding manga to category" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
refreshMangaAsync(manga.id).await()
|
refreshMangaAsync(manga.id).await()
|
||||||
}
|
}
|
||||||
@@ -189,8 +226,22 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
fun toggleRead(index: Int) {
|
fun toggleRead(index: Int) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
manga.value?.let { manga ->
|
manga.value?.let { manga ->
|
||||||
chapterHandler.updateChapter(manga, index, read = !_chapters.value.first { it.chapter.index == index }.chapter.read)
|
chapterHandler.updateChapter(
|
||||||
_chapters.value = chapterHandler.getChapters(manga).toDownloadChapters()
|
manga,
|
||||||
|
index,
|
||||||
|
read = !_chapters.value.first { it.chapter.index == index }.chapter.read
|
||||||
|
)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error toggling read" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
|
_chapters.value = chapterHandler.getChapters(manga)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error getting new chapters after toggling read" }
|
||||||
|
emit(emptyList())
|
||||||
|
}
|
||||||
|
.single()
|
||||||
|
.toDownloadChapters()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,8 +249,22 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
fun toggleBookmarked(index: Int) {
|
fun toggleBookmarked(index: Int) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
manga.value?.let { manga ->
|
manga.value?.let { manga ->
|
||||||
chapterHandler.updateChapter(manga, index, bookmarked = !_chapters.value.first { it.chapter.index == index }.chapter.bookmarked)
|
chapterHandler.updateChapter(
|
||||||
_chapters.value = chapterHandler.getChapters(manga).toDownloadChapters()
|
manga,
|
||||||
|
index,
|
||||||
|
bookmarked = !_chapters.value.first { it.chapter.index == index }.chapter.bookmarked
|
||||||
|
)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error toggling bookmarked" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
|
_chapters.value = chapterHandler.getChapters(manga)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error getting new chapters after toggling bookmarked" }
|
||||||
|
emit(emptyList())
|
||||||
|
}
|
||||||
|
.single()
|
||||||
|
.toDownloadChapters()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,29 +273,48 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
manga.value?.let { manga ->
|
manga.value?.let { manga ->
|
||||||
chapterHandler.updateChapter(manga, index, markPreviousRead = true)
|
chapterHandler.updateChapter(manga, index, markPreviousRead = true)
|
||||||
_chapters.value = chapterHandler.getChapters(manga).toDownloadChapters()
|
.catch {
|
||||||
|
info(it) { "Error marking previous as read" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
|
_chapters.value = chapterHandler.getChapters(manga)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error getting new chapters after marking previous as read" }
|
||||||
|
emit(emptyList())
|
||||||
|
}
|
||||||
|
.single()
|
||||||
|
.toDownloadChapters()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadChapter(index: Int) {
|
fun downloadChapter(index: Int) {
|
||||||
scope.launch {
|
manga.value?.let { manga ->
|
||||||
manga.value?.let { manga ->
|
chapterHandler.queueChapterDownload(manga, index)
|
||||||
chapterHandler.queueChapterDownload(manga, index)
|
.catch {
|
||||||
}
|
info(it) { "Error downloading chapter" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteDownload(index: Int) {
|
fun deleteDownload(index: Int) {
|
||||||
scope.launch {
|
chapters.value.find { it.chapter.index == index }
|
||||||
chapters.value.find { it.chapter.index == index }?.deleteDownload(chapterHandler)
|
?.deleteDownload(chapterHandler)
|
||||||
}
|
?.catch {
|
||||||
|
info(it) { "Error deleting download" }
|
||||||
|
}
|
||||||
|
?.launchIn(scope)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopDownloadingChapter(index: Int) {
|
fun stopDownloadingChapter(index: Int) {
|
||||||
scope.launch {
|
chapters.value.find { it.chapter.index == index }
|
||||||
chapters.value.find { it.chapter.index == index }?.stopDownloading(chapterHandler)
|
?.stopDownloading(chapterHandler)
|
||||||
}
|
?.catch {
|
||||||
|
info(it) { "Error stopping download" }
|
||||||
|
}
|
||||||
|
?.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDispose() {
|
override fun onDispose() {
|
||||||
@@ -242,4 +326,6 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class Params(val mangaId: Long)
|
data class Params(val mangaId: Long)
|
||||||
|
|
||||||
|
private companion object : CKLogger({})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
package ca.gosyer.ui.reader
|
package ca.gosyer.ui.reader
|
||||||
|
|
||||||
import ca.gosyer.core.lang.launchDefault
|
import ca.gosyer.core.lang.launchDefault
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
|
||||||
import ca.gosyer.core.logging.CKLogger
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.core.prefs.getAsFlow
|
import ca.gosyer.core.prefs.getAsFlow
|
||||||
import ca.gosyer.data.models.Chapter
|
import ca.gosyer.data.models.Chapter
|
||||||
@@ -34,10 +33,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.single
|
||||||
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
@@ -150,10 +153,15 @@ class ReaderMenuViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun setMangaReaderMode(mode: String) {
|
fun setMangaReaderMode(mode: String) {
|
||||||
scope.launchDefault {
|
scope.launchDefault {
|
||||||
_manga.value?.updateRemote(
|
_manga.value
|
||||||
mangaHandler,
|
?.updateRemote(
|
||||||
mode
|
mangaHandler,
|
||||||
)
|
mode
|
||||||
|
)
|
||||||
|
?.catch {
|
||||||
|
info(it) { "Error updating manga reader mode" }
|
||||||
|
}
|
||||||
|
?.collect()
|
||||||
initManga(params.mangaId)
|
initManga(params.mangaId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,35 +193,37 @@ class ReaderMenuViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun initManga(mangaId: Long) {
|
private suspend fun initManga(mangaId: Long) {
|
||||||
try {
|
mangaHandler.getManga(mangaId)
|
||||||
_manga.value = mangaHandler.getManga(mangaId)
|
.onEach {
|
||||||
} catch (e: Exception) {
|
_manga.value = it
|
||||||
e.throwIfCancellation()
|
}
|
||||||
_state.value = ReaderChapter.State.Error(e)
|
.catch {
|
||||||
throw e
|
_state.value = ReaderChapter.State.Error(it)
|
||||||
}
|
info(it) { "Error loading manga" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun initChapters(mangaId: Long, chapterIndex: Int) {
|
private suspend fun initChapters(mangaId: Long, chapterIndex: Int) {
|
||||||
resetValues()
|
resetValues()
|
||||||
val chapter = ReaderChapter(
|
val chapter = ReaderChapter(
|
||||||
try {
|
chapterHandler.getChapter(mangaId, chapterIndex)
|
||||||
chapterHandler.getChapter(mangaId, chapterIndex)
|
.catch {
|
||||||
} catch (e: Exception) {
|
_state.value = ReaderChapter.State.Error(it)
|
||||||
e.throwIfCancellation()
|
info(it) { "Error getting chapter" }
|
||||||
_state.value = ReaderChapter.State.Error(e)
|
}
|
||||||
throw e
|
.singleOrNull() ?: return
|
||||||
}
|
|
||||||
)
|
)
|
||||||
val pages = loader.loadChapter(chapter)
|
val pages = loader.loadChapter(chapter)
|
||||||
viewerChapters.currChapter.value = chapter
|
viewerChapters.currChapter.value = chapter
|
||||||
scope.launchDefault {
|
scope.launchDefault {
|
||||||
val chapters = try {
|
val chapters = chapterHandler.getChapters(mangaId)
|
||||||
chapterHandler.getChapters(mangaId)
|
.catch {
|
||||||
} catch (e: Exception) {
|
info(it) { "Error getting chapter list" }
|
||||||
e.throwIfCancellation()
|
emit(emptyList())
|
||||||
emptyList()
|
}
|
||||||
}
|
.single()
|
||||||
|
|
||||||
val nextChapter = chapters.find { it.index == chapterIndex + 1 }
|
val nextChapter = chapters.find { it.index == chapterIndex + 1 }
|
||||||
if (nextChapter != null) {
|
if (nextChapter != null) {
|
||||||
viewerChapters.nextChapter.value = ReaderChapter(
|
viewerChapters.nextChapter.value = ReaderChapter(
|
||||||
@@ -259,17 +269,23 @@ class ReaderMenuViewModel @Inject constructor(
|
|||||||
.launchIn(chapter.scope)
|
.launchIn(chapter.scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun markChapterRead(mangaId: Long, chapter: ReaderChapter) {
|
private fun markChapterRead(mangaId: Long, chapter: ReaderChapter) {
|
||||||
chapterHandler.updateChapter(mangaId, chapter.chapter.index, true)
|
chapterHandler.updateChapter(mangaId, chapter.chapter.index, true)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error marking chapter read" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
fun sendProgress(chapter: Chapter? = this.chapter.value?.chapter, lastPageRead: Int = currentPage.value) {
|
fun sendProgress(chapter: Chapter? = this.chapter.value?.chapter, lastPageRead: Int = currentPage.value) {
|
||||||
chapter ?: return
|
chapter ?: return
|
||||||
if (chapter.read) return
|
if (chapter.read) return
|
||||||
GlobalScope.launchDefault {
|
chapterHandler.updateChapter(chapter.mangaId, chapter.index, lastPageRead = lastPageRead)
|
||||||
chapterHandler.updateChapter(chapter.mangaId, chapter.index, lastPageRead = lastPageRead)
|
.catch {
|
||||||
}
|
info(it) { "Error sending progress" }
|
||||||
|
}
|
||||||
|
.launchIn(GlobalScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateLastPageReadOffset(offset: Int) {
|
fun updateLastPageReadOffset(offset: Int) {
|
||||||
@@ -278,9 +294,11 @@ class ReaderMenuViewModel @Inject constructor(
|
|||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
private fun updateLastPageReadOffset(chapter: Chapter, offset: Int) {
|
private fun updateLastPageReadOffset(chapter: Chapter, offset: Int) {
|
||||||
GlobalScope.launchDefault {
|
chapter.updateRemote(chapterHandler, offset)
|
||||||
chapter.updateRemote(chapterHandler, offset)
|
.catch {
|
||||||
}
|
info(it) { "Error updating chapter offset" }
|
||||||
|
}
|
||||||
|
.launchIn(GlobalScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDispose() {
|
override fun onDispose() {
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ import kotlinx.coroutines.cancel
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
@@ -58,24 +62,30 @@ class TachideskPageLoader(
|
|||||||
val page = priorityPage.page
|
val page = priorityPage.page
|
||||||
debug { "Loading page ${page.index}" }
|
debug { "Loading page ${page.index}" }
|
||||||
if (page.status.value == ReaderPage.Status.QUEUE) {
|
if (page.status.value == ReaderPage.Status.QUEUE) {
|
||||||
try {
|
chapterHandler
|
||||||
page.bitmap.value = chapterHandler.getPage(chapter.chapter, page.index) {
|
.getPage(chapter.chapter, page.index) {
|
||||||
onDownload { bytesSentTotal, contentLength ->
|
onDownload { bytesSentTotal, contentLength ->
|
||||||
page.progress.value = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(1.0F)
|
page.progress.value = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(1.0F)
|
||||||
}
|
}
|
||||||
}.toImageBitmap()
|
}
|
||||||
page.status.value = ReaderPage.Status.READY
|
.onEach {
|
||||||
page.error.value = null
|
page.bitmap.value = it.toImageBitmap()
|
||||||
} catch (e: Exception) {
|
page.status.value = ReaderPage.Status.READY
|
||||||
e.throwIfCancellation()
|
page.error.value = null
|
||||||
page.bitmap.value = null
|
}
|
||||||
page.status.value = ReaderPage.Status.ERROR
|
.catch {
|
||||||
page.error.value = e.message
|
page.bitmap.value = null
|
||||||
}
|
page.status.value = ReaderPage.Status.ERROR
|
||||||
|
page.error.value = it.message
|
||||||
|
info(it) { "Error getting image" }
|
||||||
|
}
|
||||||
|
.flowOn(Dispatchers.IO)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.throwIfCancellation()
|
e.throwIfCancellation()
|
||||||
|
info(e) { "Error in loop" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
@@ -125,23 +129,33 @@ class SettingsBackupViewModel @Inject constructor(
|
|||||||
val createFlow = _createFlow.asSharedFlow()
|
val createFlow = _createFlow.asSharedFlow()
|
||||||
fun restoreFile(source: Source) {
|
fun restoreFile(source: Source) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
val file = try {
|
||||||
FileSystem.SYSTEM_TEMPORARY_DIRECTORY
|
FileSystem.SYSTEM_TEMPORARY_DIRECTORY
|
||||||
.resolve("tachidesk.${Random.nextLong()}.proto.gz")
|
.resolve("tachidesk.${Random.nextLong()}.proto.gz")
|
||||||
.also { file ->
|
.also { file ->
|
||||||
source.saveTo(file)
|
source.saveTo(file)
|
||||||
val (missingSources) = backupHandler.validateBackupFile(file)
|
|
||||||
if (missingSources.isEmpty()) {
|
|
||||||
restoreBackup(file)
|
|
||||||
} else {
|
|
||||||
_missingSourceFlow.emit(file to missingSources)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
info(e) { "Error importing backup" }
|
info(e) { "Error creating backup file" }
|
||||||
_restoreStatus.value = Status.Error
|
_restoreStatus.value = Status.Error
|
||||||
e.throwIfCancellation()
|
e.throwIfCancellation()
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
file ?: return@launch
|
||||||
|
|
||||||
|
backupHandler.validateBackupFile(file)
|
||||||
|
.onEach { (missingSources) ->
|
||||||
|
if (missingSources.isEmpty()) {
|
||||||
|
restoreBackup(file)
|
||||||
|
} else {
|
||||||
|
_missingSourceFlow.emit(file to missingSources)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error importing backup" }
|
||||||
|
_restoreStatus.value = Status.Error
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,20 +164,20 @@ class SettingsBackupViewModel @Inject constructor(
|
|||||||
_restoreStatus.value = Status.Nothing
|
_restoreStatus.value = Status.Nothing
|
||||||
_restoringProgress.value = null
|
_restoringProgress.value = null
|
||||||
_restoring.value = true
|
_restoring.value = true
|
||||||
try {
|
backupHandler.importBackupFile(file) {
|
||||||
backupHandler.importBackupFile(file) {
|
onUpload { bytesSentTotal, contentLength ->
|
||||||
onUpload { bytesSentTotal, contentLength ->
|
_restoringProgress.value = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(1.0F)
|
||||||
_restoringProgress.value = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(1.0F)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_restoreStatus.value = Status.Success
|
|
||||||
} catch (e: Exception) {
|
|
||||||
info(e) { "Error importing backup" }
|
|
||||||
_restoreStatus.value = Status.Error
|
|
||||||
e.throwIfCancellation()
|
|
||||||
} finally {
|
|
||||||
_restoring.value = false
|
|
||||||
}
|
}
|
||||||
|
.onEach {
|
||||||
|
_restoreStatus.value = Status.Success
|
||||||
|
}
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error importing backup" }
|
||||||
|
_restoreStatus.value = Status.Error
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
|
_restoring.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,46 +190,47 @@ class SettingsBackupViewModel @Inject constructor(
|
|||||||
private val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
|
|
||||||
fun exportBackup() {
|
fun exportBackup() {
|
||||||
scope.launch {
|
_creatingStatus.value = Status.Nothing
|
||||||
_creatingStatus.value = Status.Nothing
|
_creatingProgress.value = null
|
||||||
_creatingProgress.value = null
|
_creating.value = true
|
||||||
_creating.value = true
|
backupHandler
|
||||||
val backup = try {
|
.exportBackupFile {
|
||||||
backupHandler.exportBackupFile {
|
onDownload { bytesSentTotal, contentLength ->
|
||||||
onDownload { bytesSentTotal, contentLength ->
|
_creatingProgress.value = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(0.99F)
|
||||||
_creatingProgress.value = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(0.99F)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
info(e) { "Error exporting backup" }
|
|
||||||
_creatingStatus.value = Status.Error
|
|
||||||
_creating.value = false
|
|
||||||
e.throwIfCancellation()
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
_creatingProgress.value = 1.0F
|
.onEach { backup ->
|
||||||
if (backup != null && backup.status.isSuccess()) {
|
if (backup.status.isSuccess()) {
|
||||||
val filename = backup.headers["content-disposition"]?.substringAfter("filename=")?.trim('"') ?: "backup"
|
val filename =
|
||||||
tempFile.value = FileSystem.SYSTEM_TEMPORARY_DIRECTORY.resolve(filename).also {
|
backup.headers["content-disposition"]?.substringAfter("filename=")
|
||||||
launch {
|
?.trim('"') ?: "backup"
|
||||||
try {
|
tempFile.value = FileSystem.SYSTEM_TEMPORARY_DIRECTORY.resolve(filename).also {
|
||||||
backup.content.toInputStream()
|
mutex.tryLock()
|
||||||
.source()
|
scope.launch {
|
||||||
.saveTo(it)
|
try {
|
||||||
} catch (e: Exception) {
|
backup.content.toInputStream()
|
||||||
e.throwIfCancellation()
|
.source()
|
||||||
error(e) { "Error creating backup" }
|
.saveTo(it)
|
||||||
_creatingStatus.value = Status.Error
|
} catch (e: Exception) {
|
||||||
_creating.value = false
|
e.throwIfCancellation()
|
||||||
} finally {
|
info(e) { "Error creating backup" }
|
||||||
mutex.unlock()
|
_creatingStatus.value = Status.Error
|
||||||
|
_creating.value = false
|
||||||
|
} finally {
|
||||||
|
mutex.unlock()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_createFlow.emit(filename)
|
||||||
}
|
}
|
||||||
mutex.tryLock()
|
|
||||||
_createFlow.emit(filename)
|
|
||||||
}
|
}
|
||||||
}
|
.catch {
|
||||||
|
info(it) { "Error exporting backup" }
|
||||||
|
_creatingStatus.value = Status.Error
|
||||||
|
_creating.value = false
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exportBackupFileFound(backupSink: Sink) {
|
fun exportBackupFileFound(backupSink: Sink) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.data.library.LibraryPreferences
|
import ca.gosyer.data.library.LibraryPreferences
|
||||||
import ca.gosyer.data.server.interactions.CategoryInteractionHandler
|
import ca.gosyer.data.server.interactions.CategoryInteractionHandler
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
@@ -39,7 +40,9 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
|||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class SettingsLibraryScreen : Screen {
|
class SettingsLibraryScreen : Screen {
|
||||||
@@ -72,10 +75,17 @@ class SettingsLibraryViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun refreshCategoryCount() {
|
fun refreshCategoryCount() {
|
||||||
scope.launch {
|
categoryHandler.getCategories(true)
|
||||||
_categories.value = categoryHandler.getCategories(true).size
|
.onEach {
|
||||||
}
|
_categories.value = it.size
|
||||||
|
}
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error getting categories" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private companion object : CKLogger({})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.sources.browse
|
package ca.gosyer.ui.sources.browse
|
||||||
|
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.data.models.Manga
|
import ca.gosyer.data.models.Manga
|
||||||
import ca.gosyer.data.models.MangaPage
|
import ca.gosyer.data.models.MangaPage
|
||||||
import ca.gosyer.data.models.Source
|
import ca.gosyer.data.models.Source
|
||||||
@@ -15,7 +15,10 @@ import ca.gosyer.uicore.vm.ContextWrapper
|
|||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class SourceScreenViewModel(
|
class SourceScreenViewModel(
|
||||||
@@ -59,36 +62,33 @@ class SourceScreenViewModel(
|
|||||||
private val _pageNum = MutableStateFlow(1)
|
private val _pageNum = MutableStateFlow(1)
|
||||||
val pageNum = _pageNum.asStateFlow()
|
val pageNum = _pageNum.asStateFlow()
|
||||||
|
|
||||||
|
private val sourceMutex = Mutex()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
getPage()?.let { (mangas, hasNextPage) ->
|
||||||
val (mangas, hasNextPage) = getPage()
|
|
||||||
_mangas.value = mangas
|
_mangas.value = mangas
|
||||||
_hasNextPage.value = hasNextPage
|
_hasNextPage.value = hasNextPage
|
||||||
} catch (e: Exception) {
|
|
||||||
e.throwIfCancellation()
|
|
||||||
} finally {
|
|
||||||
_loading.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadNextPage() {
|
fun loadNextPage() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val hasNextPage = hasNextPage.value
|
if (hasNextPage.value && sourceMutex.tryLock()) {
|
||||||
val pageNum = pageNum.value
|
|
||||||
try {
|
|
||||||
_hasNextPage.value = false
|
|
||||||
_pageNum.value++
|
_pageNum.value++
|
||||||
val page = getPage()
|
val page = getPage()
|
||||||
_mangas.value += page.mangaList
|
if (page != null) {
|
||||||
_hasNextPage.value = page.hasNextPage
|
_mangas.value += page.mangaList
|
||||||
} catch (e: Exception) {
|
_hasNextPage.value = page.hasNextPage
|
||||||
_hasNextPage.value = hasNextPage
|
} else {
|
||||||
_pageNum.value = pageNum
|
_pageNum.value--
|
||||||
} finally {
|
}
|
||||||
_loading.value = false
|
sourceMutex.unlock()
|
||||||
}
|
}
|
||||||
|
_loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,12 +104,16 @@ class SourceScreenViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getPage(): MangaPage {
|
private suspend fun getPage(): MangaPage? {
|
||||||
return when {
|
return when {
|
||||||
isLatest.value -> sourceHandler.getLatestManga(source, pageNum.value)
|
isLatest.value -> sourceHandler.getLatestManga(source, pageNum.value)
|
||||||
_query.value != null || _usingFilters.value -> sourceHandler.getSearchResults(source, _query.value.orEmpty(), pageNum.value)
|
_query.value != null || _usingFilters.value -> sourceHandler.getSearchResults(source, _query.value.orEmpty(), pageNum.value)
|
||||||
else -> sourceHandler.getPopularManga(source, pageNum.value)
|
else -> sourceHandler.getPopularManga(source, pageNum.value)
|
||||||
}
|
}
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error getting source page" }
|
||||||
|
}
|
||||||
|
.singleOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startSearch(query: String?) {
|
fun startSearch(query: String?) {
|
||||||
@@ -136,4 +140,6 @@ class SourceScreenViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class Params(val source: Source)
|
data class Params(val source: Source)
|
||||||
|
|
||||||
|
private companion object : CKLogger({})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.sources.browse.filter
|
package ca.gosyer.ui.sources.browse.filter
|
||||||
|
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
|
||||||
import ca.gosyer.core.logging.CKLogger
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.data.models.sourcefilters.SourceFilter
|
import ca.gosyer.data.models.sourcefilters.SourceFilter
|
||||||
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
||||||
@@ -15,12 +14,13 @@ import ca.gosyer.uicore.vm.ContextWrapper
|
|||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.supervisorScope
|
import kotlinx.coroutines.supervisorScope
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
@@ -69,6 +69,7 @@ class SourceFiltersViewModel(
|
|||||||
childFilter.index,
|
childFilter.index,
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
|
.collect()
|
||||||
getFilters()
|
getFilters()
|
||||||
}
|
}
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
@@ -77,13 +78,18 @@ class SourceFiltersViewModel(
|
|||||||
filter.state.drop(1).filterNotNull()
|
filter.state.drop(1).filterNotNull()
|
||||||
.onEach {
|
.onEach {
|
||||||
sourceHandler.setFilter(sourceId, filter.index, it)
|
sourceHandler.setFilter(sourceId, filter.index, it)
|
||||||
|
.collect()
|
||||||
getFilters()
|
getFilters()
|
||||||
}
|
}
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.launchIn(scope)
|
}
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error with filters" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showingFilters(show: Boolean) {
|
fun showingFilters(show: Boolean) {
|
||||||
@@ -91,21 +97,20 @@ class SourceFiltersViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getFilters(initialLoad: Boolean = false) {
|
private fun getFilters(initialLoad: Boolean = false) {
|
||||||
scope.launch {
|
sourceHandler.getFilterList(sourceId, reset = initialLoad)
|
||||||
try {
|
.onEach {
|
||||||
_filters.value = sourceHandler.getFilterList(sourceId, reset = initialLoad).toView()
|
_filters.value = it.toView()
|
||||||
} catch (e: Exception) {
|
|
||||||
e.throwIfCancellation()
|
|
||||||
} finally {
|
|
||||||
_loading.value = false
|
_loading.value = false
|
||||||
}
|
}
|
||||||
}
|
.catch {
|
||||||
|
info(it) { "Error getting filters" }
|
||||||
|
_loading.value = false
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetFilters() {
|
fun resetFilters() {
|
||||||
scope.launch {
|
getFilters(initialLoad = true)
|
||||||
getFilters(initialLoad = true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Params(val sourceId: Long)
|
data class Params(val sourceId: Long)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.sources.home
|
package ca.gosyer.ui.sources.home
|
||||||
|
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
|
||||||
import ca.gosyer.core.logging.CKLogger
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.data.catalog.CatalogPreferences
|
import ca.gosyer.data.catalog.CatalogPreferences
|
||||||
import ca.gosyer.data.models.Source
|
import ca.gosyer.data.models.Source
|
||||||
@@ -16,10 +15,12 @@ import ca.gosyer.uicore.vm.ViewModel
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
class SourceHomeScreenViewModel @Inject constructor(
|
class SourceHomeScreenViewModel @Inject constructor(
|
||||||
@@ -51,15 +52,16 @@ class SourceHomeScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getSources() {
|
private fun getSources() {
|
||||||
scope.launch {
|
sourceHandler.getSourceList()
|
||||||
try {
|
.onEach {
|
||||||
installedSources.value = sourceHandler.getSourceList()
|
installedSources.value = it
|
||||||
} catch (e: Exception) {
|
|
||||||
e.throwIfCancellation()
|
|
||||||
} finally {
|
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
.catch {
|
||||||
|
info(it) { "Error getting sources" }
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setEnabledLanguages(langs: Set<String>) {
|
fun setEnabledLanguages(langs: Set<String>) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.sources.settings
|
package ca.gosyer.ui.sources.settings
|
||||||
|
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.data.models.sourcepreference.SourcePreference
|
import ca.gosyer.data.models.sourcepreference.SourcePreference
|
||||||
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
||||||
import ca.gosyer.ui.sources.settings.model.SourceSettingsView
|
import ca.gosyer.ui.sources.settings.model.SourceSettingsView
|
||||||
@@ -14,12 +14,13 @@ import ca.gosyer.uicore.vm.ContextWrapper
|
|||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.supervisorScope
|
import kotlinx.coroutines.supervisorScope
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
@@ -43,6 +44,10 @@ class SourceSettingsScreenViewModel @Inject constructor(
|
|||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
.onEach {
|
.onEach {
|
||||||
sourceHandler.setSourceSetting(params.sourceId, setting.index, it)
|
sourceHandler.setSourceSetting(params.sourceId, setting.index, it)
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error setting source setting" }
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
getSourceSettings()
|
getSourceSettings()
|
||||||
}
|
}
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
@@ -53,15 +58,16 @@ class SourceSettingsScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getSourceSettings() {
|
private fun getSourceSettings() {
|
||||||
scope.launch {
|
sourceHandler.getSourceSettings(params.sourceId)
|
||||||
try {
|
.onEach {
|
||||||
_sourceSettings.value = sourceHandler.getSourceSettings(params.sourceId).toView()
|
_sourceSettings.value = it.toView()
|
||||||
} catch (e: Exception) {
|
|
||||||
e.throwIfCancellation()
|
|
||||||
} finally {
|
|
||||||
_loading.value = false
|
_loading.value = false
|
||||||
}
|
}
|
||||||
}
|
.catch {
|
||||||
|
info(it) { "Error setting source setting" }
|
||||||
|
_loading.value = false
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Params(val sourceId: Long)
|
data class Params(val sourceId: Long)
|
||||||
@@ -69,4 +75,6 @@ class SourceSettingsScreenViewModel @Inject constructor(
|
|||||||
private fun List<SourcePreference>.toView() = mapIndexed { index, sourcePreference ->
|
private fun List<SourcePreference>.toView() = mapIndexed { index, sourcePreference ->
|
||||||
SourceSettingsView(index, sourcePreference)
|
SourceSettingsView(index, sourcePreference)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private companion object : CKLogger({})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.updates
|
package ca.gosyer.ui.updates
|
||||||
|
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.data.download.DownloadService
|
import ca.gosyer.data.download.DownloadService
|
||||||
import ca.gosyer.data.models.Chapter
|
import ca.gosyer.data.models.Chapter
|
||||||
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
||||||
@@ -16,6 +16,8 @@ import ca.gosyer.uicore.vm.ContextWrapper
|
|||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@@ -45,77 +47,89 @@ class UpdatesScreenViewModel @Inject constructor(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
getUpdates()
|
||||||
getUpdates(1)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.throwIfCancellation()
|
|
||||||
} finally {
|
|
||||||
_isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadNextPage() {
|
fun loadNextPage() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (hasNextPage.value && updatesMutex.tryLock()) {
|
if (hasNextPage.value && updatesMutex.tryLock()) {
|
||||||
try {
|
getUpdates()
|
||||||
getUpdates(currentPage.value++)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.throwIfCancellation()
|
|
||||||
currentPage.value--
|
|
||||||
}
|
|
||||||
updatesMutex.unlock()
|
updatesMutex.unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getUpdates(pageNum: Int) {
|
private suspend fun getUpdates() {
|
||||||
val updates = updatesHandler.getRecentUpdates(pageNum)
|
updatesHandler.getRecentUpdates(currentPage.value)
|
||||||
mangaIds = updates.page.map { it.manga.id }.toSet()
|
.onEach { updates ->
|
||||||
|
mangaIds = updates.page.map { it.manga.id }.toSet()
|
||||||
|
|
||||||
_updates.value += updates.page.map {
|
_updates.value += updates.page.map {
|
||||||
ChapterDownloadItem(
|
ChapterDownloadItem(
|
||||||
it.manga,
|
it.manga,
|
||||||
it.chapter
|
it.chapter
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
downloadService.registerWatches(mangaIds).merge()
|
downloadService.registerWatches(mangaIds).merge()
|
||||||
.onEach { (mangaId, chapters) ->
|
.onEach { (mangaId, chapters) ->
|
||||||
_updates.value.filter { it.chapter.mangaId == mangaId }
|
_updates.value.filter { it.chapter.mangaId == mangaId }
|
||||||
.forEach {
|
.forEach {
|
||||||
it.updateFrom(chapters)
|
it.updateFrom(chapters)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.launchIn(scope)
|
||||||
.launchIn(scope)
|
|
||||||
|
|
||||||
hasNextPage.value = updates.hasNextPage
|
hasNextPage.value = updates.hasNextPage
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
.catch {
|
||||||
|
info(it) { "Error getting updates" }
|
||||||
|
if (currentPage.value > 1) {
|
||||||
|
currentPage.value--
|
||||||
|
}
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadChapter(chapter: Chapter) {
|
fun downloadChapter(chapter: Chapter) {
|
||||||
scope.launch {
|
chapterHandler.queueChapterDownload(chapter)
|
||||||
chapterHandler.queueChapterDownload(chapter)
|
.catch {
|
||||||
}
|
info(it) { "Error queueing chapter" }
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteDownloadedChapter(chapter: Chapter) {
|
fun deleteDownloadedChapter(chapter: Chapter) {
|
||||||
scope.launch {
|
updates.value
|
||||||
updates.value.find {
|
.find {
|
||||||
it.chapter.mangaId == chapter.mangaId &&
|
it.chapter.mangaId == chapter.mangaId &&
|
||||||
it.chapter.index == chapter.index
|
it.chapter.index == chapter.index
|
||||||
}?.deleteDownload(chapterHandler)
|
}
|
||||||
}
|
?.deleteDownload(chapterHandler)
|
||||||
|
?.catch {
|
||||||
|
info(it) { "Error deleting download" }
|
||||||
|
}
|
||||||
|
?.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopDownloadingChapter(chapter: Chapter) {
|
fun stopDownloadingChapter(chapter: Chapter) {
|
||||||
scope.launch {
|
updates.value
|
||||||
updates.value.find {
|
.find {
|
||||||
it.chapter.mangaId == chapter.mangaId &&
|
it.chapter.mangaId == chapter.mangaId &&
|
||||||
it.chapter.index == chapter.index
|
it.chapter.index == chapter.index
|
||||||
}?.stopDownloading(chapterHandler)
|
}
|
||||||
}
|
?.stopDownloading(chapterHandler)
|
||||||
|
?.catch {
|
||||||
|
info(it) { "Error stopping download" }
|
||||||
|
}
|
||||||
|
?.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDispose() {
|
override fun onDispose() {
|
||||||
downloadService.removeWatches(mangaIds)
|
downloadService.removeWatches(mangaIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private companion object : CKLogger({})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user