diff --git a/composeApp/src/commonMain/composeResources/drawable/account.xml b/composeApp/src/commonMain/composeResources/drawable/account.xml
new file mode 100644
index 0000000..f6b8d2c
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/account.xml
@@ -0,0 +1,10 @@
+
+
+
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 f2692c0..446ab8f 100644
--- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt
+++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt
@@ -14,11 +14,13 @@ 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.ui.appskeleton.BottomBar
+import net.codinux.banking.ui.appskeleton.SideMenu
import net.codinux.banking.ui.composables.StateHandler
import net.codinux.banking.ui.composables.TransactionsList
-import net.codinux.banking.ui.dialogs.AddAccountDialog
import net.codinux.banking.ui.config.Colors
import net.codinux.banking.ui.config.DI
+import net.codinux.banking.ui.dialogs.AddAccountDialog
import net.codinux.log.LoggerFactory
import org.jetbrains.compose.ui.tooling.preview.Preview
@@ -31,7 +33,7 @@ private val typography = Typography(
fun App() {
LoggerFactory.defaultLoggerName = "net.codinux.banking.ui.Bankmeister"
- val colors = MaterialTheme.colors.copy(secondary = Colors.CodinuxSecondaryColor, onSecondary = Color.White)
+ val colors = MaterialTheme.colors.copy(primary = Colors.Primary, primaryVariant = Colors.PrimaryDark, secondary = Colors.Accent, onSecondary = Color.White)
var showAddAccountDialog by remember { mutableStateOf(false) }
@@ -43,29 +45,34 @@ fun App() {
MaterialTheme(colors = colors, typography = typography) {
- Box {
- Column(
- Modifier.fillMaxWidth().fillMaxHeight().background(color = Colors.Zinc100),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- TransactionsList(DI.uiState)
- }
-
- Row(Modifier.align(Alignment.BottomEnd)) {
- FloatingActionButton(
- shape = CircleShape,
- modifier = Modifier.offset((-12).dp, (-12).dp),
- onClick = { showAddAccountDialog = true }
+ SideMenu {
+ Box(modifier = Modifier.background(Colors.CodinuxSecondaryColor).padding(0.dp)) {
+ Column(
+ Modifier.fillMaxWidth().fillMaxHeight().background(color = Colors.Zinc100)
) {
- Icon(Icons.Filled.Add, contentDescription = "Add a bank account")
+ Column(Modifier.fillMaxWidth().weight(1f), horizontalAlignment = Alignment.CenterHorizontally) {
+ TransactionsList(DI.uiState)
+ }
+
+ BottomBar()
+ }
+
+ Row(Modifier.align(Alignment.BottomEnd)) {
+ FloatingActionButton(
+ shape = CircleShape,
+ modifier = Modifier.offset((-12).dp, (-26).dp),
+ onClick = { showAddAccountDialog = true }
+ ) {
+ Icon(Icons.Filled.Add, contentDescription = "Add a bank account")
+ }
+ }
+
+ if (showAddAccountDialog) {
+ AddAccountDialog { showAddAccountDialog = false }
}
}
- if (showAddAccountDialog) {
- AddAccountDialog { showAddAccountDialog = false }
- }
+ StateHandler(DI.uiState)
}
-
- StateHandler(DI.uiState)
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/BottomBar.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/BottomBar.kt
new file mode 100644
index 0000000..a363d6c
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/BottomBar.kt
@@ -0,0 +1,40 @@
+package net.codinux.banking.ui.appskeleton
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.BottomAppBar
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Menu
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+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
+
+private val uiState = DI.uiState
+
+@Composable
+fun BottomBar() {
+ val coroutineScope = rememberCoroutineScope()
+
+
+ BottomAppBar(modifier = Modifier.background(Colors.Accent)) {
+ IconButton(
+ onClick = { coroutineScope.launch {
+ uiState.drawerState.value.toggle()
+ } }
+ ) {
+ Icon(Icons.Filled.Menu, contentDescription = "Open Navigation Drawer with sidebar menu")
+ }
+
+ Spacer(Modifier.weight(1f))
+
+ Spacer(Modifier.width(72.dp)) // space for Floating Action Button
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/SideMenu.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/SideMenu.kt
new file mode 100644
index 0000000..ba066f5
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/SideMenu.kt
@@ -0,0 +1,67 @@
+package net.codinux.banking.ui.appskeleton
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import bankmeister.composeapp.generated.resources.AppIcon
+import bankmeister.composeapp.generated.resources.Res
+import net.codinux.banking.ui.composables.BanksList
+import net.codinux.banking.ui.config.Colors
+import net.codinux.banking.ui.config.DI
+import org.jetbrains.compose.resources.imageResource
+
+private val uiState = DI.uiState
+
+private val HeaderHeight = 160f
+
+private val HeaderBackground = Brush.linearGradient(
+ colors = listOf(
+ Color(0xFF00695C), // endColor: #00695C
+ Color(0xFF009688), // centerColor: #009688
+ Color(0xFF4DB6AC), // startColor: #4DB6AC
+ )
+)
+
+@Composable
+fun SideMenu(appContent: @Composable () -> Unit) {
+ val drawerState = uiState.drawerState.collectAsState().value
+
+ ModalDrawer(
+ modifier = Modifier.fillMaxHeight(),
+ drawerState = drawerState,
+ content = appContent,
+ drawerBackgroundColor = Colors.DrawerContentBackground,
+ drawerContentColor = Colors.DrawerPrimaryText, // seems to have no effect
+ drawerContent = {
+ Column(Modifier) {
+ Column(Modifier.fillMaxWidth().height(HeaderHeight.dp).background(HeaderBackground).padding(16.dp)) {
+ Spacer(Modifier.weight(1f))
+
+ Image(imageResource(Res.drawable.AppIcon), "Bankmeister's app icon", Modifier.size(48.dp))
+
+ Text("Bankmeister", color = Color.White, modifier = Modifier.padding(top = 16.dp, bottom = 8.dp))
+
+ Text("Version 1.0.0 Alpha 12", color = Color.LightGray)
+ }
+
+ Divider(color = Colors.DrawerDivider)
+
+ Column(Modifier.padding(horizontal = 16.dp, vertical = 24.dp)) {
+ Column(Modifier.height(40.dp), verticalArrangement = Arrangement.Center) {
+ Text("Konten", color = Colors.DrawerPrimaryText)
+ }
+
+ BanksList(textColor = Colors.DrawerPrimaryText)
+ }
+ }
+ }
+ )
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/BankIcon.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/BankIcon.kt
index b47ff6d..08f93b1 100644
--- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/BankIcon.kt
+++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/BankIcon.kt
@@ -1,8 +1,8 @@
package net.codinux.banking.ui.composables
+import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -14,37 +14,45 @@ import net.codinux.banking.client.model.UserAccountViewInfo
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(userAccount: UserAccount?, modifier: Modifier = Modifier) {
+fun BankIcon(userAccount: UserAccount?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: DrawableResource? = null) {
val iconUrl by remember(userAccount?.bic) { mutableStateOf(userAccount?.let { bankIconService.findIconForBank(it) }) }
- BankIcon(iconUrl, modifier)
+ BankIcon(iconUrl, modifier, iconModifier, fallbackIcon = fallbackIcon)
}
private val bankingGroupMapper = BankingGroupMapper()
@Composable
-fun BankIcon(bank: BankInfo, modifier: Modifier = Modifier) {
+fun BankIcon(bank: BankInfo, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: DrawableResource? = null) {
val iconUrl by remember(bank.bic) { mutableStateOf(bankIconService.findIconForBank(bank.name, bankingGroupMapper.getBankingGroup(bank.name, bank.bic))) }
- BankIcon(iconUrl, modifier)
+ BankIcon(iconUrl, modifier, iconModifier, fallbackIcon = fallbackIcon)
}
@Composable
-fun BankIcon(userAccount: UserAccountViewInfo?, modifier: Modifier = Modifier) {
+fun BankIcon(userAccount: UserAccountViewInfo?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: DrawableResource? = null) {
val iconUrl = userAccount?.let { bankIconService.findIconForBank(it.bankName, it.bankingGroup) }
- BankIcon(iconUrl, modifier)
+ BankIcon(iconUrl, modifier, iconModifier, fallbackIcon = fallbackIcon)
}
@Composable
-fun BankIcon(iconUrl: String?, modifier: Modifier = Modifier) {
+fun BankIcon(iconUrl: String?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, contentDescription: String = "Favicon of this bank", fallbackIcon: DrawableResource? = null) {
Column(modifier) {
- iconUrl?.let {
- IconForUrl(iconUrl, "Favicon of this bank", Modifier.width(16.dp).height(16.dp))
+ if (iconUrl != null) {
+ IconForUrl(iconUrl, contentDescription, modifier = iconModifier)
+ } else if (fallbackIcon != null) {
+ Image(vectorResource(fallbackIcon), contentDescription, iconModifier)
+ } else {
+ Column(iconModifier) { } // show a placeholder for consistent layout
}
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/BanksList.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/BanksList.kt
new file mode 100644
index 0000000..d8b3007
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/BanksList.kt
@@ -0,0 +1,47 @@
+package net.codinux.banking.ui.composables
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Alignment
+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.ui.config.DI
+
+private val uiState = DI.uiState
+
+private val IconTextSpacing = 36.dp
+
+@Composable
+fun BanksList(modifier: Modifier = Modifier, itemModifier: Modifier = Modifier.height(48.dp).widthIn(min = 300.dp).padding(start = 8.dp), iconSize: Dp = 24.dp, textColor: Color = Color.White) {
+ val userAccounts = uiState.userAccounts.collectAsState()
+
+ Column(modifier) {
+ Row(itemModifier, verticalAlignment = Alignment.CenterVertically) {
+ BankIcon(null as? String?, Modifier.padding(end = IconTextSpacing), Modifier.size(iconSize), fallbackIcon = Res.drawable.account)
+
+ Text("Alle Konten", color = textColor)
+ }
+
+ userAccounts.value.forEach { userAccount ->
+ Spacer(Modifier.fillMaxWidth().height(12.dp))
+
+ Row(itemModifier, verticalAlignment = Alignment.CenterVertically) {
+ BankIcon(userAccount, Modifier.padding(end = IconTextSpacing), Modifier.size(iconSize), fallbackIcon = Res.drawable.account)
+
+ Text(userAccount.bankName, color = textColor)
+ }
+
+ userAccount.accounts.sortedBy { it.displayIndex }.forEach { account ->
+ Column(itemModifier.padding(start = iconSize + IconTextSpacing), verticalArrangement = Arrangement.Center) {
+ Text(account.productName ?: account.identifier, color = textColor)
+ }
+ }
+ }
+ }
+}
\ 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 a7eb892..fbceebd 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
@@ -27,9 +27,9 @@ private val formatUtil = DI.formatUtil
@Composable
fun TransactionsList(uiState: UiState) {
- val users by uiState.userAccounts.collectAsState()
- val usersById by remember(users) {
- derivedStateOf { users.associateBy { it.id } }
+ val userAccounts by uiState.userAccounts.collectAsState()
+ val userAccountsId by remember(userAccounts) {
+ derivedStateOf { userAccounts.associateBy { it.id } }
}
val transactions by uiState.transactions.collectAsState()
@@ -58,7 +58,7 @@ fun TransactionsList(uiState: UiState) {
Column(Modifier.background(Color.White)) { // LazyColumn inside LazyColumn is not allowed
monthTransactions.forEachIndexed { index, transaction ->
val backgroundColor = if (index % 2 == 1) Colors.Zinc100_50 else Color.Transparent
- TransactionListItem(usersById[transaction.userAccountId], transaction, backgroundColor)
+ TransactionListItem(userAccountsId[transaction.userAccountId], transaction, backgroundColor)
if (index < monthTransactions.size - 1) {
Divider(color = Colors.Zinc200, thickness = 1.dp)
diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Colors.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Colors.kt
index 2b8b526..58411dd 100644
--- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Colors.kt
+++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Colors.kt
@@ -1,9 +1,32 @@
package net.codinux.banking.ui.config
import androidx.compose.ui.graphics.Color
+import net.codinux.banking.ui.extensions.Color
object Colors {
+ val Primary = Color("#014e45")
+
+ val PrimaryDark = Color("#FF042204")
+
+ val Accent = Color("#00786a")
+
+ val PrimaryTextColorDark = Color("#BABABA")
+
+ val PrimaryTextColorLight = Color("#000000")
+
+ val BackgroundColorDark = Color("#303030")
+
+ val BackgroundColorLight = Color("#FFFFFF")
+
+
+ val DrawerContentBackground = BackgroundColorDark
+
+ val DrawerPrimaryText = PrimaryTextColorDark
+
+ val DrawerDivider = PrimaryTextColorDark
+
+
val CodinuxPrimaryColor = Color(30, 54, 78)
val CodinuxSecondaryColor = Color(251, 187, 33)
diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/extensions/ColorExtensions.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/extensions/ColorExtensions.kt
new file mode 100644
index 0000000..b27aa56
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/extensions/ColorExtensions.kt
@@ -0,0 +1,25 @@
+package net.codinux.banking.ui.extensions
+
+import androidx.compose.ui.graphics.Color
+
+fun Color(hex: String): Color {
+ val colorInt = parseColor(hex)
+ return Color(colorInt)
+}
+
+fun parseColor(colorString: String): Int {
+ val colorString2 = if (colorString[0] == '#') colorString.substring(1) else colorString
+
+ if (colorString2.length == 6 || colorString2.length == 8) {
+ // Use a long to avoid rollovers on #ffXXXXXX
+ var color = colorString2.toLong(16)
+ if (colorString2.length == 6) {
+ // Set the alpha value
+ color = color or 0x00000000ff000000L
+ }
+
+ return color.toInt()
+ }
+
+ throw IllegalArgumentException("Unknown color: $colorString")
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/extensions/DrawerStateExtensions.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/extensions/DrawerStateExtensions.kt
new file mode 100644
index 0000000..d4601a8
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/extensions/DrawerStateExtensions.kt
@@ -0,0 +1,11 @@
+package net.codinux.banking.ui.extensions
+
+import androidx.compose.material.DrawerState
+
+suspend fun DrawerState.toggle() {
+ if (this.isClosed) {
+ this.open()
+ } else {
+ this.close()
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt
index 84e0c22..8e0ec6e 100644
--- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt
+++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt
@@ -1,5 +1,7 @@
package net.codinux.banking.ui.state
+import androidx.compose.material.DrawerState
+import androidx.compose.material.DrawerValue
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import net.codinux.banking.dataaccess.entities.UserAccountEntity
@@ -15,6 +17,10 @@ class UiState : ViewModel() {
val transactions = MutableStateFlow>(emptyList())
+
+ val drawerState = MutableStateFlow(DrawerState(DrawerValue.Open))
+
+
val applicationErrorOccurred = MutableStateFlow(null)
val bankingClientErrorOccurred = MutableStateFlow(null)