diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index e7f365b..243af19 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -110,7 +110,7 @@ kotlin { implementation(libs.oidc) implementation("dev.datlag.sheets-compose-dialogs:rating:2.0.0-SNAPSHOT") - // implementation("dev.datlag.sheets-compose-dialogs:option:2.0.0-SNAPSHOT") + implementation("dev.datlag.sheets-compose-dialogs:option:2.0.0-SNAPSHOT") implementation(project(":firebase")) implementation(project(":anilist")) diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendSettings.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendSettings.kt index e8252a3..4f00d62 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendSettings.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendSettings.kt @@ -1,9 +1,22 @@ package dev.datlag.aniflow.common import androidx.compose.ui.graphics.Color +import dev.datlag.aniflow.SharedRes +import dev.icerock.moko.resources.StringResource import dev.datlag.aniflow.settings.model.AppSettings @OptIn(ExperimentalStdlibApi::class) fun AppSettings.Color.toComposeColor() = Color( this.hex.substringAfter('#').hexToLong() or 0x00000000FF000000 -) \ No newline at end of file +) + +fun AppSettings.Color.toComposeString(): StringResource = when (this) { + is AppSettings.Color.Blue -> SharedRes.strings.color_blue + is AppSettings.Color.Purple -> SharedRes.strings.color_purple + is AppSettings.Color.Pink -> SharedRes.strings.color_pink + is AppSettings.Color.Orange -> SharedRes.strings.color_orange + is AppSettings.Color.Red -> SharedRes.strings.color_red + is AppSettings.Color.Green -> SharedRes.strings.color_green + is AppSettings.Color.Gray -> SharedRes.strings.color_gray + is AppSettings.Color.Custom -> SharedRes.strings.color_custom +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsComponent.kt index 85e6486..f29b154 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsComponent.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.Flow interface SettingsComponent : Component { val user: Flow val adultContent: Flow + val selectedColor: Flow fun changeAdultContent(value: Boolean) fun changeProfileColor(value: AppSettings.Color?) diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreen.kt index c943d64..0106554 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreen.kt @@ -7,10 +7,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Cancel -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.NoAdultContent +import androidx.compose.material.icons.filled.* import androidx.compose.material3.* import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass @@ -28,19 +25,27 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import coil3.compose.rememberAsyncImagePainter +import com.maxkeppeker.sheets.core.models.base.IconSource +import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState +import com.maxkeppeler.sheets.option.OptionDialog +import com.maxkeppeler.sheets.option.models.DisplayMode +import com.maxkeppeler.sheets.option.models.Option +import com.maxkeppeler.sheets.option.models.OptionConfig +import com.maxkeppeler.sheets.option.models.OptionSelection import dev.chrisbanes.haze.haze import dev.datlag.aniflow.LocalHaze import dev.datlag.aniflow.LocalPaddingValues import dev.datlag.aniflow.SharedRes import dev.datlag.aniflow.common.plus import dev.datlag.aniflow.common.toComposeColor +import dev.datlag.aniflow.common.toComposeString import dev.datlag.aniflow.other.StateSaver import dev.datlag.aniflow.settings.model.AppSettings import dev.datlag.tooling.compose.onClick import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import dev.icerock.moko.resources.compose.stringResource -@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3WindowSizeClassApi::class) +@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3WindowSizeClassApi::class, ExperimentalMaterial3Api::class) @Composable fun SettingsScreen(component: SettingsComponent) { val padding = PaddingValues(16.dp) @@ -120,29 +125,52 @@ fun SettingsScreen(component: SettingsComponent) { } } item { - Text( - modifier = Modifier.padding(vertical = 16.dp), - text = "Profile Color", - style = MaterialTheme.typography.headlineSmall, - fontWeight = FontWeight.SemiBold + val selectedColor by component.selectedColor.collectAsStateWithLifecycle(null) + val useCase = rememberUseCaseState() + val colors = AppSettings.Color.all.toList() + + OptionDialog( + state = useCase, + selection = OptionSelection.Single( + options = colors.map { + Option( + icon = IconSource( + imageVector = Icons.Filled.Circle, + tint = it.toComposeColor() + ), + titleText = stringResource(it.toComposeString()) + ) + }, + onSelectOption = { option, _ -> + component.changeProfileColor(colors[option]) + } + ), + config = OptionConfig( + mode = DisplayMode.GRID_VERTICAL, + gridColumns = 4 + ) ) - } - item { - FlowRow( + + Row( modifier = Modifier.fillParentMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), - maxItemsInEachRow = when (calculateWindowSizeClass().widthSizeClass) { - WindowWidthSizeClass.Compact -> 4 - else -> Int.MAX_VALUE - } + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - AppSettings.Color.all.forEach { - ColorItem( - color = it, - onClick = { chosen -> - component.changeProfileColor(chosen) - } + Icon( + imageVector = Icons.Default.Palette, + contentDescription = null, + ) + Text( + text = "Profile Color" + ) + Spacer(modifier = Modifier.weight(1F)) + IconButton( + onClick = { useCase.show() } + ) { + Icon( + imageVector = Icons.Filled.Circle, + contentDescription = null, + tint = selectedColor?.toComposeColor() ?: LocalContentColor.current ) } } diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreenComponent.kt index d851593..2bfdd82 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreenComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreenComponent.kt @@ -23,6 +23,7 @@ class SettingsScreenComponent( override val user: Flow = userHelper.user.flowOn(ioDispatcher()) override val adultContent: Flow = appSettings.adultContent.flowOn(ioDispatcher()) + override val selectedColor: Flow = appSettings.color.flowOn(ioDispatcher()) @Composable override fun render() { diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt index 4b0a285..25b1285 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt @@ -24,7 +24,6 @@ import com.maxkeppeler.sheets.rating.RatingDialog import com.maxkeppeler.sheets.rating.models.RatingBody import com.maxkeppeler.sheets.rating.models.RatingConfig import com.maxkeppeler.sheets.rating.models.RatingSelection -import com.maxkeppeler.sheets.rating.models.RatingViewStyle import dev.chrisbanes.haze.haze import dev.chrisbanes.haze.hazeChild import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi @@ -43,33 +42,10 @@ import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle @Composable fun MediumScreen(component: MediumComponent) { val coverImage by component.coverImage.collectAsStateWithLifecycle() - val ratingState = rememberUseCaseState() - val userRating by component.rating.collectAsStateWithLifecycle() val dialogState by component.dialog.subscribeAsState() dialogState.child?.instance?.render() - RatingDialog( - state = ratingState, - selection = RatingSelection( - onSelectRating = { rating, _ -> - component.rate(rating) - } - ), - header = Header.Default( - title = "Rate this Anime", - icon = IconSource(Icons.Filled.Star) - ), - body = RatingBody.Default( - bodyText = "" - ), - config = RatingConfig( - ratingOptionsCount = 5, - ratingOptionsSelected = userRating.takeIf { it > 0 }, - ratingZeroValid = true - ) - ) - Box( modifier = Modifier.fillMaxSize(), ) { @@ -101,11 +77,35 @@ fun MediumScreen(component: MediumComponent) { ) }, floatingActionButton = { + val userRating by component.rating.collectAsStateWithLifecycle() + val ratingState = rememberUseCaseState() + val alreadyAdded by component.alreadyAdded.collectAsStateWithLifecycle() val notReleased by component.status.mapCollect { it == MediaStatus.UNKNOWN__ || it == MediaStatus.NOT_YET_RELEASED } + RatingDialog( + state = ratingState, + selection = RatingSelection( + onSelectRating = { rating, _ -> + component.rate(rating) + } + ), + header = Header.Default( + title = "Rate this Anime", + icon = IconSource(Icons.Filled.Star) + ), + body = RatingBody.Default( + bodyText = "" + ), + config = RatingConfig( + ratingOptionsCount = 5, + ratingOptionsSelected = userRating.takeIf { it > 0 }, + ratingZeroValid = true + ) + ) + if (!notReleased) { EditFAB( displayAdd = !alreadyAdded, diff --git a/composeApp/src/commonMain/moko-resources/base/strings.xml b/composeApp/src/commonMain/moko-resources/base/strings.xml index e5b92c8..7554b7c 100644 --- a/composeApp/src/commonMain/moko-resources/base/strings.xml +++ b/composeApp/src/commonMain/moko-resources/base/strings.xml @@ -37,4 +37,12 @@ NSFW / Adult Content NSFW / Adult Content is not enabled. Back + Blue + Purple + Pink + Orange + Red + Green + Gray + Custom