Add ability to add manga to categories

This commit is contained in:
Syer10
2021-08-20 21:03:19 -04:00
parent ec595baf25
commit e7d5852e47
3 changed files with 117 additions and 6 deletions

View File

@@ -8,6 +8,7 @@ package ca.gosyer.ui.manga
import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
@@ -28,10 +29,14 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Label
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
@@ -40,7 +45,10 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import ca.gosyer.BuildConfig
import ca.gosyer.data.models.Category
import ca.gosyer.data.models.Manga
import ca.gosyer.ui.base.WindowDialog
import ca.gosyer.ui.base.components.ActionIcon
import ca.gosyer.ui.base.components.ErrorScreen
import ca.gosyer.ui.base.components.KtorImage
import ca.gosyer.ui.base.components.LoadingScreen
@@ -54,6 +62,8 @@ import ca.gosyer.util.compose.ThemedWindow
import ca.gosyer.util.lang.launchApplication
import com.github.zsoltk.compose.router.BackStack
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
@OptIn(DelicateCoroutinesApi::class)
fun openMangaMenu(mangaId: Long) {
@@ -74,10 +84,30 @@ fun MangaMenu(mangaId: Long, backStack: BackStack<Route>? = null) {
val isLoading by vm.isLoading.collectAsState()
val serverUrl by vm.serverUrl.collectAsState()
val dateTimeFormatter by vm.dateTimeFormatter.collectAsState()
val categoriesExist by vm.categoriesExist.collectAsState()
LaunchedEffect(Unit) {
vm.chooseCategoriesFlow.collect { (availableCategories, usedCategories) ->
openCategorySelectDialog(availableCategories, usedCategories, vm::addFavorite)
}
}
Box {
Column(Modifier.background(MaterialTheme.colors.background)) {
Toolbar(stringResource("location_manga"), backStack, backStack != null)
Toolbar(
stringResource("location_manga"),
backStack,
backStack != null,
actions = {
if (categoriesExist) {
ActionIcon(
vm::setCategories,
stringResource("edit_categories"),
Icons.Rounded.Label
)
}
}
)
manga.let { manga ->
if (manga != null) {
@@ -200,3 +230,38 @@ private fun MangaInfo(manga: Manga, modifier: Modifier = Modifier) {
}
}
}
fun openCategorySelectDialog(
categories: List<Category>,
oldCategories: List<Category>,
onPositiveClick: (List<Category>, List<Category>) -> Unit
) {
val enabledCategoriesFlow = MutableStateFlow(oldCategories)
WindowDialog(
"Select Categories",
onPositiveButton = { onPositiveClick(enabledCategoriesFlow.value, oldCategories) }
) {
val enabledCategories by enabledCategoriesFlow.collectAsState()
LazyColumn {
items(categories) { category ->
Row(
Modifier.fillMaxWidth().padding(8.dp)
.clickable {
if (category in enabledCategories) {
enabledCategoriesFlow.value -= category
} else {
enabledCategoriesFlow.value += category
}
},
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(category.name, style = MaterialTheme.typography.subtitle1)
Checkbox(
category in enabledCategories,
onCheckedChange = null
)
}
}
}
}
}

View File

@@ -8,9 +8,11 @@ package ca.gosyer.ui.manga
import ca.gosyer.data.download.DownloadService
import ca.gosyer.data.download.model.DownloadChapter
import ca.gosyer.data.models.Category
import ca.gosyer.data.models.Chapter
import ca.gosyer.data.models.Manga
import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.data.server.interactions.CategoryInteractionHandler
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
import ca.gosyer.data.server.interactions.LibraryInteractionHandler
import ca.gosyer.data.server.interactions.MangaInteractionHandler
@@ -19,6 +21,7 @@ import ca.gosyer.ui.base.vm.ViewModel
import ca.gosyer.util.lang.throwIfCancellation
import ca.gosyer.util.lang.withIOContext
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
@@ -35,6 +38,7 @@ class MangaMenuViewModel @Inject constructor(
private val params: Params,
private val mangaHandler: MangaInteractionHandler,
private val chapterHandler: ChapterInteractionHandler,
private val categoryHandler: CategoryInteractionHandler,
private val libraryHandler: LibraryInteractionHandler,
private val downloadService: DownloadService,
serverPreferences: ServerPreferences,
@@ -53,6 +57,11 @@ class MangaMenuViewModel @Inject constructor(
private val _isLoading = MutableStateFlow(true)
val isLoading = _isLoading.asStateFlow()
private val _categoriesExist = MutableStateFlow(true)
val categoriesExist = _categoriesExist.asStateFlow()
val chooseCategoriesFlow = MutableSharedFlow<Pair<List<Category>, List<Category>>>()
val dateTimeFormatter = uiPreferences.dateFormat().changes()
.map {
getDateFormat(it)
@@ -79,6 +88,10 @@ class MangaMenuViewModel @Inject constructor(
refreshMangaAsync(params.mangaId).await() to refreshChaptersAsync(params.mangaId).await()
_isLoading.value = false
}
scope.launch {
_categoriesExist.value = categoryHandler.getCategories(true).isNotEmpty()
}
}
fun loadManga() {
@@ -105,6 +118,16 @@ class MangaMenuViewModel @Inject constructor(
}
}
fun setCategories() {
scope.launch {
manga.value?.let { manga ->
val categories = async { categoryHandler.getCategories(true) }
val oldCategories = async { categoryHandler.getMangaCategories(manga) }
chooseCategoriesFlow.emit(categories.await() to oldCategories.await())
}
}
}
private suspend fun refreshMangaAsync(mangaId: Long, refresh: Boolean = false) = withIOContext {
async {
try {
@@ -127,14 +150,36 @@ class MangaMenuViewModel @Inject constructor(
fun toggleFavorite() {
scope.launch {
manga.value?.let {
if (it.inLibrary) {
libraryHandler.removeMangaFromLibrary(it)
manga.value?.let { manga ->
if (manga.inLibrary) {
libraryHandler.removeMangaFromLibrary(manga)
refreshMangaAsync(manga.id).await()
} else {
libraryHandler.addMangaToLibrary(it)
val categories = categoryHandler.getCategories(true)
if (categories.isEmpty()) {
addFavorite(emptyList(), emptyList())
} else {
chooseCategoriesFlow.emit(categories to emptyList())
}
}
}
}
}
refreshMangaAsync(it.id).await()
fun addFavorite(categories: List<Category>, oldCategories: List<Category>) {
scope.launch {
manga.value?.let { manga ->
if (manga.inLibrary) {
oldCategories.filterNot { it in categories }.forEach {
categoryHandler.removeMangaFromCategory(manga, it)
}
} else {
libraryHandler.addMangaToLibrary(manga)
}
categories.filterNot { it in oldCategories }.forEach {
categoryHandler.addMangaToCategory(manga, it)
}
refreshMangaAsync(manga.id).await()
}
}
}

View File

@@ -66,6 +66,7 @@
<string name="page_progress">Page %1$d</string>
<string name="no_chapters_found">No chapters found</string>
<string name="failed_manga_fetch">Failed to load manga</string>
<string name="edit_categories">Edit categories</string>
<!-- Sources Menu -->
<string name="sources_home">Home</string>