From b1043314f22a00f98150491dbd0cbf78fc43e492 Mon Sep 17 00:00:00 2001 From: DatLag Date: Sun, 19 May 2024 13:47:28 +0200 Subject: [PATCH] improved manga support --- .../screen/medium/MediumComponent.kt | 5 +- .../screen/medium/MediumScreenComponent.kt | 12 +- .../screen/medium/component/CoverSection.kt | 39 ++---- .../medium/dialog/edit/EditComponent.kt | 4 +- .../screen/medium/dialog/edit/EditDialog.kt | 112 +++++++++--------- .../medium/dialog/edit/EditDialogComponent.kt | 4 +- .../screen/medium/dialog/edit/EditState.kt | 6 + .../dialog/edit/component/TopSection.kt | 2 +- .../moko-resources/base/strings.xml | 3 + .../moko-resources/de-DE/strings.xml | 3 + 10 files changed, 95 insertions(+), 95 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumComponent.kt index 07d08d1..218b7c5 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumComponent.kt @@ -10,8 +10,6 @@ import dev.datlag.aniflow.anilist.type.MediaListStatus import dev.datlag.aniflow.anilist.type.MediaStatus import dev.datlag.aniflow.anilist.type.MediaType import dev.datlag.aniflow.other.Series -import dev.datlag.aniflow.settings.model.AppSettings -import dev.datlag.aniflow.ui.navigation.Component import dev.datlag.aniflow.ui.navigation.ContentHolderComponent import dev.datlag.aniflow.ui.navigation.DialogComponent import kotlinx.coroutines.flow.Flow @@ -38,10 +36,9 @@ interface MediumComponent : ContentHolderComponent { val genres: Flow> val format: Flow - val episodes: Flow + val episodesOrChapters: Flow val duration: Flow val status: Flow - val chapters: Flow val volumes: Flow val rated: Flow diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreenComponent.kt index 1f5fb4e..538573b 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreenComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreenComponent.kt @@ -114,8 +114,8 @@ class MediumScreenComponent( } @OptIn(ExperimentalCoroutinesApi::class) - override val episodes: Flow = mediumSuccessState.mapLatest { - it.medium.episodes + override val episodesOrChapters: Flow = mediumSuccessState.mapLatest { + it.medium.episodesOrChapters }.distinctUntilChanged() @OptIn(ExperimentalCoroutinesApi::class) @@ -186,11 +186,6 @@ class MediumScreenComponent( initialValue = initialMedium.entry?.status ?: MediaListStatus.UNKNOWN__ ) - @OptIn(ExperimentalCoroutinesApi::class) - override val chapters: Flow = mediumSuccessState.mapLatest { - it.medium.chapters - } - @OptIn(ExperimentalCoroutinesApi::class) override val volumes: Flow = mediumSuccessState.mapLatest { it.medium.volumes @@ -256,13 +251,14 @@ class MediumScreenComponent( is DialogConfig.Edit -> EditDialogComponent( componentContext = context, di = di, - episodes = episodes, + episodesOrChapters = episodesOrChapters, progress = watchProgress, listStatus = listStatus, repeatCount = watchRepeat, episodeStartDate = mediumSuccessState.mapLatest { it.medium.startDate }, + type = type, onDismiss = dialogNavigation::dismiss, onSave = { status, progress, repeat -> dialogNavigation.dismiss { diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CoverSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CoverSection.kt index 4c9b6ad..1f6617a 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CoverSection.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CoverSection.kt @@ -2,12 +2,7 @@ package dev.datlag.aniflow.ui.navigation.screen.medium.component import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.List import androidx.compose.material.icons.automirrored.rounded.List -import androidx.compose.material.icons.filled.NoAdultContent -import androidx.compose.material.icons.filled.OndemandVideo -import androidx.compose.material.icons.filled.RssFeed -import androidx.compose.material.icons.filled.Timelapse import androidx.compose.material.icons.rounded.* import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -22,8 +17,7 @@ import coil3.compose.AsyncImage import coil3.compose.rememberAsyncImagePainter import dev.datlag.aniflow.SharedRes import dev.datlag.aniflow.anilist.model.Medium -import dev.datlag.aniflow.anilist.type.MediaFormat -import dev.datlag.aniflow.anilist.type.MediaStatus +import dev.datlag.aniflow.anilist.type.MediaType import dev.datlag.aniflow.common.icon import dev.datlag.aniflow.common.text import dev.datlag.aniflow.ui.navigation.screen.medium.MediumComponent @@ -31,8 +25,6 @@ import dev.datlag.tooling.compose.ifTrue import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import dev.icerock.moko.resources.compose.pluralStringResource import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow @Composable fun CoverSection( @@ -84,11 +76,10 @@ fun CoverSection( verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically) ) { val format by component.format.collectAsStateWithLifecycle(component.initialMedium.format) - val episodes by component.episodes.collectAsStateWithLifecycle(component.initialMedium.episodes) + val episodes by component.episodesOrChapters.collectAsStateWithLifecycle(component.initialMedium.episodes) val duration by component.duration.collectAsStateWithLifecycle(component.initialMedium.avgEpisodeDurationInMin) val status by component.status.collectAsStateWithLifecycle(component.initialMedium.status) val isAdult by component.isAdult.collectAsStateWithLifecycle(component.initialMedium.isAdult) - val chapters by component.chapters.collectAsStateWithLifecycle(component.initialMedium.chapters) val volumes by component.volumes.collectAsStateWithLifecycle(component.initialMedium.volumes) Row( @@ -121,25 +112,19 @@ fun CoverSection( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp) ) { + val type by component.type.collectAsStateWithLifecycle(MediaType.UNKNOWN__) + val (icon, stringRes) = remember(type) { + when (type) { + MediaType.MANGA -> Icons.Rounded.AutoStories to SharedRes.plurals.chapters + else -> Icons.AutoMirrored.Rounded.List to SharedRes.plurals.episodes + } + } + Icon( - imageVector = Icons.AutoMirrored.Rounded.List, + imageVector = icon, contentDescription = null ) - Text(text = pluralStringResource(SharedRes.plurals.episodes, episodes, episodes)) - } - } else { - if (chapters > -1) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Icon( - imageVector = Icons.Rounded.AutoStories, - contentDescription = null - ) - Text(text = pluralStringResource(SharedRes.plurals.chapters, chapters, chapters)) - } + Text(text = pluralStringResource(stringRes, episodes, episodes)) } } if (duration > -1) { diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditComponent.kt index e32f4a0..f88242f 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditComponent.kt @@ -1,16 +1,18 @@ package dev.datlag.aniflow.ui.navigation.screen.medium.dialog.edit import dev.datlag.aniflow.anilist.type.MediaListStatus +import dev.datlag.aniflow.anilist.type.MediaType import dev.datlag.aniflow.ui.navigation.DialogComponent import kotlinx.coroutines.flow.Flow import kotlinx.datetime.LocalDate interface EditComponent : DialogComponent { - val episodes: Flow + val episodesOrChapters: Flow val progress: Flow val listStatus: Flow val repeatCount: Flow val episodeStartDate: Flow + val type: Flow fun save(editState: EditState) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialog.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialog.kt index c73a49f..24f8a23 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialog.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialog.kt @@ -2,33 +2,24 @@ package dev.datlag.aniflow.ui.navigation.screen.medium.dialog.edit import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState -import com.maxkeppeler.sheets.calendar.CalendarDialog -import com.maxkeppeler.sheets.calendar.models.CalendarConfig -import com.maxkeppeler.sheets.calendar.models.CalendarSelection import dev.datlag.aniflow.LocalEdgeToEdge import dev.datlag.aniflow.SharedRes import dev.datlag.aniflow.anilist.type.MediaListStatus +import dev.datlag.aniflow.anilist.type.MediaType import dev.datlag.aniflow.common.icon import dev.datlag.aniflow.common.merge import dev.datlag.aniflow.ui.navigation.screen.medium.dialog.edit.component.TopSection import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import dev.icerock.moko.resources.compose.stringResource -import io.github.aakira.napier.Napier -import kotlinx.datetime.* -import kotlin.math.max -import kotlin.math.min @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable @@ -51,12 +42,13 @@ fun EditDialog(component: EditComponent) { sheetState = sheetState ) { val editState = rememberEditState( - mediumEpisodes = component.episodes, + mediumEpisodes = component.episodesOrChapters, progress = component.progress, repeat = component.repeatCount, listStatus = component.listStatus, ) val currentListStatus by editState.listStatus.collectAsStateWithLifecycle() + val type by component.type.collectAsStateWithLifecycle(MediaType.UNKNOWN__) LazyColumn( modifier = Modifier.fillMaxWidth(), @@ -83,7 +75,13 @@ fun EditDialog(component: EditComponent) { item { Text( modifier = Modifier.fillParentMaxWidth().padding(top = 32.dp), - text = "Watched Episode", + text = stringResource( + if (type == MediaType.MANGA) { + SharedRes.strings.read_chapter + } else { + SharedRes.strings.watched_episode + } + ), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) @@ -115,49 +113,57 @@ fun EditDialog(component: EditComponent) { } } } - item { - val currentEpisode by editState.episode.collectAsStateWithLifecycle() + if (editState.hasEpisodes) { + item { + val currentEpisode by editState.episode.collectAsStateWithLifecycle() - Row( - modifier = Modifier.fillParentMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Button( - onClick = { - editState.minusEpisode() - }, - enabled = editState.canRemoveEpisode - ) { - Text(text = stringResource(SharedRes.strings.minus_one)) - } - OutlinedTextField( - modifier = Modifier.weight(1F), - value = if (currentEpisode <= 0) "" else currentEpisode.toString(), - onValueChange = { - editState.setEpisode(it.toIntOrNull()) - }, - placeholder = { - Text( - modifier = Modifier.fillMaxWidth(), - text = stringResource(SharedRes.strings.episode), - textAlign = TextAlign.Center, - style = LocalTextStyle.current.copy(textAlign = TextAlign.Center) - ) - }, - singleLine = true, - maxLines = 1, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), - shape = MaterialTheme.shapes.medium - ) - Button( - onClick = { - editState.plusEpisode() - }, - enabled = editState.canAddEpisode + Row( + modifier = Modifier.fillParentMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - Text(text = stringResource(SharedRes.strings.plus_one)) + Button( + onClick = { + editState.minusEpisode() + }, + enabled = editState.canRemoveEpisode + ) { + Text(text = stringResource(SharedRes.strings.minus_one)) + } + OutlinedTextField( + modifier = Modifier.weight(1F), + value = if (currentEpisode <= 0) "" else currentEpisode.toString(), + onValueChange = { + editState.setEpisode(it.toIntOrNull()) + }, + placeholder = { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource( + if (type == MediaType.MANGA) { + SharedRes.strings.chapter + } else { + SharedRes.strings.episode + } + ), + textAlign = TextAlign.Center, + style = LocalTextStyle.current.copy(textAlign = TextAlign.Center) + ) + }, + singleLine = true, + maxLines = 1, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), + shape = MaterialTheme.shapes.medium + ) + Button( + onClick = { + editState.plusEpisode() + }, + enabled = editState.canAddEpisode + ) { + Text(text = stringResource(SharedRes.strings.plus_one)) + } } } } diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialogComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialogComponent.kt index 41c8473..2ddc15b 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialogComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialogComponent.kt @@ -5,6 +5,7 @@ import com.apollographql.apollo3.ApolloClient import com.arkivanov.decompose.ComponentContext import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.anilist.type.MediaListStatus +import dev.datlag.aniflow.anilist.type.MediaType import dev.datlag.aniflow.common.onRender import dev.datlag.aniflow.other.BurningSeriesResolver import dev.datlag.aniflow.other.Constants @@ -21,11 +22,12 @@ import org.kodein.di.instance class EditDialogComponent( componentContext: ComponentContext, override val di: DI, - override val episodes: Flow, + override val episodesOrChapters: Flow, override val progress: Flow, override val listStatus: Flow, override val repeatCount: Flow, override val episodeStartDate: Flow, + override val type: Flow, private val onDismiss: () -> Unit, private val onSave: (MediaListStatus, Int, Int) -> Unit ) : EditComponent, ComponentContext by componentContext { diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditState.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditState.kt index 169a300..5f9d2d2 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditState.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditState.kt @@ -27,6 +27,9 @@ class EditState( val canAddEpisode: Boolean get() = episodeState.canAdd + val hasEpisodes: Boolean + get() = episodeState.hasEpisodes + val canRemoveRepeat: Boolean get() = repeatState.canRemove @@ -146,6 +149,9 @@ class EditState( internal val canAdd: Boolean get() = currentEpisode.value < maxEpisodes + internal val hasEpisodes: Boolean + get() = maxEpisodes > 0 + internal fun plus( value: Int = 1, onNotComplete: (Boolean) -> Unit, diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/component/TopSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/component/TopSection.kt index b7b26b3..c569395 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/component/TopSection.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/component/TopSection.kt @@ -61,7 +61,7 @@ fun TopSection( } Text( modifier = Modifier.weight(1F), - text = "Edit", + text = stringResource(SharedRes.strings.edit), style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center, diff --git a/composeApp/src/commonMain/moko-resources/base/strings.xml b/composeApp/src/commonMain/moko-resources/base/strings.xml index 3bc02eb..1174650 100644 --- a/composeApp/src/commonMain/moko-resources/base/strings.xml +++ b/composeApp/src/commonMain/moko-resources/base/strings.xml @@ -107,4 +107,7 @@ Repeat Open-Source Licenses This is a list of (all) libraries used in this project and it\'s licenses + Chapter + Watched Episode + Read Chapter diff --git a/composeApp/src/commonMain/moko-resources/de-DE/strings.xml b/composeApp/src/commonMain/moko-resources/de-DE/strings.xml index 5086bed..b923f2e 100644 --- a/composeApp/src/commonMain/moko-resources/de-DE/strings.xml +++ b/composeApp/src/commonMain/moko-resources/de-DE/strings.xml @@ -107,4 +107,7 @@ Wiederholung Open-Source Lizenzen Das ist eine Liste von (allen) Bibliotheken, die in diesem Projekt verwendet werden und deren Lizenzen + Kapitel + Gesehene Folge + Gelesenes Kapitel