From 7efc3a32e8de1ab3cd276755cda5bf1825f0b228 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski <mail@justjanne.de> Date: Tue, 10 Jun 2025 03:42:59 +0200 Subject: [PATCH] refactor: navigation --- .../de/chaosdorf/meteroid/MainActivity.kt | 4 +- .../meteroid/ui/common/PriceBadge.kt | 18 +- .../de/chaosdorf/meteroid/ui/common/TopBar.kt | 315 ------------------ .../ui/navigation/NavigationAddServerItem.kt | 17 +- .../ui/navigation/NavigationServerItem.kt | 25 +- .../ui/navigation/NavigationServerListItem.kt | 26 +- .../ui/navigation/NavigationSettingsItem.kt | 17 +- .../ui/navigation/NavigationUserItem.kt | 53 ++- .../ui/navigation/NavigationUserListItem.kt | 18 +- .../ui/navigation/OverlayNavigation.kt | 207 ++++++++++++ .../ui/navigation/PersistentNavigation.kt | 135 ++++++++ .../meteroid/ui/navigation/TopNavigation.kt | 70 ++++ .../meteroid/ui/purchase/PurchaseDrinkTile.kt | 20 +- .../ui/purchase/PurchaseFilterChip.kt | 2 +- .../meteroid/ui/purchase/PurchaseFilterRow.kt | 2 +- .../meteroid/ui/purchase/PurchaseRoute.kt | 1 + .../ui/{purchase => wrapped}/WrappedBanner.kt | 12 +- .../meteroid/viewmodel/NavigationViewModel.kt | 4 +- 18 files changed, 558 insertions(+), 388 deletions(-) delete mode 100644 app/src/main/kotlin/de/chaosdorf/meteroid/ui/common/TopBar.kt create mode 100644 app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/OverlayNavigation.kt create mode 100644 app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/PersistentNavigation.kt create mode 100644 app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/TopNavigation.kt rename app/src/main/kotlin/de/chaosdorf/meteroid/ui/{purchase => wrapped}/WrappedBanner.kt (89%) diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/MainActivity.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/MainActivity.kt index a4ea473..15ee94a 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/MainActivity.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/MainActivity.kt @@ -45,7 +45,7 @@ import dagger.hilt.android.lifecycle.withCreationCallback 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.navigation.TopNavigation import de.chaosdorf.meteroid.ui.purchase.PurchaseRoute import de.chaosdorf.meteroid.viewmodel.NavigationViewModel import de.chaosdorf.meteroid.viewmodel.* @@ -84,7 +84,7 @@ class MainActivity : ComponentActivity() { val backStack by navigationViewModel.backStack.collectAsState() Scaffold( - topBar = { TopBar(navigationViewModel) }, + topBar = { TopNavigation(navigationViewModel) }, bottomBar = { BottomBar(navigationViewModel) } ) { paddingValues -> NavDisplay( diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/common/PriceBadge.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/common/PriceBadge.kt index 73317da..f78c93b 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/common/PriceBadge.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/common/PriceBadge.kt @@ -29,8 +29,8 @@ import androidx.compose.material3.Badge import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import java.math.BigDecimal @@ -39,22 +39,24 @@ import java.math.BigDecimal fun PriceBadge( price: BigDecimal, modifier: Modifier = Modifier, - containerColor: Color = - if (price >= BigDecimal.ZERO) MaterialTheme.colorScheme.primary - else MaterialTheme.colorScheme.error, - textColor: Color = - if (price >= BigDecimal.ZERO) MaterialTheme.colorScheme.onPrimary - else MaterialTheme.colorScheme.onError, textStyle: TextStyle = MaterialTheme.typography.labelLarge ) { + val positive = remember(price) { price >= BigDecimal.ZERO } + + val containerColor = if (positive) MaterialTheme.colorScheme.primary + else MaterialTheme.colorScheme.error + + val textColor = if (positive) MaterialTheme.colorScheme.onPrimary + else MaterialTheme.colorScheme.onError + Badge( containerColor = containerColor, + contentColor = textColor, modifier = modifier ) { Text( "%.02f €".format(price), style = textStyle, - color = textColor, modifier = Modifier.padding(8.dp, 4.dp) ) } diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/common/TopBar.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/common/TopBar.kt deleted file mode 100644 index 0b8f26f..0000000 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/common/TopBar.kt +++ /dev/null @@ -1,315 +0,0 @@ -/* - * 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.common - -import android.annotation.SuppressLint -import androidx.activity.compose.BackHandler -import androidx.compose.animation.* -import androidx.compose.animation.core.animateDp -import androidx.compose.animation.core.updateTransition -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Star -import androidx.compose.material.icons.filled.StarOutline -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.TileMode -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Popup -import androidx.compose.ui.window.PopupProperties -import de.chaosdorf.meteroid.ui.MeteroidRoute -import de.chaosdorf.meteroid.ui.MeteroidRoute.Purchase -import de.chaosdorf.meteroid.ui.MeteroidRoute.UserList -import de.chaosdorf.meteroid.ui.navigation.* -import de.chaosdorf.meteroid.viewmodel.NavigationElement -import de.chaosdorf.meteroid.viewmodel.NavigationViewModel -import kotlinx.coroutines.flow.update - -@SuppressLint("UnusedTransitionTargetStateParameter") -@OptIn(ExperimentalSharedTransitionApi::class) -@Composable -fun TopBar( - viewModel: NavigationViewModel, -) { - val backStack = viewModel.backStack.collectAsState().value - val currentRoute = backStack.lastOrNull() - - val entries by viewModel.entries.collectAsState() - val currentEntry = entries.firstOrNull { it.isCurrent(currentRoute) } - var open by remember { mutableStateOf(false) } - val transition = updateTransition(open, label = "transition") - val shadowElevation by transition.animateDp { if (it) 4.dp else 0.dp } - - if (currentEntry != null) { - Box( - Modifier.background( - Brush.linearGradient( - listOf( - MaterialTheme.colorScheme.background, - MaterialTheme.colorScheme.background.copy(alpha = 0f), - ), - start = Offset(0.0f, 0.0f), - end = Offset(0.0f, Float.POSITIVE_INFINITY), - tileMode = TileMode.Clamp, - ) - ) - ) { - Surface( - modifier = Modifier - .windowInsetsPadding(WindowInsets.statusBars) - .padding(8.dp), - shape = RoundedCornerShape(26.dp), - shadowElevation = 4.dp - shadowElevation, - tonalElevation = 4.dp, - ) { - when (currentEntry) { - NavigationElement.ServerListElement -> - NavigationServerListItem( - modifier = Modifier - .requiredHeight(56.dp) - .clickable { - open = true - }, - ) - - is NavigationElement.ServerElement -> - NavigationServerItem( - currentEntry.server, - modifier = Modifier - .requiredHeight(56.dp) - .clickable { - open = true - }, - ) - - is NavigationElement.UserElement -> - NavigationUserItem( - currentEntry.user, - modifier = Modifier - .requiredHeight(56.dp) - .clickable { - open = true - }, - ) { - IconButton(onClick = { viewModel.togglePin(currentEntry.user.serverId, currentEntry.user.userId) }) { - Icon( - if (currentEntry.pinned) Icons.Default.Star else Icons.Default.StarOutline, - contentDescription = null - ) - } - } - - is NavigationElement.UserListElement -> - NavigationUserListItem( - modifier = Modifier - .requiredHeight(56.dp) - .clickable { - open = true - }, - ) - - NavigationElement.AddServerElement -> - NavigationAddServerItem( - modifier = Modifier - .requiredHeight(56.dp) - .clickable { - open = true - }, - ) - - NavigationElement.SettingsElement -> - NavigationSettingsItem( - modifier = Modifier - .requiredHeight(56.dp) - .clickable { - open = true - }, - ) - } - } - } - - if (open || transition.currentState || transition.isRunning) { - Popup( - offset = IntOffset( - x = WindowInsets.safeDrawing.getLeft(LocalDensity.current, LocalLayoutDirection.current), - y = WindowInsets.safeDrawing.getTop(LocalDensity.current), - ), - onDismissRequest = { open = false }, - properties = PopupProperties( - usePlatformDefaultWidth = false, - dismissOnClickOutside = true, - dismissOnBackPress = true, - clippingEnabled = false, - ), - ) { - BackHandler { - open = false - } - - val smallItemHeight by transition.animateDp { if (it) 48.dp else 56.dp } - val itemHeight by transition.animateDp { 56.dp } - - Box( - modifier = Modifier - .fillMaxSize() - .clickable(interactionSource = null, indication = null) { - open = false - } - ) { - Surface( - modifier = Modifier.padding(8.dp), - shape = RoundedCornerShape(26.dp), - shadowElevation = shadowElevation, - tonalElevation = 4.dp, - ) { - Column { - for (entry in entries) { - key(entry.key) { - transition.AnimatedVisibility( - visible = { it || currentEntry == entry }, - enter = expandVertically() + fadeIn(), - exit = shrinkVertically() + fadeOut(), - ) { - when (entry) { - NavigationElement.ServerListElement -> - NavigationServerListItem( - modifier = Modifier - .requiredHeight(itemHeight) - .clickable { - viewModel.backStack.update { - listOf(MeteroidRoute.ServerList) - } - open = false - }, - ) - - is NavigationElement.ServerElement -> - NavigationServerItem( - server = entry.server, - modifier = Modifier - .requiredHeight(itemHeight) - .clickable { - viewModel.backStack.update { - listOf(MeteroidRoute.ServerList, UserList(entry.server.serverId)) - } - open = false - }, - ) - - is NavigationElement.UserElement -> - NavigationUserItem( - user = entry.user, - modifier = Modifier - .requiredHeight(itemHeight) - .clickable { - viewModel.backStack.update { - listOf( - MeteroidRoute.ServerList, - UserList(entry.user.serverId), - Purchase(entry.user.serverId, entry.user.userId) - ) - } - open = false - }, - ) { - transition.AnimatedVisibility( - visible = { !it && currentEntry == entry }, - enter = fadeIn(), - exit = fadeOut(), - ) { - IconButton(onClick = { viewModel.togglePin(entry.user.serverId, entry.user.userId) }) { - Icon( - if (entry.pinned) Icons.Default.Star else Icons.Default.StarOutline, - contentDescription = null - ) - } - } - } - - is NavigationElement.UserListElement -> - NavigationUserListItem( - modifier = Modifier - .requiredHeight(smallItemHeight) - .clickable { - viewModel.backStack.update { - listOf(MeteroidRoute.ServerList, UserList(entry.server.serverId)) - } - open = false - }, - ) - - NavigationElement.AddServerElement -> - NavigationAddServerItem( - modifier = Modifier - .requiredHeight(smallItemHeight) - .clickable { - viewModel.backStack.update { - it.plus(MeteroidRoute.Setup) - } - open = false - }, - ) - - NavigationElement.SettingsElement -> - NavigationSettingsItem( - modifier = Modifier - .requiredHeight(smallItemHeight) - .clickable { - viewModel.backStack.update { - it.plus(MeteroidRoute.Settings) - } - open = false - }, - ) - } - } - if (entry is NavigationElement.UserListElement || entry is NavigationElement.ServerListElement) { - transition.AnimatedVisibility( - visible = { it }, - enter = expandVertically() + fadeIn(), - exit = shrinkVertically() + fadeOut(), - ) { - HorizontalDivider() - } - } - } - } - } - } - } - } - } - } -} diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationAddServerItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationAddServerItem.kt index 36679ef..13e3e58 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationAddServerItem.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationAddServerItem.kt @@ -24,6 +24,8 @@ package de.chaosdorf.meteroid.ui.navigation +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.animateDp import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add @@ -38,8 +40,19 @@ import androidx.compose.ui.unit.dp import de.chaosdorf.meteroid.ui.common.AvatarLayout @Composable -fun NavigationAddServerItem(modifier: Modifier = Modifier) { - Row(modifier.padding(horizontal = 12.dp), verticalAlignment = Alignment.CenterVertically) { +fun NavigationAddServerItem( + modifier: Modifier = Modifier, + active: Boolean = false, + transition: Transition<Boolean>? = null, +) { + val itemHeight = transition?.animateDp { if (it && !active) 48.dp else 56.dp }?.value ?: 56.dp + + Row( + modifier + .requiredHeight(itemHeight) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { AvatarLayout { Icon(Icons.Default.Add, contentDescription = null) } diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationServerItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationServerItem.kt index fde8d7f..dcde41d 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationServerItem.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationServerItem.kt @@ -24,16 +24,11 @@ package de.chaosdorf.meteroid.ui.navigation -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.material3.IconButton -import androidx.compose.material3.ListItem +import androidx.compose.foundation.layout.* import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow @@ -44,10 +39,20 @@ import de.chaosdorf.meteroid.util.humanReadableHost import okhttp3.HttpUrl.Companion.toHttpUrl @Composable -fun NavigationServerItem(server: Server, modifier: Modifier = Modifier) { - val host = humanReadableHost(server.url.toHttpUrl()) +fun NavigationServerItem( + server: Server, + modifier: Modifier = Modifier, +) { + val host = remember(server) { + humanReadableHost(server.url.toHttpUrl()) + } - Row(modifier.padding(horizontal = 12.dp), verticalAlignment = Alignment.CenterVertically) { + Row( + modifier = modifier + .requiredHeight(56.dp) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { ServerAvatar(server.logoUrl) Spacer(Modifier.width(16.dp)) Column(Modifier.weight(1f, true)) { diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationServerListItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationServerListItem.kt index ee34bb8..5ceced3 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationServerListItem.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationServerListItem.kt @@ -26,38 +26,36 @@ package de.chaosdorf.meteroid.ui.navigation import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Icon -import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.imageResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import de.chaosdorf.meteroid.ui.common.ServerAvatar import de.chaosdorf.meteroid.R import de.chaosdorf.meteroid.ui.common.AvatarLayout +@Preview(showBackground = true) @Composable -fun NavigationServerListItem(modifier: Modifier = Modifier) { +fun NavigationServerListItem( + modifier: Modifier = Modifier, +) { val icon = ImageVector.vectorResource(R.drawable.ic_launcher) - Row(modifier.padding(horizontal = 12.dp), verticalAlignment = Alignment.CenterVertically) { + Row( + modifier = modifier + .requiredHeight(56.dp) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { AvatarLayout( Modifier .clip(CircleShape) diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationSettingsItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationSettingsItem.kt index b7ba018..faff304 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationSettingsItem.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationSettingsItem.kt @@ -24,6 +24,8 @@ package de.chaosdorf.meteroid.ui.navigation +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.animateDp import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Settings @@ -38,8 +40,19 @@ import androidx.compose.ui.unit.dp import de.chaosdorf.meteroid.ui.common.AvatarLayout @Composable -fun NavigationSettingsItem(modifier: Modifier = Modifier) { - Row(modifier.padding(horizontal = 12.dp), verticalAlignment = Alignment.CenterVertically) { +fun NavigationSettingsItem( + modifier: Modifier = Modifier, + active: Boolean = false, + transition: Transition<Boolean>? = null, +) { + val itemHeight = transition?.animateDp { if (it && !active) 48.dp else 56.dp }?.value ?: 56.dp + + Row( + modifier = modifier + .requiredHeight(itemHeight) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { AvatarLayout { Icon(Icons.Default.Settings, contentDescription = null) } diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserItem.kt index 684450e..4517b81 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserItem.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserItem.kt @@ -24,13 +24,11 @@ package de.chaosdorf.meteroid.ui.navigation -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.requiredHeight -import androidx.compose.foundation.layout.width +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.Transition +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.filled.StarOutline @@ -39,12 +37,11 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import de.chaosdorf.mete.model.ServerId -import de.chaosdorf.mete.model.UserId import de.chaosdorf.meteroid.model.User import de.chaosdorf.meteroid.ui.common.PriceBadge import de.chaosdorf.meteroid.ui.common.UserAvatar @@ -52,10 +49,18 @@ import de.chaosdorf.meteroid.ui.common.UserAvatar @Composable fun NavigationUserItem( user: User, + active: Boolean, + pinned: Boolean, modifier: Modifier = Modifier, - actions: @Composable () -> Unit = {}, + transition: Transition<Boolean>? = null, + onPin: (User) -> Unit = {}, ) { - Row(modifier.padding(horizontal = 12.dp), verticalAlignment = Alignment.CenterVertically) { + Row( + modifier = modifier + .requiredHeight(56.dp) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { UserAvatar(user.gravatarUrl) Spacer(Modifier.width(16.dp)) Column(Modifier.weight(1f, true)) { @@ -77,7 +82,31 @@ fun NavigationUserItem( } } Spacer(Modifier.width(16.dp)) - actions() + if (transition != null) { + transition.AnimatedVisibility( + visible = { !it && active }, + enter = fadeIn(), + exit = fadeOut(), + ) { + PinButton(pinned) { onPin(user) } + } + } else { + PinButton(pinned) { onPin(user) } + } PriceBadge(user.balance) } } + +@Composable +private fun PinButton( + pinned: Boolean, + onPin: () -> Unit, +) { + val pinIcon = remember(pinned) { + if (pinned) Icons.Default.Star else Icons.Default.StarOutline + } + + IconButton(onClick = onPin) { + Icon(pinIcon, contentDescription = "Pin") + } +} diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserListItem.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserListItem.kt index da5f590..244d2a2 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserListItem.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/NavigationUserListItem.kt @@ -24,14 +24,16 @@ package de.chaosdorf.meteroid.ui.navigation +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.animateDp import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Group -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow @@ -39,8 +41,18 @@ import androidx.compose.ui.unit.dp import de.chaosdorf.meteroid.ui.common.AvatarLayout @Composable -fun NavigationUserListItem(modifier: Modifier = Modifier) { - Row(modifier.padding(horizontal = 12.dp), verticalAlignment = Alignment.CenterVertically) { +fun NavigationUserListItem( + modifier: Modifier = Modifier, + transition: Transition<Boolean>? = null, +) { + val itemHeight = transition?.animateDp { if (it) 48.dp else 56.dp }?.value ?: 56.dp + + Row( + modifier = modifier + .requiredHeight(itemHeight) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { AvatarLayout { Icon(Icons.Default.Group, contentDescription = null) } diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/OverlayNavigation.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/OverlayNavigation.kt new file mode 100644 index 0000000..9c89600 --- /dev/null +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/OverlayNavigation.kt @@ -0,0 +1,207 @@ +/* + * 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.navigation + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.* +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.animateDp +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Surface +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties +import de.chaosdorf.meteroid.ui.MeteroidRoute +import de.chaosdorf.meteroid.viewmodel.NavigationElement +import de.chaosdorf.meteroid.viewmodel.NavigationViewModel +import kotlinx.coroutines.flow.update + +@Composable +fun OverlayNavigation( + viewModel: NavigationViewModel, + transition: Transition<Boolean>, + onClose: () -> Unit, +) { + val backStack = viewModel.backStack.collectAsState() + val entries = viewModel.entries.collectAsState() + val currentEntry = remember { + derivedStateOf { + entries.value.firstOrNull { it.isCurrent(backStack.value.lastOrNull()) } + } + } + + Popup( + onDismissRequest = onClose, + properties = PopupProperties( + usePlatformDefaultWidth = false, + dismissOnClickOutside = true, + dismissOnBackPress = true, + ), + ) { + val shadowElevation = transition.animateDp { if (it) 4.dp else 0.dp } + + BackHandler(onBack = onClose) + + Box( + modifier = Modifier + .fillMaxSize() + .clickable(interactionSource = null, indication = null, onClick = onClose) + ) { + Surface( + modifier = Modifier + .padding(8.dp) + .graphicsLayer { + this.shadowElevation = shadowElevation.value.toPx() + shape = NavigationShape + clip = false + }, + shape = NavigationShape, + tonalElevation = 4.dp, + ) { + Column { + for (entry in entries.value) { + key(entry.key) { + val active = remember { + derivedStateOf { + currentEntry.value == entry + } + } + transition.AnimatedVisibility( + visible = { it || currentEntry.value == entry }, + enter = expandVertically() + fadeIn(), + exit = shrinkVertically() + fadeOut(), + ) { + when (entry) { + NavigationElement.ServerListElement -> + NavigationServerListItem( + modifier = Modifier + .clickable { + viewModel.backStack.update { + listOf(MeteroidRoute.ServerList) + } + onClose() + }, + ) + + is NavigationElement.ServerElement -> + NavigationServerItem( + server = entry.server, + modifier = Modifier + .clickable { + viewModel.backStack.update { + listOf( + MeteroidRoute.ServerList, + MeteroidRoute.UserList(entry.server.serverId) + ) + } + onClose() + }, + ) + + is NavigationElement.UserElement -> + NavigationUserItem( + user = entry.user, + pinned = entry.pinned, + active = active.value, + transition = transition, + modifier = Modifier.clickable { + viewModel.backStack.update { + listOf( + MeteroidRoute.ServerList, + MeteroidRoute.UserList(entry.user.serverId), + MeteroidRoute.Purchase(entry.user.serverId, entry.user.userId) + ) + } + onClose() + }, + onPin = {}, + ) + + is NavigationElement.UserListElement -> + NavigationUserListItem( + transition = transition, + modifier = Modifier + .clickable { + viewModel.backStack.update { + listOf( + MeteroidRoute.ServerList, + MeteroidRoute.UserList(entry.server.serverId) + ) + } + onClose() + }, + ) + + NavigationElement.AddServerElement -> + NavigationAddServerItem( + transition = transition, + active = active.value, + modifier = Modifier + .clickable { + viewModel.backStack.update { + it.plus(MeteroidRoute.Setup) + } + onClose() + }, + ) + + NavigationElement.SettingsElement -> + NavigationSettingsItem( + transition = transition, + active = active.value, + modifier = Modifier + .clickable { + viewModel.backStack.update { + it.plus(MeteroidRoute.Settings) + } + onClose() + }, + ) + } + } + if (entry is NavigationElement.UserListElement || entry is NavigationElement.ServerListElement) { + transition.AnimatedVisibility( + visible = { it }, + enter = expandVertically() + fadeIn(), + exit = shrinkVertically() + fadeOut(), + ) { + HorizontalDivider() + } + } + } + } + } + } + } + } +} diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/PersistentNavigation.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/PersistentNavigation.kt new file mode 100644 index 0000000..78d873e --- /dev/null +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/PersistentNavigation.kt @@ -0,0 +1,135 @@ +/* + * 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.navigation + +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.animateDp +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.rememberGraphicsLayer +import androidx.compose.ui.unit.dp +import de.chaosdorf.meteroid.viewmodel.NavigationElement +import de.chaosdorf.meteroid.viewmodel.NavigationViewModel + +val NavigationScrim: Brush + @Composable + @Stable + get() = Brush.linearGradient( + listOf( + MaterialTheme.colorScheme.background, + MaterialTheme.colorScheme.background.copy(alpha = 0f), + ), + start = Offset(0.0f, 0.0f), + end = Offset(0.0f, Float.POSITIVE_INFINITY), + tileMode = TileMode.Companion.Clamp, + ) + +@Composable +fun PersistentNavigation( + viewModel: NavigationViewModel, + transition: Transition<Boolean>, + onOpen: () -> Unit = {}, +) { + val backStack = viewModel.backStack.collectAsState() + val entries = viewModel.entries.collectAsState() + val currentEntry = remember { + derivedStateOf { + entries.value.firstOrNull { it.isCurrent(backStack.value.lastOrNull()) } + } + } + + val shadowElevation = transition.animateDp { if (it) 4.dp else 0.dp } + val persistentShadowElevation = remember { + derivedStateOf { + 4.dp - shadowElevation.value + } + } + + val navigationScrim = NavigationScrim + + Box( + Modifier + .drawWithCache { + onDrawBehind { + drawRect(navigationScrim) + } + } + ) { + Surface( + modifier = Modifier + .windowInsetsPadding(WindowInsets.Companion.statusBars) + .padding(8.dp) + .graphicsLayer { + this.shadowElevation = persistentShadowElevation.value.toPx() + shape = NavigationShape + clip = false + }, + shape = NavigationShape, + tonalElevation = 4.dp, + ) { + when (val route = currentEntry.value) { + NavigationElement.ServerListElement -> NavigationServerListItem( + modifier = Modifier.clickable(onClick = onOpen), + ) + + is NavigationElement.ServerElement -> NavigationServerItem( + route.server, + modifier = Modifier.clickable(onClick = onOpen), + ) + + is NavigationElement.UserElement -> NavigationUserItem( + user = route.user, + active = true, + pinned = route.pinned, + modifier = Modifier.clickable(onClick = onOpen), + onPin = viewModel::togglePin, + ) + + is NavigationElement.UserListElement -> NavigationUserListItem( + modifier = Modifier.clickable(onClick = onOpen), + ) + + NavigationElement.AddServerElement -> NavigationAddServerItem( + modifier = Modifier.clickable(onClick = onOpen), + ) + + NavigationElement.SettingsElement -> NavigationSettingsItem( + modifier = Modifier.clickable(onClick = onOpen), + ) + + null -> Unit + } + } + } +} diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/TopNavigation.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/TopNavigation.kt new file mode 100644 index 0000000..2983094 --- /dev/null +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/TopNavigation.kt @@ -0,0 +1,70 @@ +/* + * 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.navigation + +import android.annotation.SuppressLint +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.* +import androidx.compose.ui.unit.dp +import de.chaosdorf.meteroid.viewmodel.NavigationViewModel + +internal val NavigationShape = RoundedCornerShape(26.dp) + +@SuppressLint("UnusedTransitionTargetStateParameter") +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun TopNavigation( + viewModel: NavigationViewModel, +) { + val backStack by viewModel.backStack.collectAsState() + val entries by viewModel.entries.collectAsState() + val currentEntry by remember { + derivedStateOf { + entries.firstOrNull { it.isCurrent(backStack.lastOrNull()) } + } + } + + var open by remember { mutableStateOf(false) } + val transition = updateTransition(open, label = "transition") + + if (currentEntry != null) { + PersistentNavigation( + viewModel = viewModel, + transition = transition, + onOpen = { open = true }, + ) + + if (open || transition.currentState || transition.isRunning) { + OverlayNavigation( + viewModel = viewModel, + transition = transition, + onClose = { open = false }, + ) + } + } +} + diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseDrinkTile.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseDrinkTile.kt index 00adcbd..2d0f9c7 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseDrinkTile.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseDrinkTile.kt @@ -104,7 +104,7 @@ fun PurchaseDrinkTile( .padding(8.dp) ) { Box( - Modifier.Companion + Modifier .aspectRatio(1.0f) .background( brush = MaterialTheme.colorScheme.secondaryGradient.verticalGradient(), @@ -114,7 +114,7 @@ fun PurchaseDrinkTile( contentAlignment = Alignment.Companion.Center ) { Box( - modifier = Modifier.Companion + modifier = Modifier .graphicsLayer { clip = true alpha = if (pendingPurchases) 0.0f else contentAlpha @@ -124,12 +124,12 @@ fun PurchaseDrinkTile( drinkPainter, contentDescription = null, contentScale = ContentScale.Companion.Fit, - modifier = Modifier.Companion + modifier = Modifier .clip(CircleShape) ) PriceBadge( item.price, - modifier = Modifier.Companion + modifier = Modifier .align(Alignment.Companion.BottomEnd) .paddingFromBaseline(bottom = 12.dp) ) @@ -140,17 +140,17 @@ fun PurchaseDrinkTile( fontWeight = FontWeight.Companion.Light, color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.67f), textAlign = TextAlign.Companion.Center, - modifier = Modifier.Companion + modifier = Modifier .graphicsLayer { clip = true alpha = if (pendingPurchases) contentAlpha else 0.0f } ) } - Spacer(Modifier.Companion.height(4.dp)) + Spacer(Modifier.height(4.dp)) Text( item.name, - modifier = Modifier.Companion + modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), textAlign = TextAlign.Companion.Center, @@ -158,8 +158,8 @@ fun PurchaseDrinkTile( 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)) { + Spacer(Modifier.height(4.dp)) + Row(modifier = Modifier.align(Alignment.Companion.CenterHorizontally)) { val label = remember(item) { val unitPrice = if (item.volume <= BigDecimal.ZERO) null @@ -176,7 +176,7 @@ fun PurchaseDrinkTile( Text( label, - modifier = Modifier.Companion + modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), textAlign = TextAlign.Companion.Center, diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseFilterChip.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseFilterChip.kt index 609d776..60feb16 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseFilterChip.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseFilterChip.kt @@ -52,7 +52,7 @@ fun PurchaseFilterChip( Icon( Icons.Default.Check, contentDescription = null, - modifier = Modifier.Companion.size(18.dp) + modifier = Modifier.size(18.dp) ) } }, diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseFilterRow.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseFilterRow.kt index 309572a..3a1a04b 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseFilterRow.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseFilterRow.kt @@ -40,7 +40,7 @@ fun PurchaseFilterRow( toggleFilter: (filter: PurchaseViewModel.Filter) -> Unit = {}, ) { FlowRow( - modifier = Modifier.Companion.padding(horizontal = 12.dp), + modifier = Modifier.padding(horizontal = 12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { PurchaseFilterChip( diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseRoute.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseRoute.kt index be3cb02..9fa20a8 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseRoute.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/PurchaseRoute.kt @@ -36,6 +36,7 @@ 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.ui.wrapped.WrappedBanner import de.chaosdorf.meteroid.viewmodel.Navigator import de.chaosdorf.meteroid.viewmodel.PurchaseViewModel import kotlinx.coroutines.flow.update diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/WrappedBanner.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedBanner.kt similarity index 89% rename from app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/WrappedBanner.kt rename to app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedBanner.kt index b4784d2..511ef23 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/purchase/WrappedBanner.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedBanner.kt @@ -22,7 +22,7 @@ * THE SOFTWARE. */ -package de.chaosdorf.meteroid.ui.purchase +package de.chaosdorf.meteroid.ui.wrapped import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -54,14 +54,14 @@ fun WrappedBanner( color = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainerTinted, shape = RoundedCornerShape(8.dp), - modifier = Modifier.Companion.padding(vertical = 8.dp, horizontal = 12.dp) + modifier = Modifier.padding(vertical = 8.dp, horizontal = 12.dp) ) { Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Companion.CenterVertically, - modifier = Modifier.Companion.padding(vertical = 8.dp) + modifier = Modifier.padding(vertical = 8.dp) ) { - Spacer(Modifier.Companion.width(12.dp)) + Spacer(Modifier.width(12.dp)) Column(verticalArrangement = Arrangement.Center) { val now = Clock.System.now().toLocalDateTime(TimeZone.Companion.UTC) Text( @@ -73,11 +73,11 @@ fun WrappedBanner( style = MaterialTheme.typography.bodyMedium, ) } - Spacer(Modifier.Companion.width(8.dp)) + Spacer(Modifier.width(8.dp)) Button(onClick = onClick) { Text("Let's go") } - Spacer(Modifier.Companion.width(4.dp)) + Spacer(Modifier.width(4.dp)) } } } diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/NavigationViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/NavigationViewModel.kt index 2a4e7b3..d1184ad 100644 --- a/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/NavigationViewModel.kt +++ b/app/src/main/kotlin/de/chaosdorf/meteroid/viewmodel/NavigationViewModel.kt @@ -147,9 +147,9 @@ class NavigationViewModel @AssistedInject constructor( } } - fun togglePin(serverId: ServerId, userId: UserId) { + fun togglePin(user: User) { viewModelScope.launch { - accountProvider.togglePin(serverId, userId) + accountProvider.togglePin(user.serverId, user.userId) } } } -- GitLab