From 342f5f1d23c3d06b97c04669f575ffa12a19c20f Mon Sep 17 00:00:00 2001 From: Florent CHAMPIGNY Date: Tue, 2 Dec 2025 10:16:55 +0100 Subject: [PATCH] feat: [DASHBOARD] open external button (#417) Co-authored-by: Florent Champigny --- .../plugins/dashboard/mapper/JsonMapper.kt | 4 +- .../features/dashboard/DashboardViewModel.kt | 24 +++++- .../dashboard/model/DashboardArrangement.kt | 5 ++ .../model/DashboardContainerViewState.kt | 13 +++ .../dashboard/view/DashboardContainerView.kt | 67 +++++++++------ .../dashboard/view/DashboardScreen.kt | 7 +- .../features/dashboard/view/DashboardView.kt | 6 +- .../view/items/DashboardPlainTextView.kt | 85 ++++++++++++------- .../network/list/delegate/OpenBodyDelegate.kt | 64 +++++++------- .../models/DashboardElementEntity.kt | 3 +- .../remote/dashboard/mapper/DataMapper.kt | 13 +++ 11 files changed, 190 insertions(+), 101 deletions(-) diff --git a/FloconAndroid/flocon/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/dashboard/mapper/JsonMapper.kt b/FloconAndroid/flocon/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/dashboard/mapper/JsonMapper.kt index 3658e21a..9b6fdee1 100644 --- a/FloconAndroid/flocon/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/dashboard/mapper/JsonMapper.kt +++ b/FloconAndroid/flocon/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/dashboard/mapper/JsonMapper.kt @@ -221,14 +221,14 @@ internal fun CheckBoxConfig.toJson(actionId: String) = buildJsonObject { } // { -// "text" : { +// "plainText" : { // "label": "user id", // "value": "01010101010", // "type": "text" / "json" // } // } internal fun PlainTextConfig.toJson() = buildJsonObject { - putJsonObject("text") { + putJsonObject("plainText") { put("label", label) put("value", value) put("type", type) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/DashboardViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/DashboardViewModel.kt index a2fd67ce..bc746681 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/DashboardViewModel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/DashboardViewModel.kt @@ -8,9 +8,9 @@ import io.github.openflocon.domain.dashboard.usecase.DeleteCurrentDeviceSelected import io.github.openflocon.domain.dashboard.usecase.DeleteDashboardUseCase import io.github.openflocon.domain.dashboard.usecase.ObserveCurrentDeviceDashboardUseCase import io.github.openflocon.domain.dashboard.usecase.ObserveDashboardArrangementUseCase +import io.github.openflocon.domain.dashboard.usecase.SelectDashboardArrangementUseCase import io.github.openflocon.domain.dashboard.usecase.SendCheckBoxUpdateDeviceDeviceUseCase import io.github.openflocon.domain.dashboard.usecase.SendClickEventToDeviceDeviceUseCase -import io.github.openflocon.domain.dashboard.usecase.SelectDashboardArrangementUseCase import io.github.openflocon.domain.dashboard.usecase.SubmitFormToDeviceDeviceUseCase import io.github.openflocon.domain.dashboard.usecase.SubmitTextFieldToDeviceDeviceUseCase import io.github.openflocon.domain.feedback.FeedbackDisplayer @@ -20,6 +20,7 @@ import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardArra import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardViewState 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.delegate.OpenBodyDelegate import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOn @@ -36,13 +37,16 @@ class DashboardViewModel( private val dashboardSelectorDelegate: DashboardSelectorDelegate, private val dispatcherProvider: DispatcherProvider, private val feedbackDisplayer: FeedbackDisplayer, - private val deleteCurrentDeviceSelectedDashboardUseCase: DeleteCurrentDeviceSelectedDashboardUseCase, + private val deleteCurrentDeviceSelectedDashboardUseCase: + DeleteCurrentDeviceSelectedDashboardUseCase, private val deleteDashboardUseCase: DeleteDashboardUseCase, private val observeDashboardArrangementUseCase: ObserveDashboardArrangementUseCase, private val selectDashboardArrangementUseCase: SelectDashboardArrangementUseCase, + private val openBodyDelegate: OpenBodyDelegate, ) : ViewModel(dashboardSelectorDelegate) { - val deviceDashboards: StateFlow = dashboardSelectorDelegate.deviceDashboards + val deviceDashboards: StateFlow = + dashboardSelectorDelegate.deviceDashboards val arrangement: StateFlow = observeDashboardArrangementUseCase() @@ -119,9 +123,21 @@ class DashboardViewModel( selectDashboardArrangementUseCase( when (arrangement) { is DashboardArrangement.Adaptive -> DashboardArrangementDomainModel.Adaptive - is DashboardArrangement.Fixed -> DashboardArrangementDomainModel.Fixed(itemsPerRow = arrangement.itemsPerRow) + is DashboardArrangement.Fixed -> + DashboardArrangementDomainModel.Fixed( + itemsPerRow = arrangement.itemsPerRow + ) } ) } } + + fun onOpenExternalClicked(content: String) { + viewModelScope.launch(dispatcherProvider.viewModel) { + openBodyDelegate.openBodyExternally(content) + .alsoFailure { + feedbackDisplayer.displayMessage(it.message) + } + } + } } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/model/DashboardArrangement.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/model/DashboardArrangement.kt index 6ceac6a7..57d62f32 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/model/DashboardArrangement.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/model/DashboardArrangement.kt @@ -1,8 +1,13 @@ package io.github.openflocon.flocondesktop.features.dashboard.model +import androidx.compose.runtime.Immutable + +@Immutable sealed interface DashboardArrangement { + @Immutable data object Adaptive : DashboardArrangement + @Immutable data class Fixed(val itemsPerRow: Int) : DashboardArrangement } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/model/DashboardContainerViewState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/model/DashboardContainerViewState.kt index 2090f473..ace7e732 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/model/DashboardContainerViewState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/model/DashboardContainerViewState.kt @@ -1,34 +1,42 @@ package io.github.openflocon.flocondesktop.features.dashboard.model +import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Color +@Immutable data class DashboardContainerViewState( val containerName: String, val containerConfig: ContainerConfig, val rows: List, ) { + @Immutable sealed interface RowItem { + @Immutable data class Text( val label: String, val value: String, val color: Color?, ) : RowItem + @Immutable data class Label( val label: String, val color: Color?, ) : RowItem + @Immutable data class PlainText( val label: String, val value: String, ) : RowItem + @Immutable data class Button( val text: String, val id: String, ) : RowItem + @Immutable data class TextField( val label: String, val placeHolder: String?, @@ -36,6 +44,7 @@ data class DashboardContainerViewState( override val id: String, ) : RowItem, InputItem + @Immutable data class CheckBox( val label: String, override val value: Boolean, @@ -44,17 +53,21 @@ data class DashboardContainerViewState( } /** Any item that can be used to change the value of a field */ + @Immutable interface InputItem { val id: String val value: Any } + @Immutable sealed interface ContainerConfig { + @Immutable data class Form( val formId: String, val submitText: String, ) : ContainerConfig + @Immutable object Section : ContainerConfig } } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardContainerView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardContainerView.kt index 23ee22df..f1199533 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardContainerView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardContainerView.kt @@ -20,16 +20,16 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardContainerViewState +import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardContainerViewState.ContainerConfig import io.github.openflocon.flocondesktop.features.dashboard.model.previewDashboardContainerViewState import io.github.openflocon.flocondesktop.features.dashboard.view.items.DashboardButtonView import io.github.openflocon.flocondesktop.features.dashboard.view.items.DashboardCheckBoxView +import io.github.openflocon.flocondesktop.features.dashboard.view.items.DashboardLabelView import io.github.openflocon.flocondesktop.features.dashboard.view.items.DashboardPlainTextView import io.github.openflocon.flocondesktop.features.dashboard.view.items.DashboardTextFieldView import io.github.openflocon.flocondesktop.features.dashboard.view.items.DashboardTextView import io.github.openflocon.library.designsystem.FloconTheme import org.jetbrains.compose.ui.tooling.preview.Preview -import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardContainerViewState.ContainerConfig -import io.github.openflocon.flocondesktop.features.dashboard.view.items.DashboardLabelView @Composable fun DashboardContainerView( @@ -38,33 +38,36 @@ fun DashboardContainerView( submitTextField: (textFieldId: String, value: String) -> Unit, submitForm: (formId: String, values: Map) -> Unit, onUpdateCheckBox: (checkBoxId: String, value: Boolean) -> Unit, + onOpenExternalClicked: (content: String) -> Unit, modifier: Modifier = Modifier, ) { - var inputState by remember(viewState) { + var inputState by + remember(viewState) { mutableStateOf( - viewState.rows - .filterIsInstance() + viewState.rows.filterIsInstance() .associate { it.id to it.value } ) } Box( - modifier = modifier - .border(width = 1.dp, color = FloconTheme.colorPalette.secondary, shape = FloconTheme.shapes.medium) - , + modifier = + modifier.border( + width = 1.dp, + color = FloconTheme.colorPalette.secondary, + shape = FloconTheme.shapes.medium + ), ) { Column( modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), ) { Text( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 4.dp), + modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp), textAlign = TextAlign.Center, text = viewState.containerName, - style = FloconTheme.typography.headlineSmall.copy( - fontWeight = FontWeight.Bold, - ), + style = + FloconTheme.typography.headlineSmall.copy( + fontWeight = FontWeight.Bold, + ), color = FloconTheme.colorPalette.onPrimary, ) @@ -80,15 +83,21 @@ fun DashboardContainerView( rowItem = rowItem, value = inputState[rowItem.id].toString(), onValueChange = { - inputState = inputState.toMutableMap().apply { - this[rowItem.id] = it - } + inputState = + inputState.toMutableMap().apply { + this[rowItem.id] = it + } }, onSubmit = { - submitTextField(rowItem.id, inputState[rowItem.id].toString()) + submitTextField( + rowItem.id, + inputState[rowItem.id].toString() + ) }, - // A form only needs a single submit button, not on every textfield - showSubmitButton = viewState.containerConfig !is ContainerConfig.Form + // A form only needs a single submit button, not on every + // textfield + showSubmitButton = + viewState.containerConfig !is ContainerConfig.Form ) } @@ -98,9 +107,10 @@ fun DashboardContainerView( rowItem = rowItem, value = inputState[rowItem.id] as Boolean, onCheckedChange = { - inputState = inputState.toMutableMap().apply { - this[rowItem.id] = it - } + inputState = + inputState.toMutableMap().apply { + this[rowItem.id] = it + } onUpdateCheckBox(rowItem.id, it) } ) @@ -132,6 +142,7 @@ fun DashboardContainerView( DashboardPlainTextView( modifier = Modifier.fillMaxWidth(), rowItem = rowItem, + onOpenExternalClicked = onOpenExternalClicked, ) } } @@ -147,10 +158,11 @@ fun DashboardContainerView( inputState, ) }, - rowItem = DashboardContainerViewState.RowItem.Button( - id = "_", - text = viewState.containerConfig.submitText, - ), + rowItem = + DashboardContainerViewState.RowItem.Button( + id = "_", + text = viewState.containerConfig.submitText, + ), ) } } @@ -169,6 +181,7 @@ private fun DashboardContainerViewPreview() { submitTextField = { _, _ -> }, submitForm = { _, _ -> }, onUpdateCheckBox = { _, _ -> }, + onOpenExternalClicked = {}, modifier = Modifier.fillMaxWidth(), ) } 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 659eb770..d537b00c 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 @@ -51,7 +51,8 @@ fun DashboardScreen(modifier: Modifier = Modifier) { onUpdateCheckBox = viewModel::onUpdateCheckBox, deleteCurrentDashboard = viewModel::deleteCurrentDashboard, onDeleteClicked = viewModel::onDeleteClicked, - onArrangementClicked = viewModel::onArrangementClicked + onArrangementClicked = viewModel::onArrangementClicked, + onOpenExternalClicked = viewModel::onOpenExternalClicked, ) } @@ -68,6 +69,7 @@ fun DashboardScreen( submitTextField: (textFieldId: String, value: String) -> Unit, submitForm: (formId: String, formValues: Map) -> Unit, onUpdateCheckBox: (checkBoxId: String, value: Boolean) -> Unit, + onOpenExternalClicked: (content: String) -> Unit, modifier: Modifier = Modifier, ) { FloconFeature( @@ -114,7 +116,8 @@ fun DashboardScreen( submitTextField = submitTextField, submitForm = submitForm, onUpdateCheckBox = onUpdateCheckBox, - arrangement = arrangement + arrangement = arrangement, + onOpenExternalClicked = onOpenExternalClicked, ) } } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardView.kt index f0b8ed38..c22718f7 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardView.kt @@ -21,13 +21,15 @@ fun DashboardView( submitTextField: (textFieldId: String, value: String) -> Unit, submitForm: (formId: String, values: Map) -> Unit, onUpdateCheckBox: (checkBoxId: String, value: Boolean) -> Unit, + onOpenExternalClicked: (content: String) -> Unit, arrangement: DashboardArrangement, modifier: Modifier = Modifier, ) { LazyVerticalStaggeredGrid( modifier = modifier, - columns = when(arrangement) { + columns = when (arrangement) { is DashboardArrangement.Adaptive -> StaggeredGridCells.Adaptive(minSize = 300.dp) + is DashboardArrangement.Fixed -> StaggeredGridCells.Fixed(arrangement.itemsPerRow) }, horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -41,6 +43,7 @@ fun DashboardView( submitTextField = submitTextField, submitForm = submitForm, onUpdateCheckBox = onUpdateCheckBox, + onOpenExternalClicked = onOpenExternalClicked, ) } } @@ -56,6 +59,7 @@ private fun DashboardViewPreview() { submitTextField = { _, _ -> }, submitForm = { _, _ -> }, onUpdateCheckBox = { _, _ -> }, + onOpenExternalClicked = {}, arrangement = DashboardArrangement.Adaptive ) } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardPlainTextView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardPlainTextView.kt index 59212cf0..73246ae9 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardPlainTextView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardPlainTextView.kt @@ -8,21 +8,27 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.OpenInNew import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardContainerViewState import io.github.openflocon.library.designsystem.FloconTheme +import io.github.openflocon.library.designsystem.components.FloconIconButton import org.jetbrains.compose.ui.tooling.preview.Preview @Composable internal fun DashboardPlainTextView( modifier: Modifier = Modifier, rowItem: DashboardContainerViewState.RowItem.PlainText, + onOpenExternalClicked: (content: String) -> Unit, ) { SelectionContainer( modifier = modifier, @@ -31,30 +37,43 @@ internal fun DashboardPlainTextView( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(4.dp), ) { - Text( - rowItem.label, - modifier = Modifier.padding(start = 4.dp), - color = FloconTheme.colorPalette.onSurface, - style = MaterialTheme.typography.bodyMedium.copy( - fontWeight = FontWeight.Thin, - ), - ) + Box(modifier = Modifier.fillMaxWidth()) { + Text( + rowItem.label, + modifier = + Modifier.padding(start = 4.dp) + .align(Alignment.CenterStart), + color = FloconTheme.colorPalette.onSurface, + style = + MaterialTheme.typography.bodyMedium.copy( + fontWeight = FontWeight.Thin, + ), + ) + Box(modifier = Modifier.align(Alignment.CenterEnd)) { + FloconIconButton( + tooltip = "Open in external editor", + imageVector = Icons.AutoMirrored.Outlined.OpenInNew, + onClick = { onOpenExternalClicked(rowItem.value) }, + ) + } + } Box( modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp) - .padding(bottom = 8.dp) + Modifier.fillMaxWidth() .background( - color = FloconTheme.colorPalette.primary, + color = FloconTheme.colorPalette.secondary, shape = RoundedCornerShape(8.dp), - ).padding(8.dp), + ) + .padding(8.dp), ) { SelectionContainer { Text( text = rowItem.value, color = FloconTheme.colorPalette.onSurface, - style = FloconTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace), + style = + FloconTheme.typography.bodySmall.copy( + fontFamily = FontFamily.Monospace + ), ) } } @@ -65,16 +84,20 @@ internal fun DashboardPlainTextView( @Preview @Composable internal fun DashboardPlainTextViewPreview() { - val rowItem = DashboardContainerViewState.RowItem.PlainText( - label = "label", - value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eget ullamcorper elit. Pellentesque turpis ex, cursus cursus urna sed, iaculis sagittis nisl. Curabitur vehicula nunc eu metus rhoncus placerat. Vivamus at placerat ligula. Morbi ullamcorper cursus tellus, vitae molestie lorem sollicitudin euismod. Sed ullamcorper, risus vitae facilisis tempor, elit leo accumsan purus, ut ultricies augue erat et justo. Duis efficitur mauris eu finibus tincidunt. Aenean magna libero, auctor quis turpis et, viverra porta lorem. Ut tempus odio sit amet vestibulum condimentum. Donec et augue quis arcu blandit sodales. In laoreet odio id turpis ultricies, eu ornare dui blandit. Morbi hendrerit velit turpis, eget ornare ex consequat id. Nullam rhoncus, libero et sollicitudin tristique, risus ipsum luctus neque, ultricies ullamcorper felis metus non turpis. Nullam sed accumsan sem, at fermentum tortor.", - ) + val rowItem = + DashboardContainerViewState.RowItem.PlainText( + label = "label", + value = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eget ullamcorper elit. Pellentesque turpis ex, cursus cursus urna sed, iaculis sagittis nisl. Curabitur vehicula nunc eu metus rhoncus placerat. Vivamus at placerat ligula. Morbi ullamcorper cursus tellus, vitae molestie lorem sollicitudin euismod. Sed ullamcorper, risus vitae facilisis tempor, elit leo accumsan purus, ut ultricies augue erat et justo. Duis efficitur mauris eu finibus tincidunt. Aenean magna libero, auctor quis turpis et, viverra porta lorem. Ut tempus odio sit amet vestibulum condimentum. Donec et augue quis arcu blandit sodales. In laoreet odio id turpis ultricies, eu ornare dui blandit. Morbi hendrerit velit turpis, eget ornare ex consequat id. Nullam rhoncus, libero et sollicitudin tristique, risus ipsum luctus neque, ultricies ullamcorper felis metus non turpis. Nullam sed accumsan sem, at fermentum tortor.", + ) FloconTheme { DashboardPlainTextView( - modifier = Modifier.background( - FloconTheme.colorPalette.primary, - ), + modifier = + Modifier.fillMaxWidth().background( + FloconTheme.colorPalette.primary, + ), rowItem = rowItem, + onOpenExternalClicked = {}, ) } } @@ -82,9 +105,11 @@ internal fun DashboardPlainTextViewPreview() { @Preview @Composable internal fun DashboardPlainTextViewPreview_json() { - val rowItem = DashboardContainerViewState.RowItem.PlainText( - label = "json", - value = """ + val rowItem = + DashboardContainerViewState.RowItem.PlainText( + label = "json", + value = + """ { "testData": { "name": "John Doe", @@ -110,13 +135,15 @@ internal fun DashboardPlainTextViewPreview_json() { "message": "Test data loaded successfully." } """.trimIndent(), - ) + ) FloconTheme { DashboardPlainTextView( - modifier = Modifier.background( - FloconTheme.colorPalette.primary, - ), + modifier = + Modifier.fillMaxWidth().background( + FloconTheme.colorPalette.primary, + ), rowItem = rowItem, + onOpenExternalClicked = {}, ) } } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/delegate/OpenBodyDelegate.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/delegate/OpenBodyDelegate.kt index e0ac5e24..afd9d543 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/delegate/OpenBodyDelegate.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/delegate/OpenBodyDelegate.kt @@ -2,7 +2,6 @@ package io.github.openflocon.flocondesktop.features.network.list.delegate import co.touchlab.kermit.Logger import io.github.openflocon.domain.common.Either -import io.github.openflocon.domain.common.Failure import io.github.openflocon.domain.common.failure import io.github.openflocon.flocondesktop.common.utils.OpenFile import io.github.openflocon.flocondesktop.common.utils.OpenFileError @@ -11,23 +10,24 @@ import java.io.File class OpenBodyDelegate { - fun openBodyExternally(request: NetworkDetailViewState) : Either { - return openAndWriteBody( - prefix = "request_body_", - body = request.requestBody - ) + fun openBodyExternally(request: NetworkDetailViewState): Either { + return openBodyExternally(prefix = "request_body_", content = request.requestBody) } - fun openBodyExternally(response: NetworkDetailViewState.Response.Success) : Either { - return openAndWriteBody( - prefix = "response_body_", - body = response.body - ) + fun openBodyExternally( + response: NetworkDetailViewState.Response.Success + ): Either { + return openBodyExternally(prefix = "response_body_", content = response.body) } - /** - * Centralizes the logic for writing the body to a temp file and opening it. - */ + fun openBodyExternally( + content: String, + prefix: String = "content_" + ): Either { + return openAndWriteBody(prefix = prefix, body = content) + } + + /** Centralizes the logic for writing the body to a temp file and opening it. */ private fun openAndWriteBody(prefix: String, body: String): Either { if (body.isBlank()) { Logger.w("⚠️ Body is empty, nothing to open.") @@ -36,41 +36,35 @@ class OpenBodyDelegate { val extension = detectBodyExtension(body) - val tempFile = runCatching { - File.createTempFile(prefix, ".$extension") - }.getOrElse { e -> - Logger.e("❌ Failed to create temporary file: ${e.message}") - return OpenFileError.IoError(e).failure() - } + val tempFile = + runCatching { File.createTempFile(prefix, ".$extension") }.getOrElse { e -> + Logger.e("❌ Failed to create temporary file: ${e.message}") + return OpenFileError.IoError(e).failure() + } runCatching { tempFile.writeText(body) Logger.i("📝 Body written to temporary file: ${tempFile.absolutePath}") - }.onFailure { e -> - Logger.e("💥 Error while writing file: ${e.message}") - return OpenFileError.IoError(e).failure() } + .onFailure { e -> + Logger.e("💥 Error while writing file: ${e.message}") + return OpenFileError.IoError(e).failure() + } return OpenFile.openFileOnDesktop(tempFile.absolutePath) - .alsoFailure { error -> - Logger.e("❌ Failed to open file: ${error.message}") - }.alsoSuccess { - Logger.i("✅ File opened successfully: ${tempFile.absolutePath}") - } + .alsoFailure { error -> Logger.e("❌ Failed to open file: ${error.message}") } + .alsoSuccess { Logger.i("✅ File opened successfully: ${tempFile.absolutePath}") } } - /** - * Tries to infer file extension based on content. - */ + /** Tries to infer file extension based on content. */ private fun detectBodyExtension(body: String): String { val trimmed = body.trimStart() return when { trimmed.startsWith("{") || trimmed.startsWith("[") -> "json" - trimmed.startsWith("<") && trimmed.contains(" "xml" + trimmed.startsWith("<") && + trimmed.contains(" "xml" trimmed.contains(" "html" else -> "txt" diff --git a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/dashboard/models/DashboardElementEntity.kt b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/dashboard/models/DashboardElementEntity.kt index 6b467de0..e76fd57b 100644 --- a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/dashboard/models/DashboardElementEntity.kt +++ b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/dashboard/models/DashboardElementEntity.kt @@ -4,6 +4,7 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Index import androidx.room.PrimaryKey +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Entity( @@ -55,7 +56,7 @@ internal sealed interface LocalDashboardElement { data class PlainText( val label: String, val value: String, - val type: String, + @SerialName("plaintext_type") val type: String, ) : LocalDashboardElement @Serializable diff --git a/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/dashboard/mapper/DataMapper.kt b/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/dashboard/mapper/DataMapper.kt index 9700af52..18268332 100644 --- a/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/dashboard/mapper/DataMapper.kt +++ b/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/dashboard/mapper/DataMapper.kt @@ -8,6 +8,7 @@ import com.flocon.data.remote.dashboard.models.DashboardElementDataModel import com.flocon.data.remote.dashboard.models.DashboardContainerDataModel import com.flocon.data.remote.dashboard.models.FormContainerConfigDataModel import com.flocon.data.remote.dashboard.models.LabelConfigDataModel +import com.flocon.data.remote.dashboard.models.PlainTextConfigDataModel import com.flocon.data.remote.dashboard.models.SectionContainerConfigDataModel import com.flocon.data.remote.dashboard.models.TextConfigDataModel import com.flocon.data.remote.dashboard.models.TextFieldConfigDataModel @@ -35,6 +36,9 @@ fun DashboardElementDataModel.toDomain(): DashboardElementDomainModel? { text?.let { return it.toDomain() } + plainText?.let { + return it.toDomain() + } button?.let { return it.toDomain() } @@ -61,6 +65,15 @@ fun TextConfigDataModel.toDomain(): DashboardElementDomainModel.Text = Dashboard color = color, ) +fun PlainTextConfigDataModel.toDomain(): DashboardElementDomainModel.PlainText = DashboardElementDomainModel.PlainText( + label = label, + value = value, + type = when (type) { + "json" -> DashboardElementDomainModel.PlainText.Type.Json + else -> DashboardElementDomainModel.PlainText.Type.Text + } +) + fun LabelConfigDataModel.toDomain(): DashboardElementDomainModel.Label = DashboardElementDomainModel.Label( label = label, color = color,