mirror of
https://github.com/openflocon/Flocon.git
synced 2026-06-24 00:49:11 +00:00
feat: [DASHBOARD] open external button (#417)
Some checks are pending
docs / deploy (push) Waiting to run
Some checks are pending
docs / deploy (push) Waiting to run
Co-authored-by: Florent Champigny <florent@bere.al>
This commit is contained in:
parent
a718792d74
commit
342f5f1d23
11 changed files with 190 additions and 101 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<DashboardsStateUiModel> = dashboardSelectorDelegate.deviceDashboards
|
||||
val deviceDashboards: StateFlow<DashboardsStateUiModel> =
|
||||
dashboardSelectorDelegate.deviceDashboards
|
||||
|
||||
val arrangement: StateFlow<DashboardArrangement> =
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<RowItem>,
|
||||
) {
|
||||
@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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, Any>) -> 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<DashboardContainerViewState.InputItem>()
|
||||
viewState.rows.filterIsInstance<DashboardContainerViewState.InputItem>()
|
||||
.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(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, Any>) -> 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,13 +21,15 @@ fun DashboardView(
|
|||
submitTextField: (textFieldId: String, value: String) -> Unit,
|
||||
submitForm: (formId: String, values: Map<String, Any>) -> 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
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<OpenFileError, Unit> {
|
||||
return openAndWriteBody(
|
||||
prefix = "request_body_",
|
||||
body = request.requestBody
|
||||
)
|
||||
fun openBodyExternally(request: NetworkDetailViewState): Either<OpenFileError, Unit> {
|
||||
return openBodyExternally(prefix = "request_body_", content = request.requestBody)
|
||||
}
|
||||
|
||||
fun openBodyExternally(response: NetworkDetailViewState.Response.Success) : Either<OpenFileError, Unit> {
|
||||
return openAndWriteBody(
|
||||
prefix = "response_body_",
|
||||
body = response.body
|
||||
)
|
||||
fun openBodyExternally(
|
||||
response: NetworkDetailViewState.Response.Success
|
||||
): Either<OpenFileError, Unit> {
|
||||
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<OpenFileError, Unit> {
|
||||
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<OpenFileError, Unit> {
|
||||
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("</") && !trimmed.contains(
|
||||
"<html",
|
||||
ignoreCase = true
|
||||
) -> "xml"
|
||||
trimmed.startsWith("<") &&
|
||||
trimmed.contains("</") &&
|
||||
!trimmed.contains("<html", ignoreCase = true) -> "xml"
|
||||
|
||||
trimmed.contains("<html", ignoreCase = true) -> "html"
|
||||
else -> "txt"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue