diff --git a/composeApp/src/commonMain/composeResources/drawable/visibility.png b/composeApp/src/commonMain/composeResources/drawable/visibility.png new file mode 100644 index 0000000..1fb3666 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/visibility.png differ diff --git a/composeApp/src/commonMain/composeResources/drawable/visibility_off.png b/composeApp/src/commonMain/composeResources/drawable/visibility_off.png new file mode 100644 index 0000000..5be7f70 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/visibility_off.png differ diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt index 67d52df..8a4c778 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt @@ -1,22 +1,22 @@ package net.codinux.banking.ui import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Typography -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope +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.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch import net.codinux.banking.client.model.AccountTransaction import net.codinux.banking.ui.composables.TransactionsList +import net.codinux.banking.ui.dialogs.AddAccountDialog import net.codinux.banking.ui.service.BankingService import net.codinux.banking.ui.service.Colors import org.jetbrains.compose.ui.tooling.preview.Preview @@ -30,6 +30,10 @@ private val bankService = BankingService() @Composable @Preview fun App() { + val colors = MaterialTheme.colors.copy(secondary = Colors.CodinuxSecondaryColor, onSecondary = Color.White) + + var showAddAccountDialog by remember { mutableStateOf(false) } + val coroutineScope = rememberCoroutineScope() val (transactions, setTransaction) = remember { mutableStateOf>(emptyList()) } @@ -37,12 +41,28 @@ fun App() { setTransaction(bankService.getTransactions()) } - MaterialTheme(typography = typography) { - Column( - Modifier.fillMaxWidth().fillMaxHeight().background(color = Colors.Zinc100), - horizontalAlignment = Alignment.CenterHorizontally - ) { - TransactionsList(transactions) + MaterialTheme(colors = colors, typography = typography) { + Box { + Column( + Modifier.fillMaxWidth().fillMaxHeight().background(color = Colors.Zinc100), + horizontalAlignment = Alignment.CenterHorizontally + ) { + TransactionsList(transactions) + } + + Row(Modifier.align(Alignment.BottomEnd)) { + FloatingActionButton( + shape = CircleShape, + modifier = Modifier.offset((-12).dp, (-12).dp), + onClick = { showAddAccountDialog = true } + ) { + Icon(Icons.Filled.Add, contentDescription = "Add a bank account") + } + } + + if (showAddAccountDialog) { + AddAccountDialog { showAddAccountDialog = false } + } } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/TransactionsList.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/TransactionsList.kt index af5e5da..02bae20 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/TransactionsList.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/TransactionsList.kt @@ -4,8 +4,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Card import androidx.compose.material.Divider import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -23,6 +21,7 @@ import kotlinx.datetime.LocalDate import net.codinux.banking.client.model.AccountTransaction import net.codinux.banking.client.model.Amount import net.codinux.banking.ui.extensions.toBigDecimal +import net.codinux.banking.ui.forms.RoundedCornersCard import net.codinux.banking.ui.service.Colors import net.codinux.banking.ui.service.DI import org.jetbrains.compose.ui.tooling.preview.Preview @@ -51,10 +50,7 @@ fun TransactionsList(transactions: List) { val monthTransactions = groupedByMonth[month].orEmpty().sortedByDescending { it.valueDate } - Card( - shape = RoundedCornerShape(12.dp), // Rounded corners - elevation = 2.dp // Shadow elevation - ) { + RoundedCornersCard { Column(Modifier.background(Color.White)) { // LazyColumn inside LazyColumn is not allowed monthTransactions.forEachIndexed { index, transaction -> TransactionListItem(transaction, if (index % 2 == 1) Colors.Zinc100_50 else Color.Transparent) diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/AddAccountDialog.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/AddAccountDialog.kt new file mode 100644 index 0000000..6edcbf4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/AddAccountDialog.kt @@ -0,0 +1,97 @@ +package net.codinux.banking.ui.dialogs + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import net.codinux.banking.ui.forms.OutlinedTextField +import net.codinux.banking.ui.forms.PasswordTextField +import net.codinux.banking.ui.forms.RoundedCornersCard +import net.codinux.banking.ui.service.Colors + +@Composable +fun AddAccountDialog( + onDismiss: () -> Unit, +) { + var bankCode by remember { mutableStateOf("") } + var loginName by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + + val isRequiredDataEntered by remember(bankCode, loginName, password) { + derivedStateOf { bankCode.length == 8 && loginName.length > 3 && password.length > 3 } + } + + val coroutineScope = rememberCoroutineScope() + + Dialog(onDismissRequest = onDismiss) { + RoundedCornersCard { + Column(Modifier.background(Color.White).padding(8.dp)) { + + Row(Modifier.fillMaxWidth()) { + Text( + "Bank Konto hinzufügen", + color = Color.Black, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(top = 8.dp, bottom = 16.dp).weight(1f) + ) + + TextButton(onDismiss, colors = ButtonDefaults.buttonColors(contentColor = Colors.Zinc700, backgroundColor = Color.Transparent)) { + Icon(Icons.Filled.Close, contentDescription = "Close dialog", Modifier.size(32.dp)) + } + } + + OutlinedTextField( + value = bankCode, + onValueChange = { bankCode = it }, + label = { Text("Bank (Suche mit Name, Bankleitzahl oder Ort)") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Text("Online-Banking Zugangsdaten", color = Colors.CodinuxSecondaryColor) + + Spacer(modifier = Modifier.height(12.dp)) + + OutlinedTextField( + value = loginName, + onValueChange = { loginName = it }, + label = { Text("Login Name") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(12.dp)) + + PasswordTextField(password) { password = it } + + Spacer(modifier = Modifier.height(16.dp)) + + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + TextButton(onClick = onDismiss) { + Text("Abbrechen") + } + + Spacer(Modifier.width(8.dp)) + + TextButton( + onClick = onDismiss, + enabled = isRequiredDataEntered + ) { + Text("Hinzufügen") + } + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/OutlinedTextField.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/OutlinedTextField.kt new file mode 100644 index 0000000..6280fea --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/OutlinedTextField.kt @@ -0,0 +1,67 @@ +package net.codinux.banking.ui.forms + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.LocalTextStyle +import androidx.compose.material.MaterialTheme +import androidx.compose.material.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.VisualTransformation +import net.codinux.banking.ui.service.Colors + +@Composable +fun OutlinedTextField( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + readOnly: Boolean = false, + textStyle: TextStyle = LocalTextStyle.current, + label: @Composable (() -> Unit)? = null, + placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + isError: Boolean = false, + visualTransformation: VisualTransformation = VisualTransformation.None, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, + singleLine: Boolean = false, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + minLines: Int = 1, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + shape: Shape = MaterialTheme.shapes.small, +// colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors() // TODO: merge +) { + val textFieldColors = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = Colors.CodinuxSecondaryColor, + focusedLabelColor = Colors.CodinuxSecondaryColor // does not work + ) + + androidx.compose.material.OutlinedTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier, + enabled = enabled, + readOnly = readOnly, + textStyle = textStyle, + label = label, + placeholder = placeholder, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + isError = isError, + visualTransformation = visualTransformation, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + singleLine = singleLine, + maxLines = maxLines, + minLines = minLines, + interactionSource = interactionSource, + shape = shape, + colors = textFieldColors + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/PasswordTextField.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/PasswordTextField.kt new file mode 100644 index 0000000..9792446 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/PasswordTextField.kt @@ -0,0 +1,46 @@ +package net.codinux.banking.ui.forms + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxWidth +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.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 +fun PasswordTextField(password: String = "", label: String = "Passwort", onChange: (String) -> Unit) { + + var passwordVisible by remember { mutableStateOf(false) } + + OutlinedTextField( + value = password, + onValueChange = { onChange(it) }, + label = { Text(label) }, + singleLine = true, + modifier = Modifier.fillMaxWidth(), + visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), + trailingIcon = { + val visibilityIcon = if (passwordVisible) { + Res.drawable.visibility_off + } else { + Res.drawable.visibility + } + Icon( + bitmap = imageResource(visibilityIcon), + contentDescription = if (passwordVisible) "Hide password" else "Show password", + modifier = Modifier.size(24.dp).clickable { passwordVisible = !passwordVisible } + ) + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/RoundedCornersCard.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/RoundedCornersCard.kt new file mode 100644 index 0000000..c9bcf42 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/RoundedCornersCard.kt @@ -0,0 +1,16 @@ +package net.codinux.banking.ui.forms + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun RoundedCornersCard(cornerSize: Dp = 12.dp, shadowElevation: Dp = 2.dp, content: @Composable () -> Unit) { + Card( + shape = RoundedCornerShape(cornerSize), // Rounded corners + elevation = shadowElevation, // Shadow elevation + content = content + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/Colors.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/Colors.kt index 72abca4..430787e 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/Colors.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/Colors.kt @@ -4,6 +4,11 @@ import androidx.compose.ui.graphics.Color object Colors { + val CodinuxPrimaryColor = Color(30, 54, 78) + + val CodinuxSecondaryColor = Color(251, 187, 33) + + val Zinc100 = Color(244, 244, 245) val Zinc100_50 = Zinc100.copy(alpha = 0.5f)