mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 06:42:05 +01:00
268 lines
10 KiB
Kotlin
268 lines
10 KiB
Kotlin
/*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
package ca.gosyer.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
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.Row
|
|
import androidx.compose.foundation.layout.Spacer
|
|
import androidx.compose.foundation.layout.aspectRatio
|
|
import androidx.compose.foundation.layout.defaultMinSize
|
|
import androidx.compose.foundation.layout.fillMaxHeight
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.layout.height
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.foundation.layout.width
|
|
import androidx.compose.foundation.lazy.LazyColumn
|
|
import androidx.compose.foundation.lazy.items
|
|
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
|
|
import androidx.compose.ui.Modifier
|
|
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
|
|
import ca.gosyer.ui.base.components.Toolbar
|
|
import ca.gosyer.ui.base.components.mangaAspectRatio
|
|
import ca.gosyer.ui.base.resources.stringResource
|
|
import ca.gosyer.ui.base.vm.viewModel
|
|
import ca.gosyer.ui.main.Route
|
|
import ca.gosyer.ui.reader.openReaderMenu
|
|
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) {
|
|
launchApplication {
|
|
ThemedWindow(::exitApplication, title = BuildConfig.NAME) {
|
|
MangaMenu(mangaId)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun MangaMenu(mangaId: Long, backStack: BackStack<Route>? = null) {
|
|
val vm = viewModel<MangaMenuViewModel> {
|
|
MangaMenuViewModel.Params(mangaId)
|
|
}
|
|
val manga by vm.manga.collectAsState()
|
|
val chapters by vm.chapters.collectAsState()
|
|
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,
|
|
actions = {
|
|
if (categoriesExist) {
|
|
ActionIcon(
|
|
vm::setCategories,
|
|
stringResource("edit_categories"),
|
|
Icons.Rounded.Label
|
|
)
|
|
}
|
|
}
|
|
)
|
|
|
|
manga.let { manga ->
|
|
if (manga != null) {
|
|
Surface(Modifier.height(40.dp).fillMaxWidth()) {
|
|
Row(horizontalArrangement = Arrangement.SpaceBetween) {
|
|
Button(onClick = vm::toggleFavorite) {
|
|
Text(stringResource(if (manga.inLibrary) "action_remove_favorite" else "action_favorite"))
|
|
}
|
|
Button(onClick = vm::refreshManga, enabled = !isLoading) {
|
|
Text(stringResource("action_refresh_manga"))
|
|
}
|
|
}
|
|
}
|
|
|
|
Box {
|
|
val state = rememberLazyListState()
|
|
LazyColumn(state = state) {
|
|
item {
|
|
MangaItem(manga, serverUrl)
|
|
}
|
|
if (chapters.isNotEmpty()) {
|
|
items(chapters) { chapter ->
|
|
ChapterItem(
|
|
chapter,
|
|
dateTimeFormatter::format,
|
|
onClick = { openReaderMenu(it, manga.id) },
|
|
toggleRead = vm::toggleRead,
|
|
toggleBookmarked = vm::toggleBookmarked,
|
|
markPreviousAsRead = vm::markPreviousRead,
|
|
downloadAChapter = vm::downloadChapter,
|
|
deleteDownload = vm::deleteDownload,
|
|
stopDownload = vm::deleteDownload
|
|
)
|
|
}
|
|
} else if (!isLoading) {
|
|
item {
|
|
ErrorScreen(
|
|
stringResource("no_chapters_found"),
|
|
Modifier.height(400.dp).fillMaxWidth(),
|
|
retry = vm::loadChapters
|
|
)
|
|
}
|
|
}
|
|
}
|
|
VerticalScrollbar(
|
|
modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(),
|
|
adapter = rememberScrollbarAdapter(state)
|
|
)
|
|
}
|
|
} else if (!isLoading) {
|
|
ErrorScreen(stringResource("failed_manga_fetch"), retry = vm::loadManga)
|
|
}
|
|
}
|
|
}
|
|
if (isLoading) {
|
|
LoadingScreen()
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun MangaItem(manga: Manga, serverUrl: String) {
|
|
BoxWithConstraints(Modifier.padding(8.dp)) {
|
|
if (maxWidth > 600.dp) {
|
|
Row {
|
|
Cover(manga, serverUrl)
|
|
Spacer(Modifier.width(16.dp))
|
|
Surface(
|
|
elevation = 2.dp,
|
|
modifier = Modifier.defaultMinSize(minHeight = 450.dp).fillMaxWidth()
|
|
) {
|
|
MangaInfo(manga)
|
|
}
|
|
}
|
|
} else {
|
|
Column {
|
|
Cover(manga, serverUrl, Modifier.align(Alignment.CenterHorizontally))
|
|
Spacer(Modifier.height(16.dp))
|
|
Surface(elevation = 2.dp) {
|
|
MangaInfo(manga)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun Cover(manga: Manga, serverUrl: String, modifier: Modifier = Modifier) {
|
|
Surface(
|
|
modifier = modifier then Modifier
|
|
.width(300.dp)
|
|
.aspectRatio(mangaAspectRatio)
|
|
.padding(4.dp),
|
|
elevation = 4.dp,
|
|
shape = RoundedCornerShape(4.dp)
|
|
) {
|
|
Box(modifier = Modifier.fillMaxSize()) {
|
|
manga.cover(serverUrl)?.let {
|
|
KtorImage(it)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun MangaInfo(manga: Manga, modifier: Modifier = Modifier) {
|
|
Column(modifier) {
|
|
Text(manga.title, fontSize = 22.sp, fontWeight = FontWeight.Bold)
|
|
if (!manga.author.isNullOrEmpty()) {
|
|
Text(manga.author, fontSize = 18.sp)
|
|
}
|
|
if (!manga.artist.isNullOrEmpty() && manga.artist != manga.author) {
|
|
Text(manga.artist, fontSize = 18.sp)
|
|
}
|
|
if (!manga.description.isNullOrEmpty()) {
|
|
Text(manga.description)
|
|
}
|
|
if (!manga.genre.isNullOrEmpty()) {
|
|
Text(manga.genre)
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|