Compare commits

..

10 Commits

15 changed files with 127 additions and 64 deletions

View File

@ -0,0 +1,59 @@
package net.codinux.banking.ui.dialogs
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import net.codinux.banking.client.model.BankAccountType
import net.codinux.banking.client.model.BankAccountViewInfo
import net.codinux.banking.client.model.BankingGroup
import net.codinux.banking.client.model.BankViewInfo
import net.codinux.banking.client.model.tan.*
import net.codinux.banking.ui.model.TanChallengeReceived
@Preview
@Composable
fun EnterTanDialogPreview_EnterTan() {
val tanMethods = listOf(TanMethod("Zeig mich an", TanMethodType.AppTan, "902"))
val user = BankViewInfo("12345678", "SupiDupiNutzer", "Abzockbank", BankingGroup.Postbank)
val tanChallenge = TanChallenge(TanChallengeType.EnterTan, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethods.first().identifier, tanMethods, user = user)
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
}
@Preview
@Composable
fun EnterTanDialogPreview_TanImage() {
val tanImageBytes = "iVBORw0KGgoAAAANSUhEUgAAANYAAADWCAYAAACt43wuAAAOUUlEQVR42u2doW4cyRqFDQwN/AAGAYb7CAYBYQnPK0QKtRQYaikkIDzU0FIewTRSgGFAoKFBQGCve1fWjXI9V/fY35lqT3/HKmm3M1NTXTNHGX/5/zp7k1IK155boJTGUkpjKaWxlFIaSymNpZTGUkppLKU0llIaSymlsZTSWEppLKXUAGPd3NxMZ2dn08nJyfTs2bNpb2/P4XgyY/7MvnjxYvr06dP069evZRjr4uJiOjw89A1y7MQ4OjqaLi8vxxrr/PzcN8Oxc2N/f//R5nqwsa6vr6eDgwPfCMfO/s31mK+FDzbW6empb4Bjp8f8O9fWjXV8fOzmO3Z6zEBj68Zy4x1roIUay+EoDI3lcGgsh0NjaSyHxtJYDseKjDX/a/b79++nq6srKzTVVvXly5fp5cuXu2mst2/f+g6roZo/gztnLP+mUqP17du33TOWUkuQxlJKY2kspbF20lhz+8zz5/O6punVq2m6+5Uuvn778/z253bV06vbn6vpip1/w/X0vuJ9CO+r/b5QT0jn11ih/vrr3829G/MmP+j67c/ebz/zhxCdf8P19L7ifQjvq/2+UE9I59dY8Xr+ezzo+j0/6Pwbrqf3Fe9DeF/t94V6Qr4/GivSXOH/++aenDzw+u3P7x++k+mEnX/D9fS+4n0I76v9vlBPSOfXWKEuLv6zyfPXg7sjC+Lrtz93H8L569P8uwk6/4br6X3F+xDeV/t9oZ6Qzq+xlJIK8gus07D0erqeDfMMo6Ob1gPhS2z+MqZcvbHqNCy9nq5nwzzD6Oim9UD4Epu/jClXb6w6DUuvp+vZMM8wOrppPRC+xOYvY8rVG6tOw9Lr6Xo2zDOMjm5aD4QvsfnLmHL1xqrTsPR6up4N8wyjo5vWA+FLbP4yppQKKiUVXECtYEjhYooVY7j7r5enjyeiaiOx/SnXNGqslIaFFC6mWDGGu/96efp4Iqo2Etufck2jxopvJKNwMcWKMdz918vTxxNRtZHY/pRrGjVWSsNCChdTrBjD3X+9PH08EVUbie1PuaZRY6U0LKRwMcWKMdz918vTxxNRtZHY/pRrGjWWUlLBBdQKUpSPopEQLdy4fKqWr73/5cfbQZxSPqo2r1x7RtXIxRSOquVr73/58XYQx/NDtXnt2jOoRi6mcFQtX3v/y4+3gzilfFRtXrn2jKqRiykcVcvX3v/y4+0gTikfVZtXrj2jauRiCkfV8rX3v/x4O4iVkgou11hp52+bMsWdwm1aiGG+cD1Qx3S7s1tjpXRoEGWKO4XbtBDDfOF6oI7pdme3xkrp0CjKlHYKt2khhvnC9UAd0/XObo0V0qFBlCnuFG7TQgzzheuBOqbbnd0aK6VDgyhT3CncpoUY5gvXA3VMtzu7NZZSUsFCrWC7IzjsYG13wmLnEJbTOuLOaGifKRpsrWC7IzjsYG13wmLnEJbTOuLOaGifKRpsrWC7IzjsYG13wmLnEJbTOuLOaGifMRq8+lrBdkdw2MHa7oTFziEsp3XEndHQPlM02FrBdkdw2MHa7oTFziEsp3XEndHQPlM0WCqolFTw4QukOmTT+bFaQSjlZNh9UTWE5XQVav2rMRbVIZvOj9UKQiknw+6LqiEsp6tQ61+NsagO2XR+rvaMSTkZdl9UDWE5XQXrpF6LsagO2XR+rFYQSjkZdl9UDWE5XYVa/2qMRXXIpvNjtYJQysmw+6JqCMvpKtT6pYJKSQW3eK4gVKvWPpcvvt9y6gcVBRzTzvY+SwW7FCimRuVz+eL7Lad+UFHAMe1s77NUsEuBYmpUPpcvfyO6qR9UFHBMO9v7LBXsUqCYGpXP5Yvvt5z6QUUBx7Szvc9SwS4FiqlR+Vy++H7LqR9UFHBMO9v7LBVUSio4foFQhi/W8QqFlmDhJ9T5hFQn76DzGD1XEMKCwzpeodASLPyEOp+Q6uQddB6j5wpCWHBYxyuWgQuFn1DnE1KdvIPOY/RcQQgLDut4hUJLsPAT6nxCqpN30HmMnisIYcFhHa9QaAkWfkKdT0h18g46j9FzBZWSCm5vge3UDIp6UZ2zVJrG4jJ/IRrZzmK2gxh6AkW9qM5ZKk1jcZm/EI1sZzHbQQw9gaJeVOcslaaxuMxfiEbWs5hX30EMPYGiXlTnLJWmsbjMX4hGtrOY7SCGnkBRL6pzlkrTWFzmL0Qj21nMUkGlpIKPoILllA2ssxXqwKXoZbwP5RrFejYxtP71UMFyygbW2Qp14FL0Mt6Hco1iPZsYWv96qGA5ZQPrbIU6cCl6Ge9DuUaxnk2M1WquhQqWUzawzlaoA5eil/E+lGsU69nE0PrXQwXLKRtYZyvUgUvRy3gfyjWK9WxiaP1SQaWkgtszVruWDKNhFIVLl5nWOrY7qcu0VmNBateSYTSMonDpMtNax3YndZnWaixsY7q1ZBgNoyhc/IaGtY7tTuoyrdVYkNq1ZBgNoyhcusy01rHdSV2mtRoLUruWDKNhFIVLl5nWOrY7qcu0VmMpJRXc3gLb59GNqpEbtv70PMAynaPSTKj9sYM4pV7Q/BQVHLb+9DzAMp2j0kyo/bGDOKVe0PwUFRy2/vQ8wDKdo9JMsM+PHcQh9YLmp6jgsPWn5wGW6RyVZkLtjx3EKfWC5qeo4LD1p+cBlukclWZC7Y9UUCmp4IJrBUM6V69hK9fCUaki2G1RYckaa2G1giGdq9ewlWvhqFQR7LaosGSNtbBawZDO1WvY6rVwTKoIdltUWLLGWlitYEjn6jVs5Vo4KlUEuy0qLFljLaxWMKRz9Rq2ci0clSqC3RYVlqyxpIJKKjjeWFBtG9VZHKdjpHSR2odyCsmo+41pZPi66zEWVNtGdRbH6RgpXaT2oZxCMup+YxoZvu56jAXVtlGdxXE6RkoXqX0op5CMut/8A5zWFq7FWFBtG9VZHKdjpHSR2odyCsmo+41pZPi66zEWVNtGdRbH6RgpXaT2oZxCMup+YxoZvq5UUCmpYGGBIearU0SKqpXpJSWsk7ecliIVjHFPhvnqFJGiamV6SQnr5C2npUgF8xeIMF+dIlJUrUwvsa9MVCdvOS1FKhjjngzz1SkiRdXK9JIS1slbTkuRCsa4J8N8dYpIUbUyvaSEdfKW01KkgkpJBbe3QIySQRSuTcPSddb3jUpLWVim8+qNhVEyiMK1aVi6zvq+UWkpC8t0Xr2xMEoGUbg2DUvXWd83Ki1lYZnOqzcWRskgCtemYek66/tGpaUsLNN59cbCKBlE4do0LF1nfd+otJSFZTpLBZWSChaoYNp5Gs5DnR8Yn8vXpmTQvrWpI0UjrRVMqWDaeRrOQ50fGJ/L16Zk0L61qSNFI60VTKlg2nkazkOdHxify9emZNC+takjRSOtFUypYNp5Gs5DnR8Yn8vXpmTQvrWpI0UjrRVMqWDaeRrOQ50fGJ/L16Zk0L61qSNFI60VlAoqqeATrBWEJsKyg6n5R3UuQykk2PzlYkFrBalatXZ2MDX/qM5lKIUEm79cLGitIFWr1s4OpuYf1bkMpZBg85eLBa0VpGrV2tnB1PyjOpehFBJs/nKxoLWCVK1aOzuYmn9U5zKUQoLNXy4WlAoqJRXk00bamb9YjWK5lq9O/yBKOYwiaqw2Fszmx2oUy7V8dfoHUcphFFFjtbFgNj9Wo1iu5avTP4hSDqOIGquNBbP5sRrFci1fnf5BlHIYRdRYbSyYzY/VKJZr+er0D6KUwyiixlJKKjh8gWUoyJ0HCHUop1QwfjxE7ah9bmcoayyKSlHQkUozoWrwoExhitpR+9zOUNZYFJWioCOVZkLV4EGZwhS1o/a5naGssSgqRUFHKs2EqsGDMoUpakftcztDWWNRVIqCjlSaCVWDB2UKU9SO2ud2hrLGUkoqyC8wpUMxhUupV7njGEsDoWhkPcyYoazU+u0gpihcSr3KHcdYGghFI+thxgxlpdZvBzFF4VLqVe44xtJAKBpZDzNmKCt3fuPaO4gpCpdSr3LHMZYGQtHIepgxQ1mp9dtBTFG4lHqVO46xNBCKRtbDjBnKSq1fKqiUVHB7xqqnYEDnB1I1cu1UEWqj27Wd1OM1VkoRqRQM6PxAqkaunSpCbXS7tpN6vMZKKSKVggGdH0jVyLVTRaiNbtd2co/XWBlFpFIwoPMDqRq5dqoItdHt2k7q8RorpYhUCgZ0fiBVI9dOFaE2ul3bST1eYyklFVyAsSj6B9GnNj2r09R0H9qd1xprkLEo+gfRpzY9q9PUdB/andcaa5CxKPoH0ac2PavT1HQf2p3XGmuQsSj6B9GnNj2r09R0H9qd1xprkLEo+gfRpzY9q9PUdB/andcaSyqopIKL7SBu0632+X7t+43XCVFTqvO33gm+dmO1z6+jXridToI9HurAbZ+jWO8EX7ux2ufXUS/cTifBHg914LbPUax3gq/dWO3z66gXbqeTYI+HOnDb5yjWO8HXbqz2+XXUC7fTSbDHQx247XMU653gUkGlpIIaS2ksjaWUxlJKY6UL/Pnzp++qGqrLy8vdM9bnz599Z9VQvX79eveMdXBwML1792768eOH77Daqm5ubqYPHz5M+/v7u2csh+MpDo3lcGgsh0NjaSzHsPH161eN5XD8rw/nnVHu/vvPz9Ldn/3555ser7EcqzbVff//+/X/59p9f9YeR0dH2zfW/KJ+cBy7bKyTR5z082BjvXnzxg+OI/4q+KdBNhnmvq9/2/62dHZ2tn1jff/+/UH/4OZwPIVxeHj4zz8sb91Ysz5+/Oib4NjJcTF3UD6m2Pex5SKzufyby7ErYy69Oz8/f3wVPVGLNX8tnH/nEmg4nuo4Pj6eTk9Pp+vra6Y9xRJNpXhpLKU0llIaSymNpZTSWEppLKU0llJKYymlsZTSWEopjaWUxlJKYymlQv0Now2p4n2VPN0AAAAASUVORK5CYII="
val tanMethod = TanMethod("photoTAN-Verfahren", TanMethodType.photoTan, "902", 6, AllowedTanFormat.Numeric)
val tanImage = TanImage("image/png", tanImageBytes)
val user = BankViewInfo("10010010", "Ihr krasser Login Name", "Phantasie Bank", BankingGroup.Comdirect)
val tanChallenge = TanChallenge(TanChallengeType.Image, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethod.identifier, listOf(tanMethod), null, emptyList(), tanImage, null, user)
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
}
@Preview
@Composable
fun EnterTanDialogPreview_WithMultipleTanMedia() { // shows that dialog is really long if a TAN Image, select box for TAN Media, bank name, bank account, ... gets displayed
val tanImageBytes = "iVBORw0KGgoAAAANSUhEUgAAAJYAAACWAQAAAAAUekxPAAABZUlEQVR4Xs2WsY7DMAxDtfmXvVmbflnDAT6SSXDBdQsz1O4QvQKVQkp2Y3+sn/hPvo5lxKiMlXP1YmCy2rsKYFcPBS7LURthT+Ri8AJTnp1DwQts5xzAvd5g+/wwxU2Xp0weXevu20PGJVFZ89lDDoPfMSFD5ULNPZnDYXxxiEk4Z0QskzFCsWzLDrS5ajaY2hEhhIW+UzU7LDl4GBn00YLt6TI43eOK2ZYm200hGSNdj5bOBqPXuWhR8NQp5TXYltmDv8/yj5oNVhxoNWNyT9VsMHiOkuX20Zwmw3HNBuJXU9NjMtQcg5nYk3TeZXqmELhSsF22ZXfAdd4CeZxhBsMT5KTxmBs4xRwO48RgQ9dmhnIZV3FwYDx90nsYLHUHoFj1eP39Z3jIeC1BAIw1yj5zWCzZkJo+nhaHpiZTL1LXuM4cl8EqHdx5y/GQbdrOoV5ooz7Pjefs8Ajs0EAz47CP9fXsFzPY58fQF5StAAAAAElFTkSuQmCC"
val tanImage = TanImage("image/png", tanImageBytes)
val tanMethods = listOf(
TanMethod("chipTAN optisch", TanMethodType.ChipTanFlickercode, "911", 6, AllowedTanFormat.Numeric),
TanMethod("chipTAN-QR", TanMethodType.ChipTanQrCode, "913", 6, AllowedTanFormat.Numeric)
)
val tanMedia = listOf(
TanMedium(TanMediumType.TanGenerator, "SparkassenCard (Debitkarte)", TanMediumStatus.Available, TanGeneratorTanMedium("5678901234")),
TanMedium(TanMediumType.TanGenerator, "SparkassenCard (Debitkarte)", TanMediumStatus.Used, TanGeneratorTanMedium("5432109876"))
)
val user = BankViewInfo("10010010", "Ihr krasser Login Name", "Eine ganz gewöhnliche Sparkasse", BankingGroup.Sparkasse)
val account = BankAccountViewInfo("12345678", null, BankAccountType.CheckingAccount, null, "Giro Konto")
val tanChallenge = TanChallenge(TanChallengeType.Image, ActionRequiringTan.GetTransactions, "Sie möchten eine \"Umsatzabfrage\" freigeben: Bitte bestätigen Sie den \"Startcode 80061030\" mit der Taste \"OK\".", "913", tanMethods, "SparkassenCard (Debitkarte)", tanMedia, tanImage, null, user, account)
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
}

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,24C18.6274,24 24,18.6274 24,12C24,5.3726 18.6274,0 12,0C5.3726,0 0,5.3726 0,12C0,18.6274 5.3726,24 12,24ZM6,7.5C6,6.9477 6.4477,6.5 7,6.5H17C17.5523,6.5 18,6.9477 18,7.5V16.5C18,17.0523 17.5523,17.5 17,17.5H7C6.4477,17.5 6,17.0523 6,16.5V7.5ZM7.5,12.9C7.5,12.6791 7.6791,12.5 7.9,12.5H16.1C16.3209,12.5 16.5,12.6791 16.5,12.9V13.1C16.5,13.3209 16.3209,13.5 16.1,13.5H7.9C7.6791,13.5 7.5,13.3209 7.5,13.1V12.9ZM13.9,8.5C13.6791,8.5 13.5,8.6791 13.5,8.9V9.3C13.5,9.5209 13.6791,9.7 13.9,9.7H16.1C16.3209,9.7 16.5,9.5209 16.5,9.3V8.9C16.5,8.6791 16.3209,8.5 16.1,8.5H13.9ZM7.5,14.9C7.5,14.6791 7.6791,14.5 7.9,14.5H16.1C16.3209,14.5 16.5,14.6791 16.5,14.9V15.1C16.5,15.3209 16.3209,15.5 16.1,15.5H7.9C7.6791,15.5 7.5,15.3209 7.5,15.1V14.9Z"
android:fillColor="#989792"
android:fillType="evenOdd"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -6,10 +6,8 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.FilterAlt
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -20,13 +18,10 @@ import androidx.compose.ui.input.key.*
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import bankmeister.composeapp.generated.resources.Res
import bankmeister.composeapp.generated.resources.filter_alt
import kotlinx.coroutines.launch
import net.codinux.banking.ui.config.Colors
import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.extensions.toggle
import org.jetbrains.compose.resources.imageResource
private val uiState = DI.uiState
@ -138,11 +133,11 @@ fun BottomBar(showMenuDrawer: Boolean = true) {
Row(Modifier.fillMaxHeight().widthIn(IconWidth.times(2), IconWidth.times(2)), verticalAlignment = Alignment.CenterVertically) {
IconButton({ uiState.showFilterBar.value = !!!uiState.showFilterBar.value }, Modifier.width(IconWidth)) {
Icon(imageResource(Res.drawable.filter_alt), "Kontoumsätze nach Konto, Zeitraum oder Betrag filtern", Modifier.size(24.dp))
Icon(Icons.Outlined.FilterAlt, "Kontoumsätze nach Konto, Zeitraum oder Betrag filtern", Modifier.size(24.dp))
}
IconButton({ coroutineScope.launch { DI.bankingService.updateAccountTransactions() } }, Modifier.width(IconWidth)) { // TODO: use sync, cached or autorenew as icon?
Icon(Icons.Filled.Refresh, contentDescription = "Neue Kontoumsätze abholen")
IconButton({ coroutineScope.launch { DI.bankingService.updateAccountTransactions() } }, Modifier.width(IconWidth)) {
Icon(Icons.Filled.Cached, contentDescription = "Neue Kontoumsätze abholen") // other possible icons: Refresh, sync, autorenew
}
}
}

View File

@ -6,8 +6,8 @@ import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.SaveAs
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
@ -58,7 +58,7 @@ fun SideMenuContent() {
val coroutineScope = rememberCoroutineScope()
Column(Modifier.background(Colors.DrawerContentBackground).verticalScroll(ScrollState(0), enabled = true)) {
Column(Modifier.fillMaxSize().background(Colors.DrawerContentBackground).verticalScroll(ScrollState(0), enabled = true)) {
Column(Modifier.fillMaxWidth().height(HeaderHeight.dp).background(HeaderBackground).padding(16.dp)) {
Spacer(Modifier.weight(1f))
@ -99,7 +99,7 @@ fun SideMenuContent() {
Spacer(Modifier.height(VerticalSpacing))
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, "Neue Überweisung", Modifier.size(iconSize), tint = textColor) }) {
uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData()
coroutineScope.launch {
@ -116,7 +116,7 @@ fun SideMenuContent() {
UiSettings(Modifier.fillMaxWidth().padding(bottom = VerticalSpacing), textColor)
NavigationMenuItem(itemModifier, "Daten exportieren", textColor, horizontalPadding = ItemHorizontalPadding,
icon = { Icon(Icons.AutoMirrored.Filled.Send, "Konto hinzufügen", Modifier.size(iconSize), tint = textColor) }) {
icon = { Icon(Icons.Filled.SaveAs, "Daten als CSV oder JSON exportieren", Modifier.size(iconSize), tint = textColor) }) {
uiState.showExportScreen.value = true
coroutineScope.launch {

View File

@ -1,56 +1,58 @@
package net.codinux.banking.ui.composables
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import net.codinux.banking.client.model.User
import net.codinux.banking.client.model.BankViewInfo
import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.model.BankInfo
import net.dankito.banking.banklistcreator.prettifier.BankingGroupMapper
import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.vectorResource
private val bankIconService = DI.bankIconService
private val DefaultIconModifier: Modifier = Modifier.size(16.dp)
@Composable
fun BankIcon(user: User?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, iconResource: DrawableResource? = null) {
fun BankIcon(user: User?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: ImageVector? = null, fallbackIconTintColor: Color? = null) {
val iconUrl by remember(user?.bic) { mutableStateOf(user?.let { bankIconService.findIconForBank(it) }) }
BankIcon(iconUrl, modifier, iconModifier, iconResource = iconResource)
BankIcon(iconUrl, modifier, iconModifier, fallbackIcon = fallbackIcon, fallbackIconTintColor = fallbackIconTintColor)
}
private val bankingGroupMapper = BankingGroupMapper()
@Composable
fun BankIcon(bank: BankInfo, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, iconResource: DrawableResource? = null) {
fun BankIcon(bank: BankInfo, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: ImageVector? = null) {
val iconUrl by remember(bank.bic) { mutableStateOf(bankIconService.findIconForBank(bank.name, bankingGroupMapper.getBankingGroup(bank.name, bank.bic))) }
BankIcon(iconUrl, modifier, iconModifier, iconResource = iconResource)
BankIcon(iconUrl, modifier, iconModifier, fallbackIcon = fallbackIcon)
}
@Composable
fun BankIcon(user: BankViewInfo?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, iconResource: DrawableResource? = null) {
fun BankIcon(user: BankViewInfo?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: ImageVector? = null) {
val iconUrl = user?.let { bankIconService.findIconForBank(it.bankName, it.bankingGroup) }
BankIcon(iconUrl, modifier, iconModifier, iconResource = iconResource)
BankIcon(iconUrl, modifier, iconModifier, fallbackIcon = fallbackIcon)
}
@Composable
fun BankIcon(iconUrl: String?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, contentDescription: String = "Favicon of this bank", iconResource: DrawableResource? = null) {
fun BankIcon(iconUrl: String?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, contentDescription: String = "Favicon of this bank", fallbackIcon: ImageVector? = null, fallbackIconTintColor: Color? = null) {
Column(modifier) {
if (iconUrl != null) {
IconForUrl(iconUrl, contentDescription, modifier = iconModifier)
} else if (iconResource != null) {
Image(vectorResource(iconResource), contentDescription, iconModifier)
} else if (fallbackIcon != null) {
Icon(fallbackIcon, contentDescription, iconModifier, tint = fallbackIconTintColor ?: LocalContentColor.current.copy(alpha = LocalContentAlpha.current))
} else {
Column(iconModifier) { } // show a placeholder for consistent layout
}

View File

@ -1,14 +1,15 @@
package net.codinux.banking.ui.composables
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AccountBalance
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import bankmeister.composeapp.generated.resources.Res
import bankmeister.composeapp.generated.resources.account
import net.codinux.banking.dataaccess.entities.BankAccountEntity
import net.codinux.banking.dataaccess.entities.UserEntity
import net.codinux.banking.ui.config.DI
@ -17,6 +18,8 @@ private val uiState = DI.uiState
private val IconTextSpacing = 24.dp
private val defaultBankIcon = Icons.Outlined.AccountBalance
@Composable
fun BanksList(
modifier: Modifier = Modifier,
@ -30,14 +33,14 @@ fun BanksList(
Column(modifier) {
NavigationMenuItem(itemModifier, "Alle Konten", textColor, iconSize, IconTextSpacing, itemHorizontalPadding, iconResource = Res.drawable.account) {
NavigationMenuItem(itemModifier, "Alle Konten", textColor, iconSize, IconTextSpacing, itemHorizontalPadding, icon = { Icon(defaultBankIcon, "Alle Konten", Modifier.size(iconSize), tint = textColor) }) {
accountSelected?.invoke(null, null)
}
users.value.sortedBy { it.displayIndex }.forEach { user ->
Spacer(Modifier.fillMaxWidth().height(12.dp))
NavigationMenuItem(itemModifier, user.displayName, textColor, iconSize, IconTextSpacing, itemHorizontalPadding, user, iconResource = Res.drawable.account) {
NavigationMenuItem(itemModifier, user.displayName, textColor, iconSize, IconTextSpacing, itemHorizontalPadding, user, fallbackIcon = defaultBankIcon) {
accountSelected?.invoke(user, null)
}

View File

@ -14,6 +14,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@ -21,7 +22,6 @@ import net.codinux.banking.dataaccess.entities.BankAccountEntity
import net.codinux.banking.dataaccess.entities.UserEntity
import net.codinux.banking.ui.config.Colors
import net.codinux.banking.ui.config.DI
import org.jetbrains.compose.resources.DrawableResource
private val filterService = DI.accountTransactionsFilterService
@ -39,7 +39,7 @@ fun NavigationMenuItem(
horizontalPadding: Dp = 8.dp,
user: UserEntity? = null,
bankAccount: BankAccountEntity? = null,
iconResource: DrawableResource? = null,
fallbackIcon: ImageVector? = null,
icon: (@Composable () -> Unit)? = null,
onClick: (() -> Unit)? = null
) {
@ -68,7 +68,7 @@ fun NavigationMenuItem(
icon()
}
} else {
BankIcon(user, Modifier.padding(end = iconTextSpacing), Modifier.size(iconSize), iconResource = iconResource)
BankIcon(user, Modifier.padding(end = iconTextSpacing), Modifier.size(iconSize), fallbackIcon = fallbackIcon, fallbackIconTintColor = textColor)
}
Text(text, color = textColor, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis)

View File

@ -45,6 +45,8 @@ fun AddAccountDialog(
val disabledColor: Color = MaterialTheme.colors.onSurface.copy(ContentAlpha.disabled)
val bankAutocompleteFocus = remember { FocusRequester() }
val loginNameFocus = remember { FocusRequester() }
var isAddingAccount by remember { mutableStateOf(false) }
@ -97,6 +99,7 @@ fun AddAccountDialog(
AutocompleteTextField(
label = "Bank (Suche mit Name, Ort, BIC oder Bankleitzahl)",
value = enteredBankSearchQuery,
textFieldFocus = bankAutocompleteFocus,
onEnteredTextChanged = { enteredBankSearchQuery = it },
onSelectedItemChanged = {
selectedBank = it
@ -166,4 +169,9 @@ fun AddAccountDialog(
}
}
}
LaunchedEffect(bankAutocompleteFocus) {
bankAutocompleteFocus.requestFocus()
}
}

View File

@ -4,6 +4,9 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ZoomIn
import androidx.compose.material.icons.filled.ZoomOut
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -13,14 +16,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import bankmeister.composeapp.generated.resources.Res
import bankmeister.composeapp.generated.resources.zoom_in
import bankmeister.composeapp.generated.resources.zoom_out
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import net.codinux.banking.client.model.tan.ActionRequiringTan
import net.codinux.banking.client.model.tan.AllowedTanFormat
import net.codinux.banking.client.model.tan.EnterTanResult
import net.codinux.banking.client.model.tan.*
import net.codinux.banking.ui.composables.BankIcon
import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.config.Internationalization
@ -30,7 +28,6 @@ import net.codinux.banking.ui.model.TanChallengeReceived
import net.codinux.banking.ui.model.error.ErroneousAction
import net.codinux.banking.ui.service.createImageBitmap
import net.codinux.log.Log
import org.jetbrains.compose.resources.imageResource
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
@ -113,9 +110,19 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () ->
"TAN Verfahren",
challenge.availableTanMethods.sortedBy { it.identifier },
challenge.selectedTanMethod,
{ Log.info { "User selected TanMethod $it" } }, // TODO: change TanMethod
{ tanMethod ->
if (tanMethod.type != TanMethodType.ChipTanFlickercode) {
tanChallengeReceived.callback(EnterTanResult(null, tanMethod))
}
},
{ it.displayName }
)
) { tanMethod ->
if (tanMethod.type == TanMethodType.ChipTanFlickercode) {
Text(tanMethod.displayName + " (noch nicht implementiert)", color = MaterialTheme.colors.onSurface.copy(ContentAlpha.disabled))
} else {
Text(tanMethod.displayName)
}
}
}
if (challenge.availableTanMedia.isNotEmpty()) {
@ -135,7 +142,7 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () ->
Column(Modifier.fillMaxWidth().padding(top = 6.dp)) {
if (challenge.flickerCode != null) {
Text("Es tut uns Leid, für die TAN müsste ein Flickercode angezeigt werden, was wir noch nicht implementiert haben.")
Text("Bitte wählen Sie ein anderes TAN Verfahren, z. B. manuelle TAN Eingabe wie chipTAN manuell.", Modifier.padding(top = 6.dp))
Text("Bitte wählen Sie ein anderes TAN Verfahren, z. B. chipTAN-QrCode oder manuelle TAN Eingabe wie chipTAN manuell.", Modifier.padding(top = 6.dp))
}
challenge.tanImage?.let { tanImage ->
@ -146,11 +153,11 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () ->
Text("Größe")
Spacer(Modifier.width(6.dp))
TextButton({ tanImageHeight -= 25}, enabled = tanImageHeight > minTanImageHeight, modifier = Modifier.width(48.dp), colors = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent)) {
Icon(imageResource(Res.drawable.zoom_out), contentDescription = "Bild mit enkodierter TAN verkleiner", Modifier.size(28.dp))
Icon(Icons.Filled.ZoomOut, contentDescription = "Bild mit enkodierter TAN verkleiner", Modifier.size(28.dp))
}
Spacer(Modifier.width(6.dp))
TextButton({ tanImageHeight += 25}, enabled = tanImageHeight < maxTanImageHeight, modifier = Modifier.width(48.dp), colors = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent)) {
Icon(imageResource(Res.drawable.zoom_in), contentDescription = "Bild mit enkodierter TAN vergrößern", Modifier.size(28.dp))
Icon(Icons.Filled.ZoomIn, contentDescription = "Bild mit enkodierter TAN vergrößern", Modifier.size(28.dp))
}
}
@ -167,7 +174,7 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () ->
Text(challenge.messageToShowToUser)
}
challenge.tanExpirationTime?.let { expirationTime ->
challenge.tanExpirationTime?.let { expirationTime -> // is almost never set
Row(Modifier.padding(top = 16.dp)) {
val time = expirationTime.toLocalDateTime(TimeZone.currentSystemDefault()).time
Text("TAN ist gültig bis:", Modifier.padding(end = 6.dp))

View File

@ -6,16 +6,15 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import bankmeister.composeapp.generated.resources.Res
import bankmeister.composeapp.generated.resources.visibility
import bankmeister.composeapp.generated.resources.visibility_off
import org.jetbrains.compose.resources.imageResource
@Composable // try BasicSecureTextField
fun PasswordTextField(password: String = "", label: String = "Passwort", forceHidePassword: Boolean? = null, onEnterPressed: (() -> Unit)? = null, onChange: (String) -> Unit) {
@ -34,12 +33,12 @@ fun PasswordTextField(password: String = "", label: String = "Passwort", forceHi
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
val visibilityIcon = if (passwordVisible) {
Res.drawable.visibility_off
Icons.Filled.VisibilityOff
} else {
Res.drawable.visibility
Icons.Filled.Visibility
}
Icon(
bitmap = imageResource(visibilityIcon),
visibilityIcon,
contentDescription = if (passwordVisible) "Hide password" else "Show password",
modifier = Modifier.size(24.dp).clickable { passwordVisible = !passwordVisible }
)

View File

@ -7,7 +7,7 @@ banking-client = "0.5.2-SNAPSHOT"
kcsv = "2.2.0"
kotlinx-serializable = "1.7.1"
klf = "1.6.0"
klf = "1.6.1"
logback = "1.5.7"
sqlDelight = "2.0.2"