Implemented doing the same transfer again or transfer money to the recipient when long pressing a TransactionListItem

This commit is contained in:
dankito 2024-09-06 01:05:47 +02:00
parent ac308700c0
commit 418c188eb6
7 changed files with 74 additions and 21 deletions

View File

@ -23,6 +23,7 @@ import net.codinux.banking.ui.composables.NavigationMenuItem
import net.codinux.banking.ui.composables.settings.UiSettings import net.codinux.banking.ui.composables.settings.UiSettings
import net.codinux.banking.ui.config.Colors import net.codinux.banking.ui.config.Colors
import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.model.ShowTransferMoneyDialogData
import org.jetbrains.compose.resources.imageResource import org.jetbrains.compose.resources.imageResource
private val uiState = DI.uiState private val uiState = DI.uiState
@ -99,7 +100,7 @@ fun SideMenuContent() {
NavigationMenuItem(itemModifier, "Neue Überweisung", textColor, horizontalPadding = ItemHorizontalPadding, NavigationMenuItem(itemModifier, "Neue Überweisung", textColor, horizontalPadding = ItemHorizontalPadding,
icon = { Icon(Icons.Filled.Add, "Konto hinzufügen", Modifier.size(iconSize), tint = textColor) }) { icon = { Icon(Icons.Filled.Add, "Konto hinzufügen", Modifier.size(iconSize), tint = textColor) }) {
uiState.showTransferMoneyDialog.value = true uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData()
coroutineScope.launch { coroutineScope.launch {
drawerState.close() drawerState.close()

View File

@ -14,7 +14,7 @@ private val formatUtil = DI.formatUtil
@Composable @Composable
fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) { fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
val showAddAccountDialog by uiState.showAddAccountDialog.collectAsState() val showAddAccountDialog by uiState.showAddAccountDialog.collectAsState()
val showTransferMoneyDialog by uiState.showTransferMoneyDialog.collectAsState() val showTransferMoneyDialogData by uiState.showTransferMoneyDialogData.collectAsState()
val showExportScreen by uiState.showExportScreen.collectAsState() val showExportScreen by uiState.showExportScreen.collectAsState()
val tanChallengeReceived by uiState.tanChallengeReceived.collectAsState() val tanChallengeReceived by uiState.tanChallengeReceived.collectAsState()
@ -28,8 +28,8 @@ fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
AddAccountDialog { uiState.showAddAccountDialog.value = false } AddAccountDialog { uiState.showAddAccountDialog.value = false }
} }
if (showTransferMoneyDialog) { showTransferMoneyDialogData?.let { data ->
TransferMoneyDialog { uiState.showTransferMoneyDialog.value = false } TransferMoneyDialog(data) { uiState.showTransferMoneyDialogData.value = null }
} }
if (showExportScreen) { if (showExportScreen) {

View File

@ -1,22 +1,23 @@
package net.codinux.banking.ui.composables.transactions package net.codinux.banking.ui.composables.transactions
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.Divider import androidx.compose.material.*
import androidx.compose.material.Text import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import net.codinux.banking.client.model.UserAccount import net.codinux.banking.client.model.UserAccount
import net.codinux.banking.ui.composables.BankIcon import net.codinux.banking.ui.composables.BankIcon
import net.codinux.banking.ui.config.Colors import net.codinux.banking.ui.config.Colors
import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.AccountTransactionViewModel
import net.codinux.banking.ui.model.ShowTransferMoneyDialogData
private val uiSettings = DI.uiSettings private val uiSettings = DI.uiSettings
@ -32,9 +33,35 @@ fun TransactionListItem(userAccount: UserAccount?, transaction: AccountTransacti
val backgroundColor = if (zebraStripes && itemIndex % 2 == 1) Colors.Zinc100_50 else Color.White val backgroundColor = if (zebraStripes && itemIndex % 2 == 1) Colors.Zinc100_50 else Color.White
val bottomPadding = 56.dp
var showMenuAt by remember { mutableStateOf<DpOffset?>(null) }
fun newMoneyTransferToOtherParty(withSameData: Boolean) {
showMenuAt = null
DI.uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData(
DI.uiState.userAccounts.value.firstNotNullOf { it.accounts.firstOrNull { it.id == transaction.bankAccountId } },
transaction.otherPartyName,
if (withSameData) transaction.amount else null,
if (withSameData) transaction.reference else null
)
}
Row( Row(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
.background(color = backgroundColor) .background(color = backgroundColor)
.pointerInput(Unit) {
detectTapGestures(
onLongPress = {
if (transaction.otherPartyName != null) { // TODO: also check if IBAN is set
showMenuAt = DpOffset(it.x.dp, it.y.dp - bottomPadding)
}
}
)
}
.padding(horizontal = 6.dp, vertical = 6.dp) .padding(horizontal = 6.dp, vertical = 6.dp)
) { ) {
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
@ -61,7 +88,7 @@ fun TransactionListItem(userAccount: UserAccount?, transaction: AccountTransacti
) )
} }
Spacer(Modifier.width(6.dp).background(Color.Yellow)) Spacer(Modifier.width(6.dp))
Column(Modifier.width(90.dp), horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.Center) { Column(Modifier.width(90.dp), horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.Center) {
Text( Text(
@ -75,6 +102,18 @@ fun TransactionListItem(userAccount: UserAccount?, transaction: AccountTransacti
text = formatUtil.formatDate(transaction.valueDate) text = formatUtil.formatDate(transaction.valueDate)
) )
} }
DropdownMenu(showMenuAt != null, { showMenuAt = null }, Modifier.widthIn(min = 350.dp),
offset = showMenuAt ?: DpOffset.Zero,
) {
DropdownMenuItem({ newMoneyTransferToOtherParty(false) }) {
Text("Neue Überweisung an ${transaction.otherPartyName} ...")
}
DropdownMenuItem({ newMoneyTransferToOtherParty(true) }) {
Text("Neue Überweisung mit gleichen Daten ...")
}
}
} }
if (itemIndex < countItems - 1) { if (itemIndex < countItems - 1) {

View File

@ -24,6 +24,7 @@ import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.forms.AutocompleteTextField import net.codinux.banking.ui.forms.AutocompleteTextField
import net.codinux.banking.ui.forms.OutlinedTextField import net.codinux.banking.ui.forms.OutlinedTextField
import net.codinux.banking.ui.forms.Select import net.codinux.banking.ui.forms.Select
import net.codinux.banking.ui.model.ShowTransferMoneyDialogData
import net.codinux.banking.ui.model.error.ErroneousAction import net.codinux.banking.ui.model.error.ErroneousAction
import net.codinux.banking.ui.service.RecipientFinder import net.codinux.banking.ui.service.RecipientFinder
@ -35,6 +36,7 @@ private val formatUtil = DI.formatUtil
@Composable @Composable
fun TransferMoneyDialog( fun TransferMoneyDialog(
data: ShowTransferMoneyDialogData,
onDismiss: () -> Unit, onDismiss: () -> Unit,
) { ) {
val userAccounts = uiState.userAccounts.value val userAccounts = uiState.userAccounts.value
@ -51,12 +53,12 @@ fun TransferMoneyDialog(
} }
var senderAccount by remember { mutableStateOf(accountsSupportingTransferringMoney.first()) } var senderAccount by remember { mutableStateOf(data.senderAccount ?: accountsSupportingTransferringMoney.first()) }
var recipientName by remember { mutableStateOf("") } var recipientName by remember { mutableStateOf(data.recipientName ?: "") }
var recipientAccountIdentifier by remember { mutableStateOf("") } var recipientAccountIdentifier by remember { mutableStateOf("") }
var amount by remember { mutableStateOf("") } var amount by remember { mutableStateOf(data.amount?.amount ?: "") }
var paymentReference by remember { mutableStateOf("") } var paymentReference by remember { mutableStateOf(data.reference ?: "") }
val accountSupportsInstantTransfer by remember(senderAccount) { derivedStateOf { senderAccount.supportsAnyFeature(BankAccountFeatures.InstantPayment) } } val accountSupportsInstantTransfer by remember(senderAccount) { derivedStateOf { senderAccount.supportsAnyFeature(BankAccountFeatures.InstantPayment) } }
var instantTransfer by remember { mutableStateOf(false) } var instantTransfer by remember { mutableStateOf(false) }
@ -134,6 +136,7 @@ fun TransferMoneyDialog(
Column(Modifier.padding(top = verticalSpace)) { Column(Modifier.padding(top = verticalSpace)) {
AutocompleteTextField( AutocompleteTextField(
"Name des Empfängers / der Empfängerin", "Name des Empfängers / der Empfängerin",
recipientName,
dropdownMaxHeight = 350.dp, dropdownMaxHeight = 350.dp,
minTextLengthForSearch = 0, minTextLengthForSearch = 0,
onEnteredTextChanged = { recipientName = it }, onEnteredTextChanged = { recipientName = it },
@ -194,6 +197,7 @@ fun TransferMoneyDialog(
AutocompleteTextField( AutocompleteTextField(
"Verwendungszweck (optional)", "Verwendungszweck (optional)",
paymentReference,
dropdownMaxHeight = 250.dp, dropdownMaxHeight = 250.dp,
minTextLengthForSearch = 0, minTextLengthForSearch = 0,
getItemTitle = { suggestion -> suggestion.reference }, getItemTitle = { suggestion -> suggestion.reference },

View File

@ -19,6 +19,7 @@ import net.codinux.banking.ui.config.Colors
@Composable @Composable
fun <T> AutocompleteTextField( fun <T> AutocompleteTextField(
label: String, label: String,
initialValue: String? = null,
onSelectedItemChanged: (T?) -> Unit, onSelectedItemChanged: (T?) -> Unit,
onEnteredTextChanged: ((String) -> Unit)? = null, onEnteredTextChanged: ((String) -> Unit)? = null,
minTextLengthForSearch: Int = 1, minTextLengthForSearch: Int = 1,
@ -29,7 +30,7 @@ fun <T> AutocompleteTextField(
fetchSuggestions: suspend (query: String) -> Collection<T> = { emptyList() }, fetchSuggestions: suspend (query: String) -> Collection<T> = { emptyList() },
suggestionContent: @Composable (T) -> Unit suggestionContent: @Composable (T) -> Unit
) { ) {
var searchQuery by remember { mutableStateOf("") } var searchQuery by remember { mutableStateOf(initialValue ?: "") }
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
var isLoading by remember { mutableStateOf(false) } var isLoading by remember { mutableStateOf(false) }
var suggestions by remember { mutableStateOf<Collection<T>>(emptyList()) } var suggestions by remember { mutableStateOf<Collection<T>>(emptyList()) }

View File

@ -0,0 +1,12 @@
package net.codinux.banking.ui.model
import net.codinux.banking.client.model.Amount
import net.codinux.banking.dataaccess.entities.BankAccountEntity
data class ShowTransferMoneyDialogData(
val senderAccount: BankAccountEntity? = null,
val recipientName: String? = null,
// TODO: add recipient account identifier
val amount: Amount? = null,
val reference: String? = null
)

View File

@ -3,14 +3,10 @@ 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.coroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import net.codinux.banking.dataaccess.entities.UserAccountEntity import net.codinux.banking.dataaccess.entities.UserAccountEntity
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.*
import net.codinux.banking.ui.model.AccountTransactionsFilter
import net.codinux.banking.ui.model.TanChallengeReceived
import net.codinux.banking.ui.model.error.ApplicationError import net.codinux.banking.ui.model.error.ApplicationError
import net.codinux.banking.ui.model.error.BankingClientError import net.codinux.banking.ui.model.error.BankingClientError
import net.codinux.banking.ui.model.error.ErroneousAction import net.codinux.banking.ui.model.error.ErroneousAction
@ -45,7 +41,7 @@ class UiState : ViewModel() {
val showAddAccountDialog = MutableStateFlow(false) val showAddAccountDialog = MutableStateFlow(false)
val showTransferMoneyDialog = MutableStateFlow(false) val showTransferMoneyDialogData = MutableStateFlow<ShowTransferMoneyDialogData?>(null)
val showExportScreen = MutableStateFlow(false) val showExportScreen = MutableStateFlow(false)