Skip to content
Snippets Groups Projects
Unverified Commit 09b02dc9 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

refactor: purchase route

parent dec73811
No related branches found
No related tags found
No related merge requests found
Showing with 478 additions and 263 deletions
......@@ -46,6 +46,7 @@ import de.chaosdorf.meteroid.theme.MeteroidTheme
import de.chaosdorf.meteroid.ui.*
import de.chaosdorf.meteroid.ui.common.BottomBar
import de.chaosdorf.meteroid.ui.common.TopBar
import de.chaosdorf.meteroid.ui.purchase.PurchaseRoute
import de.chaosdorf.meteroid.viewmodel.NavigationViewModel
import de.chaosdorf.meteroid.viewmodel.*
import kotlinx.coroutines.flow.update
......
......@@ -26,6 +26,7 @@ package de.chaosdorf.meteroid.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.luminance
......@@ -92,7 +93,7 @@ val md_theme_dark_surfaceTint = Color(0xFFAFC6FF)
val md_theme_dark_outlineVariant = Color(0xFF44474F)
val md_theme_dark_scrim = Color(0xFF000000)
@Stable
val ColorScheme.secondaryGradient
get() = ThemeGradient(
listOf(
......
......@@ -24,12 +24,14 @@
package de.chaosdorf.meteroid.theme
import androidx.compose.runtime.Immutable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TileMode
class ThemeGradient(val colors: List<Color>) {
@Immutable
data class ThemeGradient(val colors: List<Color>) {
fun linearGradient(
start: Offset = Offset.Zero,
end: Offset = Offset.Infinite,
......
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2025 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.rememberAsyncImagePainter
import de.chaosdorf.meteroid.model.Drink
import de.chaosdorf.meteroid.theme.onPrimaryContainerTinted
import de.chaosdorf.meteroid.theme.secondaryGradient
import de.chaosdorf.meteroid.ui.common.PriceBadge
import de.chaosdorf.meteroid.viewmodel.Navigator
import de.chaosdorf.meteroid.viewmodel.PurchaseViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.update
import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import java.math.BigDecimal
import java.util.*
@Composable
fun PurchaseRoute(
viewModel: PurchaseViewModel,
navigator: Navigator,
contentPadding: PaddingValues,
) {
val drinks by viewModel.drinks.collectAsState()
val filters by viewModel.filters.collectAsState()
LazyVerticalGrid(
GridCells.Adaptive(104.dp),
contentPadding = contentPadding,
modifier = Modifier.padding(horizontal = 8.dp),
) {
item("filter", span = { GridItemSpan(maxLineSpan) }) {
FlowRow(
modifier = Modifier.padding(horizontal = 12.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
PurchaseFilterChip(
label = "Active",
selected = filters.contains(PurchaseViewModel.Filter.Active),
onClick = { viewModel.toggleFilter(PurchaseViewModel.Filter.Active) }
)
PurchaseFilterChip(
label = "Coffeine Free",
selected = filters.contains(PurchaseViewModel.Filter.CaffeineFree),
onClick = { viewModel.toggleFilter(PurchaseViewModel.Filter.CaffeineFree) }
)
}
}
item("wrapped", span = { GridItemSpan(maxLineSpan) }) {
Surface(
color = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainerTinted,
shape = RoundedCornerShape(8.dp),
modifier = Modifier.padding(vertical = 8.dp, horizontal = 12.dp)
) {
Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 8.dp)) {
Spacer(Modifier.width(12.dp))
Column(verticalArrangement = Arrangement.Center) {
val now = Clock.System.now().toLocalDateTime(TimeZone.UTC)
Text(
"Your ${now.year} Wrapped is here",
style = MaterialTheme.typography.bodyLarge,
)
Text(
"Jump into your year in beverages",
style = MaterialTheme.typography.bodyMedium,
)
}
Spacer(Modifier.width(8.dp))
Button(
onClick = {
navigator.backStack.update {
it.plus(MeteroidRoute.Wrapped(viewModel.serverId, viewModel.userId))
}
},
) {
Text("Let's go")
}
Spacer(Modifier.width(4.dp))
}
}
}
items(
drinks,
key = { "drink-${it.serverId}-${it.drinkId}" },
) { drink ->
PurchaseDrinkTile(drink) { item, count ->
viewModel.purchase(item, count, onBack = {})
}
}
}
}
@Composable
fun PurchaseFilterChip(
label: String,
selected: Boolean,
onClick: () -> Unit,
) {
FilterChip(
label = {
Text(label, style = MaterialTheme.typography.labelLarge)
},
selected = selected,
leadingIcon = {
if (selected) {
Icon(
Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(18.dp)
)
}
},
onClick = onClick,
colors = FilterChipDefaults.filterChipColors(
selectedContainerColor = MaterialTheme.colorScheme.secondaryContainer
)
)
}
@Composable
fun PurchaseDrinkTile(
item: Drink,
modifier: Modifier = Modifier,
onPurchase: (Drink, Int) -> Unit = { _, _ -> }
) {
var purchaseCount by remember { mutableIntStateOf(0) }
val pendingPurchases = purchaseCount != 0
LaunchedEffect(purchaseCount) {
delay(2000L)
onPurchase(item, purchaseCount)
purchaseCount = 0
}
val thumbPainter = rememberAsyncImagePainter(
item.logoUrl
)
val drinkPainter = rememberAsyncImagePainter(
item.originalLogoUrl,
error = thumbPainter
)
Column(
modifier = modifier
.height(IntrinsicSize.Max)
.alpha(if (item.active) 1.0f else 0.67f)
.clip(RoundedCornerShape(8.dp))
.clickable { purchaseCount += 1 }
.padding(8.dp)
) {
Box(
Modifier
.aspectRatio(1.0f)
.background(MaterialTheme.colorScheme.secondaryGradient.verticalGradient(), CircleShape),
contentAlignment = Alignment.Center
) {
Image(
drinkPainter,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.alpha(if (pendingPurchases) 0.0f else 1.0f)
.clip(CircleShape)
)
PriceBadge(
item.price,
modifier = Modifier
.alpha(if (pendingPurchases) 0.0f else 1.0f)
.align(Alignment.BottomEnd)
.paddingFromBaseline(bottom = 12.dp)
)
Text(
"×$purchaseCount",
fontSize = 36.sp,
fontWeight = FontWeight.Light,
color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.67f),
textAlign = TextAlign.Center,
modifier = Modifier.alpha(if (pendingPurchases) 1.0f else 0.0f)
)
}
Spacer(Modifier.height(4.dp))
Text(
item.name,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
textAlign = TextAlign.Center,
fontWeight = FontWeight.SemiBold,
style = MaterialTheme.typography.labelLarge,
)
Spacer(Modifier.height(4.dp))
Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
val unitPrice =
if (item.volume <= BigDecimal.ZERO) null
else item.price / item.volume
Text(
if (unitPrice == null) String.format(Locale.getDefault(), "%.02fl", item.volume)
else String.format(Locale.getDefault(), "%.02fl · %.02f€/l", item.volume, item.price / item.volume),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
textAlign = TextAlign.Center,
fontWeight = FontWeight.SemiBold,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
)
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2025 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.purchase
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.paddingFromBaseline
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.rememberAsyncImagePainter
import de.chaosdorf.meteroid.model.Drink
import de.chaosdorf.meteroid.theme.secondaryGradient
import de.chaosdorf.meteroid.ui.common.PriceBadge
import kotlinx.coroutines.delay
import java.math.BigDecimal
import java.util.Locale
@Composable
fun PurchaseDrinkTile(
item: Drink,
modifier: Modifier = Modifier,
onPurchase: (Drink, Int) -> Unit = { _, _ -> }
) {
var purchaseCount by remember { mutableIntStateOf(0) }
val pendingPurchases by remember {
derivedStateOf {
purchaseCount != 0
}
}
LaunchedEffect(purchaseCount) {
delay(2000L)
onPurchase(item, purchaseCount)
purchaseCount = 0
}
val thumbPainter = rememberAsyncImagePainter(
item.logoUrl
)
val drinkPainter = rememberAsyncImagePainter(
item.originalLogoUrl,
error = thumbPainter
)
val contentAlpha = remember(item) {
if (item.active) 1.0f else 0.67f
}
Column(
modifier = modifier
.height(IntrinsicSize.Max)
.clip(RoundedCornerShape(8.dp))
.clickable { purchaseCount += 1 }
.padding(8.dp)
) {
Box(
Modifier.Companion
.aspectRatio(1.0f)
.background(
brush = MaterialTheme.colorScheme.secondaryGradient.verticalGradient(),
alpha = contentAlpha,
shape = CircleShape,
),
contentAlignment = Alignment.Companion.Center
) {
Box(
modifier = Modifier.Companion
.graphicsLayer {
clip = true
alpha = if (pendingPurchases) 0.0f else contentAlpha
}
) {
Image(
drinkPainter,
contentDescription = null,
contentScale = ContentScale.Companion.Fit,
modifier = Modifier.Companion
.clip(CircleShape)
)
PriceBadge(
item.price,
modifier = Modifier.Companion
.align(Alignment.Companion.BottomEnd)
.paddingFromBaseline(bottom = 12.dp)
)
}
Text(
"×$purchaseCount",
fontSize = 36.sp,
fontWeight = FontWeight.Companion.Light,
color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.67f),
textAlign = TextAlign.Companion.Center,
modifier = Modifier.Companion
.graphicsLayer {
clip = true
alpha = if (pendingPurchases) contentAlpha else 0.0f
}
)
}
Spacer(Modifier.Companion.height(4.dp))
Text(
item.name,
modifier = Modifier.Companion
.fillMaxWidth()
.padding(horizontal = 8.dp),
textAlign = TextAlign.Companion.Center,
fontWeight = FontWeight.Companion.SemiBold,
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = contentAlpha)
)
Spacer(Modifier.Companion.height(4.dp))
Row(modifier = Modifier.Companion.align(Alignment.Companion.CenterHorizontally)) {
val label = remember(item) {
val unitPrice =
if (item.volume <= BigDecimal.ZERO) null
else item.price / item.volume
if (unitPrice == null) String.Companion.format(Locale.getDefault(), "%.02fl", item.volume)
else String.Companion.format(
Locale.getDefault(),
"%.02fl · %.02f€/l",
item.volume,
item.price / item.volume
)
}
Text(
label,
modifier = Modifier.Companion
.fillMaxWidth()
.padding(horizontal = 8.dp),
textAlign = TextAlign.Companion.Center,
fontWeight = FontWeight.Companion.SemiBold,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f * contentAlpha)
)
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2025 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.purchase
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun PurchaseFilterChip(
label: String,
selected: Boolean,
onClick: () -> Unit,
) {
FilterChip(
label = {
Text(label, style = MaterialTheme.typography.labelLarge)
},
selected = selected,
leadingIcon = {
if (selected) {
Icon(
Icons.Default.Check,
contentDescription = null,
modifier = Modifier.Companion.size(18.dp)
)
}
},
onClick = onClick,
colors = FilterChipDefaults.filterChipColors(
selectedContainerColor = MaterialTheme.colorScheme.secondaryContainer
)
)
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2025 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.purchase
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import de.chaosdorf.meteroid.viewmodel.PurchaseViewModel
@Preview(showBackground = true)
@Composable
fun PurchaseFilterRow(
filters: Set<PurchaseViewModel.Filter> = emptySet(),
toggleFilter: (filter: PurchaseViewModel.Filter) -> Unit = {},
) {
FlowRow(
modifier = Modifier.Companion.padding(horizontal = 12.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
PurchaseFilterChip(
label = "Active",
selected = filters.contains(PurchaseViewModel.Filter.Active),
onClick = { toggleFilter(PurchaseViewModel.Filter.Active) }
)
PurchaseFilterChip(
label = "Coffeine Free",
selected = filters.contains(PurchaseViewModel.Filter.CaffeineFree),
onClick = { toggleFilter(PurchaseViewModel.Filter.CaffeineFree) }
)
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2025 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.purchase
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import de.chaosdorf.meteroid.ui.MeteroidRoute
import de.chaosdorf.meteroid.viewmodel.Navigator
import de.chaosdorf.meteroid.viewmodel.PurchaseViewModel
import kotlinx.coroutines.flow.update
@Composable
fun PurchaseRoute(
viewModel: PurchaseViewModel,
navigator: Navigator,
contentPadding: PaddingValues,
) {
val drinks by viewModel.drinks.collectAsState()
val filters by viewModel.filters.collectAsState()
LazyVerticalGrid(
GridCells.Adaptive(104.dp),
contentPadding = contentPadding,
modifier = Modifier.padding(horizontal = 8.dp),
) {
item("filter", span = { GridItemSpan(maxLineSpan) }) {
PurchaseFilterRow(filters, viewModel::toggleFilter)
}
item("wrapped", span = { GridItemSpan(maxLineSpan) }) {
WrappedBanner(onClick = {
navigator.backStack.update {
it.plus(MeteroidRoute.Wrapped(viewModel.serverId, viewModel.userId))
}
})
}
items(
drinks,
key = { "drink-${it.serverId}-${it.drinkId}" },
) { drink ->
PurchaseDrinkTile(drink) { item, count ->
viewModel.purchase(item, count, onBack = {})
}
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2025 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.purchase
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import de.chaosdorf.meteroid.theme.onPrimaryContainerTinted
import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
@Preview(showBackground = true)
@Composable
fun WrappedBanner(
onClick: () -> Unit = {}
) {
Surface(
color = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainerTinted,
shape = RoundedCornerShape(8.dp),
modifier = Modifier.Companion.padding(vertical = 8.dp, horizontal = 12.dp)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Companion.CenterVertically,
modifier = Modifier.Companion.padding(vertical = 8.dp)
) {
Spacer(Modifier.Companion.width(12.dp))
Column(verticalArrangement = Arrangement.Center) {
val now = Clock.System.now().toLocalDateTime(TimeZone.Companion.UTC)
Text(
"Your ${now.year} Wrapped is here",
style = MaterialTheme.typography.bodyLarge,
)
Text(
"Jump into your year in beverages",
style = MaterialTheme.typography.bodyMedium,
)
}
Spacer(Modifier.Companion.width(8.dp))
Button(onClick = onClick) {
Text("Let's go")
}
Spacer(Modifier.Companion.width(4.dp))
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment