diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 2e0fb03..bd8f127 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -104,6 +104,7 @@ kotlin { implementation(libs.coil.compose) implementation(libs.coil.mp) implementation(libs.coil.network.ktor) + implementation(libs.windowsize.multiplatform) } desktopMain.dependencies { implementation(compose.desktop.currentOs) diff --git a/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/cocktail/CocktailScreen.kt b/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/cocktail/CocktailScreen.kt index f873dc9..16ad482 100644 --- a/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/cocktail/CocktailScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/cocktail/CocktailScreen.kt @@ -5,16 +5,19 @@ package com.aslansari.spiritvisor.cocktail import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.* +import androidx.compose.material.icons.Icons 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.graphics.StrokeCap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import coil3.compose.AsyncImage +import coil3.compose.SubcomposeAsyncImage +import coil3.compose.SubcomposeAsyncImageContent import com.aslansari.spiritvisor.cocktail.component.CreditText +import com.aslansari.spiritvisor.theme.icon.LocalBar @Composable internal fun CocktailRoute( @@ -49,11 +52,37 @@ internal fun CocktailScreen( horizontalAlignment = Alignment.CenterHorizontally, ) { // Add content here - AsyncImage( + SubcomposeAsyncImage( modifier = Modifier.size(256.dp), model = uiState.cocktailImageUrl, contentDescription = "Cocktail Image", contentScale = ContentScale.Fit, + error = { + Surface( + color = Color(0xFFDDE1E6), + contentColor = Color(0xFF4D5358), + shape = CircleShape, + ) { + Box(Modifier.size(64.dp), contentAlignment = Alignment.Center) { + Icon( + modifier = Modifier.fillMaxSize(.7f), + imageVector = Icons.LocalBar, + contentDescription = "Cocktail icon", + ) + } + } + }, + loading = { + CircularProgressIndicator( + modifier = Modifier.size(32.dp), + strokeWidth = 8.dp, + color = MaterialTheme.colors.primary, + strokeCap = StrokeCap.Round, + ) + }, + success = { painter -> + SubcomposeAsyncImageContent() + } ) Spacer(Modifier.size(24.dp)) Text(uiState.title, style = MaterialTheme.typography.h4) diff --git a/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/cocktail/CocktailViewModel.kt b/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/cocktail/CocktailViewModel.kt index 08998de..eb795c9 100644 --- a/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/cocktail/CocktailViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/cocktail/CocktailViewModel.kt @@ -38,8 +38,7 @@ class CocktailViewModel : BaseViewModel() { fun selectCocktailByFlavor(flavor: String) { val cocktails = cocktailsByFlavor[flavor] if (!cocktails.isNullOrEmpty()) { - val randomIndex = cocktails.indices.random() - val cocktail = cocktails[randomIndex] + val cocktail = cocktails.random() setState { copy( title = cocktail.title, diff --git a/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/cocktail/component/CreditText.kt b/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/cocktail/component/CreditText.kt index dd837f1..617e236 100644 --- a/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/cocktail/component/CreditText.kt +++ b/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/cocktail/component/CreditText.kt @@ -31,7 +31,7 @@ fun CreditText(modifier: Modifier = Modifier) { } append(" with") } - Text(creditText, style = MaterialTheme.typography.caption.copy(color = Color(0xFF697077))) + Text(creditText, style = MaterialTheme.typography.body2.copy(color = Color(0xFF697077))) Spacer(Modifier.size(2.dp)) Icon( imageVector = Icons.HeartSharp, diff --git a/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/home/HomeRoute.kt b/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/home/HomeScreen.kt similarity index 61% rename from composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/home/HomeRoute.kt rename to composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/home/HomeScreen.kt index 49e0b63..7a8460c 100644 --- a/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/home/HomeRoute.kt +++ b/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/home/HomeScreen.kt @@ -1,15 +1,22 @@ +@file:OptIn(ExperimentalMaterial3WindowSizeClassApi::class) + package com.aslansari.spiritvisor.home import androidx.compose.foundation.layout.* import androidx.compose.material.Button +import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog import androidx.lifecycle.viewmodel.compose.viewModel import com.aslansari.spiritvisor.cocktail.component.CreditText @@ -35,6 +42,14 @@ internal fun HomeScreen( uiState: HomeUiState, modifier: Modifier = Modifier, ) { + if (uiState.loading) { + Dialog(onDismissRequest = { /*TODO*/ }) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } + } + val windowSizeClass = calculateWindowSizeClass() Box(modifier = modifier.fillMaxSize()) { Column( modifier = Modifier.fillMaxSize(), @@ -44,20 +59,20 @@ internal fun HomeScreen( Text("Pick a flavor for your cocktail ...", style = MaterialTheme.typography.h4) Spacer(Modifier.size(32.dp)) FlowRow( - modifier = Modifier.fillMaxWidth(.5f), + modifier = Modifier.padding(16.dp).then( + if (windowSizeClass.widthSizeClass == WindowWidthSizeClass.Expanded) { + Modifier.fillMaxWidth(.5f) + } else { + Modifier.fillMaxWidth() + } + ), horizontalArrangement = Arrangement.spacedBy(32.dp), verticalArrangement = Arrangement.spacedBy(16.dp), maxItemsInEachRow = 3, ) { - FlavorCategoryButton("Sour", onClick = { onClick("Sour") }) - FlavorCategoryButton("Sweet", onClick = { onClick("Sweet") }) - FlavorCategoryButton("Salty", onClick = { onClick("Salty") }) - FlavorCategoryButton("Spicy", onClick = { onClick("Spicy") }) - FlavorCategoryButton("Bitter", onClick = { onClick("Bitter") }) - FlavorCategoryButton("Herbal", onClick = { onClick("Herbal") }) - FlavorCategoryButton("Fruity", onClick = { onClick("Fruity") }) - FlavorCategoryButton("Smoky", onClick = { onClick("Smoky") }) - FlavorCategoryButton("Umami", onClick = { onClick("Umami") }) + uiState.flavors.forEach { flavor -> + FlavorCategoryButton(flavor, onClick = { onClick(flavor) }) + } } } CreditText(modifier = Modifier.align(Alignment.BottomCenter)) @@ -71,6 +86,6 @@ private fun RowScope.FlavorCategoryButton( modifier: Modifier = Modifier, ) { Button(modifier = modifier.weight(1f), onClick = onClick) { - Text(text) + Text(text, style = MaterialTheme.typography.h6) } } diff --git a/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/home/HomeViewModel.kt b/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/home/HomeViewModel.kt index 9e63528..d3679c8 100644 --- a/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/home/HomeViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/home/HomeViewModel.kt @@ -1,12 +1,28 @@ package com.aslansari.spiritvisor.home +import androidx.lifecycle.viewModelScope import com.aslansari.spiritvisor.BaseViewModel import com.aslansari.spiritvisor.UIState +import com.aslansari.spiritvisor.cocktail.CocktailDTO +import com.aslansari.spiritvisor.cocktail.CocktailService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch class HomeViewModel : BaseViewModel() { + val cocktailService = CocktailService() + var cocktailsByFlavor: Map> = emptyMap() override fun createInitialState(): HomeUiState = HomeUiState() + init { + viewModelScope.launch(Dispatchers.Default) { + setState { copy(loading = true) } + cocktailsByFlavor = cocktailService.fetchCocktailsByFlavor().flavors + setState { copy(flavors = cocktailsByFlavor.keys.toList()) } + setState { copy(loading = false) } + } + } } data class HomeUiState( - val title: String = "Home" + val loading: Boolean = false, + val flavors: List = emptyList(), ) : UIState \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/theme/icon/LocalBar.kt b/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/theme/icon/LocalBar.kt new file mode 100644 index 0000000..5861396 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/aslansari/spiritvisor/theme/icon/LocalBar.kt @@ -0,0 +1,74 @@ +package com.aslansari.spiritvisor.theme.icon + +import androidx.compose.foundation.Image +import androidx.compose.material.icons.Icons +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.group +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Preview +@Composable +private fun VectorPreview() { + Image(Icons.LocalBar, null) +} + +private var _LocalBar: ImageVector? = null + +internal val Icons.LocalBar: ImageVector + get() { + if (_LocalBar != null) { + return _LocalBar!! + } + _LocalBar = ImageVector.Builder( + name = "LocalBar", + defaultWidth = 25.dp, + defaultHeight = 25.dp, + viewportWidth = 25f, + viewportHeight = 25f + ).apply { + group { + path( + fill = SolidColor(Color(0xFF1C1B1F)), + fillAlpha = 1.0f, + stroke = null, + strokeAlpha = 1.0f, + strokeLineWidth = 1.0f, + strokeLineCap = StrokeCap.Butt, + strokeLineJoin = StrokeJoin.Miter, + strokeLineMiter = 1.0f, + pathFillType = PathFillType.NonZero + ) { + moveTo(6.66943f, 21.9361f) + verticalLineTo(19.9361f) + horizontalLineTo(11.6694f) + verticalLineTo(14.9361f) + lineTo(3.66943f, 5.9361f) + verticalLineTo(3.9361f) + horizontalLineTo(21.6694f) + verticalLineTo(5.9361f) + lineTo(13.6694f, 14.9361f) + verticalLineTo(19.9361f) + horizontalLineTo(18.6694f) + verticalLineTo(21.9361f) + horizontalLineTo(6.66943f) + close() + moveTo(8.11943f, 7.9361f) + horizontalLineTo(17.2194f) + lineTo(19.0194f, 5.9361f) + horizontalLineTo(6.31943f) + lineTo(8.11943f, 7.9361f) + close() + moveTo(12.6694f, 13.0361f) + lineTo(15.4444f, 9.9361f) + horizontalLineTo(9.89443f) + lineTo(12.6694f, 13.0361f) + close() + } + } + }.build() + return _LocalBar!! + } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1b58652..44daa13 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,6 +23,7 @@ logback = "1.5.6" kotlinx-datetime = "0.6.0-RC.2" kotlinx-serialization-json = "1.6.2" coroutines = "1.8.1" +windowsize-multiplatform = "0.5.0" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -57,6 +58,7 @@ coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3" coil-compose-core = { module = "io.coil-kt.coil3:coil-compose-core", version.ref = "coil3" } coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor", version.ref = "coil3" } coil-mp = { module = "io.coil-kt.coil3:coil", version.ref = "coil3" } +windowsize-multiplatform = { module = "dev.chrisbanes.material3:material3-window-size-class-multiplatform", version.ref = "windowsize-multiplatform" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" }