Implemented show floating action menu with options Add account and New money transfer

This commit is contained in:
dankito 2024-09-24 02:48:35 +02:00
parent 18ea0e35f1
commit 7275d60f82
6 changed files with 139 additions and 8 deletions

View File

@ -0,0 +1,99 @@
package net.codinux.banking.ui.appskeleton
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.config.Style.FabMenuSpacing
import net.codinux.banking.ui.config.Style.FabSize
import net.codinux.banking.ui.config.Style.SmallFabSize
import net.codinux.banking.ui.model.ShowTransferMoneyDialogData
private val uiState = DI.uiState
@Composable
fun FloatingActionMenu(
showFloatingActionMenu: Boolean,
menuItemClicked: () -> Unit
) {
val fabVisibilityAnimation = animateFloatAsState(targetValue = if (showFloatingActionMenu) 1f else 0f)
val bottomPadding = FabSize + FabSize / 2
val accountsThatSupportMoneyTransfer = uiState.accountsThatSupportMoneyTransfer.collectAsState().value
val coroutineScope = rememberCoroutineScope()
fun handleClick(action: () -> Unit) {
menuItemClicked()
coroutineScope.launch {
delay(50)
action()
}
}
if (fabVisibilityAnimation.value > 0) {
Box(Modifier.fillMaxSize().padding(bottom = bottomPadding, end = 12.dp), contentAlignment = Alignment.BottomEnd) {
Column(Modifier, horizontalAlignment = Alignment.End) {
FloatingActionMenuItem("Überweisung", "Neue Überweisung", enabled = accountsThatSupportMoneyTransfer.isNotEmpty()) {
handleClick {
uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData()
}
}
FloatingActionMenuItem("Konto", "Neues Konto hinzufügen") {
handleClick {
uiState.showAddAccountDialog.value = true
}
}
}
}
}
}
@Composable
fun FloatingActionMenuItem(
label: String,
contentDescription: String,
enabled: Boolean = true,
onClick: () -> Unit
) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = FabMenuSpacing).clickable(enabled) { onClick() }) {
Text(label, fontSize = 16.sp, color = contentColorFor(MaterialTheme.colors.secondary).copy(if (enabled) 1f else ContentAlpha.disabled), modifier = Modifier.padding(end = 8.dp).background(MaterialTheme.colors.secondary).shadow(20.dp).padding(horizontal = 20.dp, vertical = 4.dp)) // the same background color as the FAB
FloatingActionButton(
shape = CircleShape,
elevation = FloatingActionButtonDefaults.elevation(defaultElevation = 20.dp),
modifier = Modifier.padding(end = (FabSize - SmallFabSize) / 2).size(SmallFabSize),
onClick = {
if (enabled) {
onClick()
}
}
) {
Icon(Icons.Filled.Add, contentDescription = contentDescription, tint = LocalContentColor.current.copy(if (enabled) LocalContentAlpha.current else ContentAlpha.disabled))
}
}
}

View File

@ -56,7 +56,9 @@ private val VerticalSpacing = 8.dp
fun SideMenuContent() { fun SideMenuContent() {
val drawerState = uiState.drawerState.collectAsState().value val drawerState = uiState.drawerState.collectAsState().value
val accounts = uiState.banks.collectAsState().value val accounts = uiState.accounts.collectAsState().value
val accountsThatSupportMoneyTransfer = uiState.accountsThatSupportMoneyTransfer.collectAsState().value
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@ -97,7 +99,7 @@ fun SideMenuContent() {
} }
} }
if (accounts.isNotEmpty()) { if (accountsThatSupportMoneyTransfer.isNotEmpty()) {
Spacer(Modifier.height(VerticalSpacing)) Spacer(Modifier.height(VerticalSpacing))
NavigationMenuItem(itemModifier, "Neue Überweisung", textColor, horizontalPadding = ItemHorizontalPadding, NavigationMenuItem(itemModifier, "Neue Überweisung", textColor, horizontalPadding = ItemHorizontalPadding,

View File

@ -19,6 +19,15 @@ object Style {
val ListItemHeaderWeight = FontWeight.Medium // couldn't believe it, the FontWeights look different on Desktop and Android val ListItemHeaderWeight = FontWeight.Medium // couldn't believe it, the FontWeights look different on Desktop and Android
val FabSize = 56.dp
val SmallFabSize = 46.dp
val FabSpacing = 16.dp
val FabMenuSpacing = FabSpacing / 2
val DividerThickness = 1.dp val DividerThickness = 1.dp
} }

View File

@ -42,8 +42,7 @@ fun TransferMoneyDialog(
val accountsToBank = banks.sortedBy { it.displayIndex } val accountsToBank = banks.sortedBy { it.displayIndex }
.flatMap { bank -> bank.accountsSorted.map { it to bank } }.toMap() .flatMap { bank -> bank.accountsSorted.map { it to bank } }.toMap()
val accountsSupportingTransferringMoney = banks.flatMap { it.accounts } val accountsSupportingTransferringMoney = uiState.accountsThatSupportMoneyTransfer.collectAsState().value
.filter { it.supportsMoneyTransfer }
if (accountsSupportingTransferringMoney.isEmpty()) { if (accountsSupportingTransferringMoney.isEmpty()) {
uiState.applicationErrorOccurred(ErroneousAction.TransferMoney, "Keines Ihrer Konten unterstützt das Überweisen von Geld") uiState.applicationErrorOccurred(ErroneousAction.TransferMoney, "Keines Ihrer Konten unterstützt das Überweisen von Geld")

View File

@ -6,6 +6,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -30,6 +31,8 @@ fun MainScreen() {
val fabPositionAdjustment = if (isMobile) 44.dp // FabSpacing = 16.dp + FAB height (= 56.dp) / 2 val fabPositionAdjustment = if (isMobile) 44.dp // FabSpacing = 16.dp + FAB height (= 56.dp) / 2
else (-10).dp else (-10).dp
var showFloatingActionMenu by remember { mutableStateOf(false) }
val desktopDrawerWidth = 350.dp val desktopDrawerWidth = 350.dp
val uiState = DI.uiState val uiState = DI.uiState
@ -51,9 +54,13 @@ fun MainScreen() {
FloatingActionButton( FloatingActionButton(
shape = CircleShape, shape = CircleShape,
modifier = Modifier.offset(x = 4.dp, y = fabPositionAdjustment), modifier = Modifier.offset(x = 4.dp, y = fabPositionAdjustment),
onClick = { uiState.showAddAccountDialog.value = true } onClick = { showFloatingActionMenu = !showFloatingActionMenu }
) { ) {
Icon(Icons.Filled.Add, contentDescription = "Add a bank account") if (showFloatingActionMenu) {
Icon(Icons.Filled.Close, contentDescription = "Menü zum Hinzufügen eines Kontos, für eine neue Überweisung, ... verstecken")
} else {
Icon(Icons.Filled.Add, contentDescription = "Zeigt ein Menü zum Hinzufügen eines Kontos, für eine neue Überweisung, ... an")
}
} }
}, },
drawerContent = { if (isMobile) SideMenuContent() else null }, drawerContent = { if (isMobile) SideMenuContent() else null },
@ -85,6 +92,9 @@ fun MainScreen() {
} }
} }
FloatingActionMenu(showFloatingActionMenu) { showFloatingActionMenu = false }
if (showFilterBar.value) { if (showFilterBar.value) {
FilterBar() FilterBar()
} }

View File

@ -3,8 +3,8 @@ package net.codinux.banking.ui.state
import androidx.compose.material.DrawerState import androidx.compose.material.DrawerState
import androidx.compose.material.DrawerValue import androidx.compose.material.DrawerValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableSharedFlow import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.*
import net.codinux.banking.client.model.tan.EnterTanResult import net.codinux.banking.client.model.tan.EnterTanResult
import net.codinux.banking.client.model.tan.TanChallenge import net.codinux.banking.client.model.tan.TanChallenge
import net.codinux.banking.persistence.entities.HoldingEntity import net.codinux.banking.persistence.entities.HoldingEntity
@ -25,6 +25,18 @@ class UiState : ViewModel() {
val banks = MutableStateFlow<List<BankAccessEntity>>(emptyList()) val banks = MutableStateFlow<List<BankAccessEntity>>(emptyList())
val accounts = banks.map { it.flatMap { it.accounts } }.stateIn(
scope = this.viewModelScope,
started = SharingStarted.Eagerly,
initialValue = emptyList()
)
val accountsThatSupportMoneyTransfer = accounts.map { it.filter { it.supportsMoneyTransfer } }.stateIn(
scope = this.viewModelScope,
started = SharingStarted.Eagerly,
initialValue = emptyList()
)
val transactions = MutableStateFlow<List<AccountTransactionViewModel>>(emptyList()) val transactions = MutableStateFlow<List<AccountTransactionViewModel>>(emptyList())
val holdings = MutableStateFlow<List<HoldingEntity>>(emptyList()) val holdings = MutableStateFlow<List<HoldingEntity>>(emptyList())