Added layout for Desktop and Tablets

This commit is contained in:
dankito 2024-09-05 05:25:42 +02:00
parent 12d23fb790
commit f19a23cbc8
10 changed files with 144 additions and 30 deletions

View File

@ -1,12 +1,29 @@
package net.codinux.banking.ui package net.codinux.banking.ui
import android.os.Build import android.os.Build
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
actual val Dispatchers.IOorDefault: CoroutineDispatcher actual val Dispatchers.IOorDefault: CoroutineDispatcher
get() = Dispatchers.IO get() = Dispatchers.IO
@Composable
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
val config = LocalConfiguration.current
return remember(config) {
ScreenSizeInfo(
heightDp = config.screenHeightDp.dp,
widthDp = config.screenWidthDp.dp
)
}
}
class AndroidPlatform : Platform { class AndroidPlatform : Platform {
override val name: String = "Android ${Build.VERSION.SDK_INT}" override val name: String = "Android ${Build.VERSION.SDK_INT}"

View File

@ -35,8 +35,15 @@ fun App() {
// the same values as in BottomBar, but LocalContentColor.current and LocalContentAlpha.current have a different value there // the same values as in BottomBar, but LocalContentColor.current and LocalContentAlpha.current have a different value there
val snackbarTextColor = MaterialTheme.colors.onPrimary.copy(alpha = 0.74f) // 0.74f = ContentAlpha.HighContrastContentAlpha.medium val snackbarTextColor = MaterialTheme.colors.onPrimary.copy(alpha = 0.74f) // 0.74f = ContentAlpha.HighContrastContentAlpha.medium
val screenSizeInfo = rememberScreenSizeInfo()
val isMobile = derivedStateOf { screenSizeInfo.getUiType() == UiType.COMPACT }.value
// we want to place the FAB half size into the BottomBar so we have to add some top padding to it // we want to place the FAB half size into the BottomBar so we have to add some top padding to it
val fabPositionAdjustment = 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
val desktopDrawerWidth = 350.dp
val uiState = DI.uiState val uiState = DI.uiState
@ -46,17 +53,15 @@ fun App() {
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope() var isInitialized by remember { mutableStateOf(false) }
coroutineScope.launch { val coroutineScope = rememberCoroutineScope()
DI.init()
}
MaterialTheme(colors = colors, typography = typography) { MaterialTheme(colors = colors, typography = typography) {
Scaffold( Scaffold(
scaffoldState = rememberScaffoldState(drawerState, snackbarHostState), scaffoldState = rememberScaffoldState(drawerState, snackbarHostState),
bottomBar = { BottomBar() }, bottomBar = { if (isMobile) BottomBar() else null },
backgroundColor = Colors.Zinc100, backgroundColor = Colors.Zinc100,
floatingActionButton = { floatingActionButton = {
FloatingActionButton( FloatingActionButton(
@ -67,14 +72,15 @@ fun App() {
Icon(Icons.Filled.Add, contentDescription = "Add a bank account") Icon(Icons.Filled.Add, contentDescription = "Add a bank account")
} }
}, },
drawerContent = { SideMenuContent() }, drawerContent = { if (isMobile) SideMenuContent() else null },
drawerBackgroundColor = Colors.DrawerContentBackground, drawerBackgroundColor = Colors.DrawerContentBackground,
snackbarHost = { snackbarHostState -> snackbarHost = { snackbarHostState ->
SnackbarHost( SnackbarHost(
hostState = snackbarHostState hostState = snackbarHostState
) { data -> ) { data ->
Snackbar( Snackbar(
modifier = Modifier.offset(y = fabPositionAdjustment - 4.dp).padding(horizontal = 12.dp), modifier = Modifier.offset(y = fabPositionAdjustment - 4.dp)
.let { if (isMobile) it.padding(horizontal = 12.dp) else it.padding(start = desktopDrawerWidth + 6.dp, end = 2.dp) },
action = { if (data.actionLabel == null) null else { action = { if (data.actionLabel == null) null else {
TextButton( TextButton(
onClick = { data.performAction() }, onClick = { data.performAction() },
@ -88,7 +94,36 @@ fun App() {
} }
} }
) { scaffoldPadding -> ) { scaffoldPadding ->
AppContent(scaffoldPadding, uiState, uiSettings, snackbarHostState) if (isMobile) {
AppContent(scaffoldPadding, uiState, uiSettings, snackbarHostState, isMobile)
} else {
Row(Modifier.fillMaxSize()) {
Column(Modifier.width(desktopDrawerWidth)) {
SideMenuContent()
}
Column(Modifier.fillMaxSize().weight(1f).padding(start = 6.dp)) {
Row(Modifier.fillMaxWidth().weight(1f)) {
AppContent(scaffoldPadding, uiState, uiSettings, snackbarHostState, isMobile)
}
Row(Modifier.fillMaxWidth()) {
BottomBar(false)
}
}
}
}
}
}
LaunchedEffect(isInitialized) {
if (isInitialized == false) {
isInitialized = true
coroutineScope.launch {
DI.init()
}
} }
} }
} }

View File

@ -1,11 +1,37 @@
package net.codinux.banking.ui package net.codinux.banking.ui
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
expect val Dispatchers.IOorDefault: CoroutineDispatcher expect val Dispatchers.IOorDefault: CoroutineDispatcher
@Composable
expect fun rememberScreenSizeInfo(): ScreenSizeInfo
data class ScreenSizeInfo(
val heightDp: Dp,
val widthDp: Dp
)
fun ScreenSizeInfo.getUiType(): UiType {
return when (widthDp){
in 0.dp..600.dp -> UiType.COMPACT
in 600.dp..840.dp -> UiType.MEDIUM
else -> UiType.EXPANDED
}
}
enum class UiType {
COMPACT,
MEDIUM,
EXPANDED
}
expect fun getPlatform(): Platform expect fun getPlatform(): Platform
interface Platform { interface Platform {

View File

@ -15,11 +15,12 @@ fun AppContent(
scaffoldPadding: PaddingValues, scaffoldPadding: PaddingValues,
uiState: UiState, uiState: UiState,
uiSettings: UiSettings, uiSettings: UiSettings,
snackbarHostState: SnackbarHostState snackbarHostState: SnackbarHostState,
isMobile: Boolean
) { ) {
Column(Modifier.fillMaxSize().padding(scaffoldPadding), horizontalAlignment = Alignment.CenterHorizontally) { Column(Modifier.fillMaxSize().padding(scaffoldPadding), horizontalAlignment = Alignment.CenterHorizontally) {
TransactionsList(uiState, uiSettings) TransactionsList(uiState, uiSettings, isMobile)
StateHandler(uiState, snackbarHostState) StateHandler(uiState, snackbarHostState)
} }

View File

@ -38,7 +38,7 @@ private val uiState = DI.uiState
private val IconWidth = 48.dp private val IconWidth = 48.dp
@Composable @Composable
fun BottomBar() { fun BottomBar(showMenuDrawer: Boolean = true) {
val userAccounts by uiState.userAccounts.collectAsState() val userAccounts by uiState.userAccounts.collectAsState()
val transactionsFilter by uiState.transactionsFilter.collectAsState() val transactionsFilter by uiState.transactionsFilter.collectAsState()
@ -50,20 +50,17 @@ fun BottomBar() {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val configuration = LocalViewConfiguration.current BottomAppBar {
println("config: $configuration") if (showMenuDrawer) {
IconButton(
val windowInfo = LocalWindowInfo.current onClick = { coroutineScope.launch {
println("windowInfo: $windowInfo") uiState.drawerState.value.toggle()
} }
) {
BottomAppBar(modifier = Modifier.background(Colors.Accent)) { Icon(Icons.Filled.Menu, contentDescription = "Open Navigation Drawer with sidebar menu")
IconButton( }
onClick = { coroutineScope.launch { } else {
uiState.drawerState.value.toggle() Spacer(Modifier.width(6.dp)) // or show app icon?
} }
) {
Icon(Icons.Filled.Menu, contentDescription = "Open Navigation Drawer with sidebar menu")
} }
Row(Modifier.fillMaxWidth().padding(end = 64.dp)) { // 72.dp = leave space for Floating Action Button Row(Modifier.fillMaxWidth().padding(end = 64.dp)) { // 72.dp = leave space for Floating Action Button

View File

@ -57,7 +57,7 @@ fun SideMenuContent() {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
Column(Modifier.verticalScroll(ScrollState(0), enabled = true)) { Column(Modifier.background(Colors.DrawerContentBackground).verticalScroll(ScrollState(0), enabled = true)) {
Column(Modifier.fillMaxWidth().height(HeaderHeight.dp).background(HeaderBackground).padding(16.dp)) { Column(Modifier.fillMaxWidth().height(HeaderHeight.dp).background(HeaderBackground).padding(16.dp)) {
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))

View File

@ -23,7 +23,7 @@ private val calculator = DI.calculator
private val formatUtil = DI.formatUtil private val formatUtil = DI.formatUtil
@Composable @Composable
fun TransactionsList(uiState: UiState, uiSettings: UiSettings) { fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean = true) {
val userAccounts by uiState.userAccounts.collectAsState() val userAccounts by uiState.userAccounts.collectAsState()
val userAccountsId by remember(userAccounts) { val userAccountsId by remember(userAccounts) {
derivedStateOf { userAccounts.associateBy { it.id } } derivedStateOf { userAccounts.associateBy { it.id } }
@ -46,7 +46,7 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings) {
val transactionsListModifier = Modifier.fillMaxSize() val transactionsListModifier = Modifier.fillMaxSize()
Column(Modifier.fillMaxSize().padding(horizontal = 8.dp)) { Column(Modifier.fillMaxSize().let { if (isMobile) it.padding(horizontal = 8.dp) else it.padding(end = 2.dp) }) {
Row(Modifier.fillMaxWidth().height(32.dp).background(Colors.Zinc200), verticalAlignment = Alignment.CenterVertically) { Row(Modifier.fillMaxWidth().height(32.dp).background(Colors.Zinc200), verticalAlignment = Alignment.CenterVertically) {
Text("${transactionsToDisplay.size} Umsätze") Text("${transactionsToDisplay.size} Umsätze")
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))

View File

@ -1,11 +1,30 @@
package net.codinux.banking.ui package net.codinux.banking.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalWindowInfo
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
actual val Dispatchers.IOorDefault: CoroutineDispatcher actual val Dispatchers.IOorDefault: CoroutineDispatcher
get() = Dispatchers.IO get() = Dispatchers.IO
@OptIn(ExperimentalComposeUiApi::class)
@Composable
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
val density = LocalDensity.current
val config = LocalWindowInfo.current.containerSize
return remember(density, config) {
ScreenSizeInfo(
heightDp = with(density) { config.height.toDp() },
widthDp = with(density) { config.width.toDp() }
)
}
}
class JVMPlatform: Platform { class JVMPlatform: Platform {
override val name: String = "Java ${System.getProperty("java.version")}" override val name: String = "Java ${System.getProperty("java.version")}"

View File

@ -24,7 +24,7 @@ fun main() = application {
onCloseRequest = ::exitApplication, onCloseRequest = ::exitApplication,
title = "Bankmeister", title = "Bankmeister",
icon = painterResource(Res.drawable.AppIcon_svg), icon = painterResource(Res.drawable.AppIcon_svg),
state = WindowState(position = WindowPosition(Alignment.Center), size = DpSize(900.dp, 800.dp)), state = WindowState(position = WindowPosition(Alignment.Center), size = DpSize(1000.dp, 800.dp)),
) { ) {
File("data/db").mkdirs() File("data/db").mkdirs()
DI.setRepository(JdbcSqliteDriver("jdbc:sqlite:data/db/Bankmeister.db").apply { DI.setRepository(JdbcSqliteDriver("jdbc:sqlite:data/db/Bankmeister.db").apply {

View File

@ -1,11 +1,30 @@
package net.codinux.banking.ui package net.codinux.banking.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalWindowInfo
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
actual val Dispatchers.IOorDefault: CoroutineDispatcher actual val Dispatchers.IOorDefault: CoroutineDispatcher
get() = Dispatchers.Default get() = Dispatchers.Default
@OptIn(ExperimentalComposeUiApi::class)
@Composable
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
val density = LocalDensity.current
val config = LocalWindowInfo.current.containerSize
return remember(density, config) {
ScreenSizeInfo(
heightDp = with(density) { config.height.toDp() },
widthDp = with(density) { config.width.toDp() }
)
}
}
class JsPlatform: Platform { class JsPlatform: Platform {
override val name: String = "Web with Kotlin/Js" override val name: String = "Web with Kotlin/Js"