From 8e74ae689afdbcd5ffe2fe3a64447e8c1df6bc8b Mon Sep 17 00:00:00 2001 From: Florent CHAMPIGNY Date: Wed, 24 Dec 2025 10:16:39 +0100 Subject: [PATCH] refact: [DASHBOARD] display list on left (#467) Co-authored-by: Florent Champigny Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../composeResources/values/strings.xml | 1 + .../dashboard/view/DashboardScreen.kt | 90 +++++---- .../dashboard/view/DashboardSelectorView.kt | 136 ------------- .../dashboard/view/DashboardSidebarView.kt | 184 ++++++++++++++++++ 4 files changed, 238 insertions(+), 173 deletions(-) delete mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardSelectorView.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardSidebarView.kt diff --git a/FloconDesktop/composeApp/src/commonMain/composeResources/values/strings.xml b/FloconDesktop/composeApp/src/commonMain/composeResources/values/strings.xml index 2d73886a..e44a6428 100644 --- a/FloconDesktop/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/FloconDesktop/composeApp/src/commonMain/composeResources/values/strings.xml @@ -6,6 +6,7 @@ Close Copied to clipboard Crashes + Dashboards Delete Dashboards No dashboard Dashboard removed diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardScreen.kt index 2e07449e..4f69fda5 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardScreen.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardScreen.kt @@ -13,6 +13,12 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height import androidx.lifecycle.compose.collectAsStateWithLifecycle import flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.dashboard_action_delete @@ -78,50 +84,60 @@ fun DashboardScreen( FloconFeature( modifier = modifier ) { - FloconPageTopBar( - modifier = Modifier.fillMaxWidth(), - selector = { - DashboardSelectorView( - dashboardsState = deviceDashboards, + Column(Modifier.fillMaxSize()) { + FloconPageTopBar( + modifier = Modifier.fillMaxWidth(), + selector = null, + filterBar = { + Box(modifier = Modifier.weight(1f)) // to have actions on the right + }, + actions = { + DashboardArrangementView( + onArrangementClicked = onArrangementClicked, + arrangement = arrangement + ) + + FloconOverflow { + FloconDropdownMenuItem( + text = stringResource(Res.string.dashboard_action_delete), + leadingIcon = Icons.Outlined.Delete, + onClick = { deleteCurrentDashboard() } + ) + } + } + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Row(Modifier.fillMaxSize()) { + DashboardSidebarView( + modifier = Modifier + .fillMaxHeight() + .width(340.dp), + state = deviceDashboards, onDashboardSelected = onDashboardSelected, - modifier = Modifier.fillMaxWidth(), onDeleteClicked = onDeleteClicked, ) - }, - filterBar = { - Box(modifier = Modifier.weight(1f)) // to have actions on the right - }, - actions = { - DashboardArrangementView( - onArrangementClicked = onArrangementClicked, - arrangement = arrangement - ) - FloconOverflow { - FloconDropdownMenuItem( - text = stringResource(Res.string.dashboard_action_delete), - leadingIcon = Icons.Outlined.Delete, - onClick = { deleteCurrentDashboard() } + Spacer(modifier = Modifier.width(12.dp)) + + state?.let { + DashboardView( + modifier = Modifier + .fillMaxSize() + .clip(FloconTheme.shapes.medium) + .background(FloconTheme.colorPalette.primary) + .padding(all = 8.dp), + viewState = state, + onClickButton = onClickButton, + submitTextField = submitTextField, + submitForm = submitForm, + onUpdateCheckBox = onUpdateCheckBox, + arrangement = arrangement, + onOpenExternalClicked = onOpenExternalClicked, ) } } - ) - - state?.let { - DashboardView( - modifier = Modifier - .fillMaxSize() - .clip(FloconTheme.shapes.medium) - .background(FloconTheme.colorPalette.primary) - .padding(all = 8.dp), - viewState = state, - onClickButton = onClickButton, - submitTextField = submitTextField, - submitForm = submitForm, - onUpdateCheckBox = onUpdateCheckBox, - arrangement = arrangement, - onOpenExternalClicked = onOpenExternalClicked, - ) } } } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardSelectorView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardSelectorView.kt deleted file mode 100644 index 391c5974..00000000 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardSelectorView.kt +++ /dev/null @@ -1,136 +0,0 @@ -package io.github.openflocon.flocondesktop.features.dashboard.view - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Close -import androidx.compose.material.icons.outlined.KeyboardArrowDown -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastForEach -import flocondesktop.composeapp.generated.resources.Res -import flocondesktop.composeapp.generated.resources.dashboard_empty -import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardsStateUiModel -import io.github.openflocon.flocondesktop.features.dashboard.model.DeviceDashboardUiModel -import io.github.openflocon.library.designsystem.FloconTheme -import io.github.openflocon.library.designsystem.components.DropdownFilterFieldView -import io.github.openflocon.library.designsystem.components.FloconButton -import io.github.openflocon.library.designsystem.components.FloconDropdownMenu -import io.github.openflocon.library.designsystem.components.FloconDropdownMenuItem -import io.github.openflocon.library.designsystem.components.FloconIcon -import io.github.openflocon.library.designsystem.components.FloconLinearProgressIndicator -import org.jetbrains.compose.resources.stringResource - -@Composable -internal fun DashboardSelectorView( - dashboardsState: DashboardsStateUiModel, - onDashboardSelected: (DeviceDashboardUiModel) -> Unit, - onDeleteClicked: (DeviceDashboardUiModel) -> Unit, - modifier: Modifier = Modifier, -) { - var expanded by remember { mutableStateOf(false) } - - FloconDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - onExpandRequest = { - if (dashboardsState is DashboardsStateUiModel.WithContent) expanded = true - }, - anchorContent = { - FloconButton( - onClick = { - if (dashboardsState is DashboardsStateUiModel.WithContent) expanded = true - }, containerColor = FloconTheme.colorPalette.secondary - ) { - when (dashboardsState) { - DashboardsStateUiModel.Empty -> Text( - stringResource(Res.string.dashboard_empty), - style = FloconTheme.typography.bodySmall - ) - - DashboardsStateUiModel.Loading -> FloconLinearProgressIndicator() - is DashboardsStateUiModel.WithContent -> { - Text( - text = dashboardsState.selected.id, - style = FloconTheme.typography.bodySmall - ) - FloconIcon( - imageVector = Icons.Outlined.KeyboardArrowDown, - modifier = Modifier.size(16.dp), - ) - } - } - } - }, - modifier = modifier - ) { - var filterText by remember { mutableStateOf("") } - DropdownFilterFieldView( - value = filterText, - onValueChanged = { - filterText = it - }, - modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp) - ) - Column( - modifier = Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()) - ) { - if (dashboardsState is DashboardsStateUiModel.WithContent) { - val filteredItems = remember(filterText, dashboardsState.dashboards) { - dashboardsState.dashboards.filter { - it.id.contains( - filterText, - ignoreCase = true - ) - } - } - - filteredItems.fastForEach { dashboard -> - FloconDropdownMenuItem( - text = dashboard.id, - onClick = { - onDashboardSelected(dashboard) - expanded = false - }, - secondaryAction = { - Box( - Modifier.clip(RoundedCornerShape(4.dp)) - .background( - Color.White.copy(alpha = 0.8f) - ).padding(2.dp).clickable { - onDeleteClicked(dashboard) - }, - contentAlignment = Alignment.Center, - ) { - FloconIcon( - imageVector = Icons.Outlined.Close, - tint = FloconTheme.colorPalette.primary, - modifier = Modifier.size(14.dp) - ) - } - } - ) - } - } - } - } -} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardSidebarView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardSidebarView.kt new file mode 100644 index 00000000..f17eb02f --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardSidebarView.kt @@ -0,0 +1,184 @@ +package io.github.openflocon.flocondesktop.features.dashboard.view + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Surface +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Close +import androidx.compose.material.icons.outlined.Dashboard +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.composeunstyled.Text +import flocondesktop.composeapp.generated.resources.Res +import flocondesktop.composeapp.generated.resources.dashboards +import flocondesktop.composeapp.generated.resources.filter +import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardsStateUiModel +import io.github.openflocon.flocondesktop.features.dashboard.model.DeviceDashboardUiModel +import io.github.openflocon.flocondesktop.features.network.list.view.components.FilterBar +import io.github.openflocon.library.designsystem.FloconTheme +import io.github.openflocon.library.designsystem.components.FloconIcon +import org.jetbrains.compose.resources.stringResource + +@Composable +fun DashboardSidebarView( + modifier: Modifier = Modifier, + state: DashboardsStateUiModel, + onDashboardSelected: (DeviceDashboardUiModel) -> Unit, + onDeleteClicked: (DeviceDashboardUiModel) -> Unit, +) { + val borderColor = FloconTheme.colorPalette.secondary + + Surface( + color = FloconTheme.colorPalette.primary, + modifier = modifier + .clip(FloconTheme.shapes.medium) + .border( + width = 1.dp, + color = borderColor, + shape = FloconTheme.shapes.medium + ) + ) { + Column( + Modifier.fillMaxSize() + ) { + Column( + Modifier.fillMaxWidth() + .weight(1f) + .verticalScroll(rememberScrollState()) + .padding(all = 4.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + stringResource(Res.string.dashboards), + color = FloconTheme.colorPalette.onSurface, + style = FloconTheme.typography.bodyMedium.copy( + fontWeight = FontWeight.Bold, + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp) + ) + when (state) { + DashboardsStateUiModel.Empty -> Unit + DashboardsStateUiModel.Loading -> Unit + is DashboardsStateUiModel.WithContent -> { + var filterText = remember { mutableStateOf("") } + val filteredDashboards = remember(filterText.value, state.dashboards) { + val filterTextValue = filterText.value + if (filterTextValue.isBlank()) { + state.dashboards + } else { + state.dashboards.filter { + it.id.contains(filterTextValue, ignoreCase = true) + } + } + } + FilterBar( + filterText = filterText, + placeholderText = stringResource(Res.string.filter), + onTextChange = { + filterText.value = it + }, + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp).padding(bottom = 12.dp), + ) + filteredDashboards.forEach { + DashboardItemView( + dashboard = it, + onSelect = { + onDashboardSelected(it) + }, + onDelete = { + onDeleteClicked(it) + }, + modifier = Modifier.fillMaxWidth(), + isSelected = it.id == state.selected.id + ) + } + } + } + } + } + } +} + +@Composable +private fun DashboardItemView( + dashboard: DeviceDashboardUiModel, + isSelected: Boolean, + onSelect: () -> Unit, + onDelete: () -> Unit, + modifier: Modifier = Modifier +) { + val (background, textColor) = if (isSelected) { + FloconTheme.colorPalette.accent.copy(alpha = 0.4f) to FloconTheme.colorPalette.onAccent + } else { + Color.Transparent to FloconTheme.colorPalette.onSurface + } + + Row( + modifier = modifier + .clip(RoundedCornerShape(8.dp)) + .background(background) + .clickable(onClick = onSelect) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + modifier = Modifier.width(14.dp), + imageVector = Icons.Outlined.Dashboard, + contentDescription = null, + colorFilter = ColorFilter.tint(textColor), + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + dashboard.id, + color = textColor, + style = FloconTheme.typography.bodyMedium, + modifier = Modifier.weight(1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + + Box( + Modifier.clip(RoundedCornerShape(4.dp)) + .background( + Color.White.copy(alpha = 0.1f) + ).clickable { + onDelete() + }.padding(2.dp), + contentAlignment = Alignment.Center, + ) { + FloconIcon( + imageVector = Icons.Outlined.Close, + tint = textColor.copy(alpha = 0.6f), + modifier = Modifier.size(14.dp) + ) + } + } +}