From 789eadf595fc45d6bb2b8745e1462b38db0328c6 Mon Sep 17 00:00:00 2001 From: DatLag Date: Fri, 17 May 2024 12:16:55 +0200 Subject: [PATCH] added list info --- .../datlag/aniflow/anilist/ListRepository.kt | 18 ++- .../datlag/aniflow/anilist/model/Medium.kt | 6 + .../aniflow/ui/navigation/RootComponent.kt | 5 +- .../screen/favorites/FavoritesComponent.kt | 3 + .../screen/favorites/FavoritesScreen.kt | 13 +- .../favorites/FavoritesScreenComponent.kt | 6 + .../screen/favorites/component/ListCard.kt | 149 ++++++++++++++---- .../ui/navigation/screen/home/HomeScreen.kt | 4 +- .../datlag/aniflow/ui/theme/SchemeTheme.kt | 15 +- 9 files changed, 173 insertions(+), 46 deletions(-) diff --git a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/ListRepository.kt b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/ListRepository.kt index fa5d3e6..4a667cf 100644 --- a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/ListRepository.kt +++ b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/ListRepository.kt @@ -4,6 +4,7 @@ import com.apollographql.apollo3.ApolloClient import com.apollographql.apollo3.api.Optional import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.anilist.model.User +import dev.datlag.aniflow.anilist.type.MediaListSort import dev.datlag.aniflow.anilist.type.MediaType import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* @@ -17,6 +18,7 @@ class ListRepository( private val page = MutableStateFlow(0) private val _type = MutableStateFlow(MediaType.UNKNOWN__) + private val sort = MutableStateFlow(MediaListSort.UPDATED_TIME_DESC) @OptIn(ExperimentalCoroutinesApi::class) private val type = _type.transformLatest { @@ -36,12 +38,14 @@ class ListRepository( private val query = combine( page, type, + sort, user.filterNotNull().distinctUntilChanged(), - ) { p, t, u -> + ) { p, t, s, u -> Query( page = p, type = t, - userId = u.id + userId = u.id, + sort = s ) } @@ -86,7 +90,8 @@ class ListRepository( private data class Query( val page: Int, val type: MediaType, - val userId: Int + val userId: Int, + val sort: MediaListSort ) { fun toGraphQL() = ListQuery( page = Optional.present(page), @@ -95,7 +100,12 @@ class ListRepository( } else { Optional.present(type) }, - userId = Optional.present(userId) + userId = Optional.present(userId), + sort = if (sort == MediaListSort.UNKNOWN__) { + Optional.absent() + } else { + Optional.present(listOf(sort)) + } ) } diff --git a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/model/Medium.kt b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/model/Medium.kt index ad176ea..c91c233 100644 --- a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/model/Medium.kt +++ b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/model/Medium.kt @@ -335,6 +335,12 @@ data class Medium( } ?: -1 } + val episodesOrChapters: Int + get() = when (type) { + MediaType.MANGA -> chapters + else -> episodes + } + @Serializable data class Title( /** diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/RootComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/RootComponent.kt index f9ba5e9..d8f67b2 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/RootComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/RootComponent.kt @@ -41,7 +41,7 @@ class RootComponent( componentContext = componentContext, di = di, onMediumDetails = { - navigation.push(RootConfig.Details(it)) + navigation.bringToFront(RootConfig.Details(it)) }, onDiscover = { // navigation.replaceCurrent(RootConfig.Wallpaper) @@ -67,6 +67,9 @@ class RootComponent( }, onHome = { navigation.replaceCurrent(RootConfig.Home) + }, + onMedium = { + navigation.bringToFront(RootConfig.Details(it)) } ) is RootConfig.Nekos -> NekosScreenComponent( diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesComponent.kt index 8aa9493..13b157a 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesComponent.kt @@ -1,6 +1,7 @@ package dev.datlag.aniflow.ui.navigation.screen.favorites import dev.datlag.aniflow.anilist.ListRepository +import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.settings.model.TitleLanguage import dev.datlag.aniflow.ui.navigation.Component import kotlinx.coroutines.flow.Flow @@ -12,4 +13,6 @@ interface FavoritesComponent : Component { fun viewDiscover() fun viewHome() + + fun details(medium: Medium) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesScreen.kt index cd823c6..ab181f5 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesScreen.kt @@ -63,7 +63,7 @@ fun FavoritesScreen(component: FavoritesComponent) { floatingActionButton = { ExtendedFloatingActionButton( onClick = {}, - expanded = listState.isScrollingUp() && listState.canScrollForward, + expanded = listState.isScrollingUp(), icon = { Icon( imageVector = Icons.Rounded.SwapVert, @@ -77,7 +77,7 @@ fun FavoritesScreen(component: FavoritesComponent) { }, bottomBar = { HidingNavigationBar( - visible = listState.isScrollingUp() && listState.canScrollForward, + visible = listState.isScrollingUp(), selected = NavigationBarState.Favorite, loggedIn = flowOf(true), onDiscover = component::viewDiscover, @@ -108,14 +108,15 @@ fun FavoritesScreen(component: FavoritesComponent) { LazyColumn( state = listState, modifier = Modifier.fillMaxSize().haze(state = LocalHaze.current), - contentPadding = padding.plus(PaddingValues(8.dp)), - verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = padding.merge(PaddingValues(16.dp)), + verticalArrangement = Arrangement.spacedBy(16.dp), ) { - items(current.medium.toList()) { + items(current.medium.toList(), key = { it.id }) { ListCard( medium = it, titleLanguage = titleLanguage, - modifier = Modifier.fillParentMaxWidth().height(150.dp) + modifier = Modifier.fillParentMaxWidth().height(150.dp), + onClick = component::details ) } } diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesScreenComponent.kt index fb8b6f9..88adff3 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesScreenComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesScreenComponent.kt @@ -7,6 +7,7 @@ import com.arkivanov.decompose.ComponentContext import dev.chrisbanes.haze.HazeState import dev.datlag.aniflow.LocalHaze import dev.datlag.aniflow.anilist.ListRepository +import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.common.onRender import dev.datlag.aniflow.other.UserHelper import dev.datlag.aniflow.settings.Settings @@ -23,6 +24,7 @@ class FavoritesScreenComponent( override val di: DI, private val onDiscover: () -> Unit, private val onHome: () -> Unit, + private val onMedium: (Medium) -> Unit ) : FavoritesComponent, ComponentContext by componentContext { private val appSettings by instance() @@ -57,4 +59,8 @@ class FavoritesScreenComponent( override fun viewHome() { onHome() } + + override fun details(medium: Medium) { + onMedium(medium) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/component/ListCard.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/component/ListCard.kt index 80f9632..54050d8 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/component/ListCard.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/component/ListCard.kt @@ -1,13 +1,17 @@ package dev.datlag.aniflow.ui.navigation.screen.favorites.component +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.PlayArrow import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -16,19 +20,36 @@ import androidx.compose.ui.unit.max import coil3.compose.AsyncImage import coil3.compose.rememberAsyncImagePainter import dev.datlag.aniflow.anilist.model.Medium -import dev.datlag.aniflow.common.preferred +import dev.datlag.aniflow.common.* import dev.datlag.aniflow.settings.model.TitleLanguage +import dev.datlag.aniflow.ui.theme.SchemeTheme +import dev.datlag.aniflow.ui.theme.rememberSchemeThemeDominantColorState +import dev.icerock.moko.resources.compose.stringResource import kotlin.math.max import kotlin.math.min +@OptIn(ExperimentalStdlibApi::class) @Composable fun ListCard( medium: Medium, titleLanguage: TitleLanguage?, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + onClick: (Medium) -> Unit ) { + val defaultColor = remember(medium.coverImage.color) { + medium.coverImage.color?.substringAfter('#')?.let { + val colorValue = it.hexToLong() or 0x00000000FF000000 + Color(colorValue) + } + } + + val updater = SchemeTheme.create( + key = medium.id, + defaultColor = defaultColor, + ) + ElevatedCard( - onClick = { }, + onClick = { onClick(medium) }, modifier = modifier ) { Row( @@ -36,32 +57,75 @@ fun ListCard( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - AsyncImage( - modifier = Modifier.widthIn(min = 100.dp, max = 120.dp).fillMaxHeight().clip(MaterialTheme.shapes.medium), - model = medium.coverImage.extraLarge, - contentDescription = medium.preferred(titleLanguage), - contentScale = ContentScale.Crop, - error = rememberAsyncImagePainter( - model = medium.coverImage.large, + Box( + modifier = Modifier + .widthIn(min = 100.dp, max = 120.dp) + .fillMaxHeight() + .clip(MaterialTheme.shapes.medium) + ) { + val colorState = rememberSchemeThemeDominantColorState( + key = medium.id, + applyMinContrast = true, + minContrastBackgroundColor = MaterialTheme.colorScheme.surfaceVariant, + defaultColor = defaultColor ?: MaterialTheme.colorScheme.primary, + defaultOnColor = defaultColor?.plainOnColor ?: MaterialTheme.colorScheme.onPrimary + ) + + AsyncImage( + modifier = Modifier.fillMaxSize(), + model = medium.coverImage.extraLarge, + contentDescription = medium.preferred(titleLanguage), contentScale = ContentScale.Crop, error = rememberAsyncImagePainter( - model = medium.coverImage.medium, + model = medium.coverImage.large, contentScale = ContentScale.Crop, + error = rememberAsyncImagePainter( + model = medium.coverImage.medium, + contentScale = ContentScale.Crop, + onSuccess = { state -> + updater?.update(state.painter) + } + ), onSuccess = { state -> + updater?.update(state.painter) } ), onSuccess = { state -> + updater?.update(state.painter) + } + ) + medium.entry?.let { entry -> + Row( + modifier = Modifier + .align(Alignment.BottomStart) + .fillMaxWidth() + .bottomShadowBrush(colorState.primary) + .padding(8.dp) + .padding(top = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + imageVector = entry.status.icon(), + contentDescription = null, + tint = colorState.onPrimary + ) + Text( + text = stringResource(entry.status.stringRes(medium.type)), + color = colorState.onPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + softWrap = true + ) } - ), - onSuccess = { state -> } - ) + } Column( modifier = Modifier.weight(1F).padding(top = 16.dp, end = 16.dp, bottom = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - val progress = medium.entry?.progress ?: 0 - val maxProgress = max(progress, medium.episodes) + var progress by remember(medium.entry?.progress) { mutableStateOf(medium.entry?.progress ?: 0) } + val totalEpisodes = max(medium.episodesOrChapters, 0) + val maxProgress = max(max(progress, totalEpisodes), 0) Text( text = medium.preferred(titleLanguage), @@ -73,21 +137,44 @@ fun ListCard( ) Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Bottom ) { - LinearProgressIndicator( - progress = { 1F }, - modifier = Modifier - .fillMaxWidth( - fraction = (progress.toFloat() / maxProgress.toFloat()) + Text(text = "$progress/$totalEpisodes") + AnimatedVisibility( + visible = progress < totalEpisodes + ) { + Button( + onClick = { + progress++ + }, + enabled = progress < totalEpisodes + ) { + Text(text = "+1") + } + } + } + if (maxProgress > 0) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + LinearProgressIndicator( + progress = { 1F }, + modifier = Modifier + .fillMaxWidth( + fraction = (progress.toFloat() / maxProgress.toFloat()) + ) + .clip(CircleShape) + ) + if (progress < maxProgress) { + LinearProgressIndicator( + progress = { 0F }, + modifier = Modifier.weight(1F).clip(CircleShape) ) - .clip(CircleShape) - ) - LinearProgressIndicator( - progress = { 0F }, - modifier = Modifier.weight(1F).clip(CircleShape) - ) + } + } } } } diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/home/HomeScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/home/HomeScreen.kt index d2753ac..8eca16a 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/home/HomeScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/home/HomeScreen.kt @@ -153,7 +153,7 @@ fun HomeScreen(component: HomeComponent) { onClick = { imagePicker.launch() }, - expanded = listState.isScrollingUp() && listState.canScrollForward, + expanded = listState.isScrollingUp(), icon = { Icon( imageVector = Icons.Filled.CameraEnhance, @@ -168,7 +168,7 @@ fun HomeScreen(component: HomeComponent) { }, bottomBar = { HidingNavigationBar( - visible = listState.isScrollingUp() && listState.canScrollForward, + visible = listState.isScrollingUp(), selected = NavigationBarState.Home, loggedIn = component.loggedIn, onDiscover = component::viewDiscover, diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/theme/SchemeTheme.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/theme/SchemeTheme.kt index 8620e55..f31673c 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/theme/SchemeTheme.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/theme/SchemeTheme.kt @@ -49,12 +49,23 @@ data object SchemeTheme { }.getOrNull() @Composable - fun create(key: Any?): Updater? { + fun create( + key: Any?, + defaultColor: Color? = null, + defaultOnColor: Color? = null, + ): Updater? { if (key == null) { return null } - val state = rememberSchemeThemeDominantColorState(key) + val onColor = defaultOnColor ?: remember(defaultColor) { + defaultColor?.plainOnColor + } + val state = rememberSchemeThemeDominantColorState( + key = key, + defaultColor = defaultColor ?: MaterialTheme.colorScheme.primary, + defaultOnColor = onColor ?: MaterialTheme.colorScheme.onPrimary, + ) val scope = rememberCoroutineScope() return remember(state, scope) { state?.let { Updater.State(scope, it) }