feature: Add detail json (#61)

Co-authored-by: TEYSSANDIER Raphael <rteyssandier@sephora.fr>
This commit is contained in:
Raphael Teyssandier 2025-08-06 14:02:07 +02:00 committed by GitHub
parent 46da1787cb
commit 46a4e00094
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 177 additions and 39 deletions

View file

@ -1,12 +1,15 @@
package io.github.openflocon.flocondesktop.features.network.ui
import androidx.compose.runtime.Immutable
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkJsonUi
@Immutable
data class ContentUiState(
val selectedRequestId: String?
val selectedRequestId: String?,
val detailJsons: Set<NetworkJsonUi>
)
fun previewContentUiState() = ContentUiState(
selectedRequestId = null
selectedRequestId = null,
detailJsons = emptySet()
)

View file

@ -14,6 +14,10 @@ sealed interface NetworkAction {
data object Reset : NetworkAction
data class JsonDetail(val id: String, val json: String) : NetworkAction
data class CloseJsonDetail(val id: String) : NetworkAction
data class CopyUrl(val item: NetworkItemViewState) : NetworkAction
data class CopyCUrl(val item: NetworkItemViewState) : NetworkAction

View file

@ -15,6 +15,7 @@ import io.github.openflocon.flocondesktop.features.network.ui.mapper.toDetailUi
import io.github.openflocon.flocondesktop.features.network.ui.mapper.toUi
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkDetailViewState
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkItemViewState
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkJsonUi
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkMethodUi
import io.github.openflocon.flocondesktop.features.network.ui.view.filters.MethodFilter
import kotlinx.coroutines.flow.MutableStateFlow
@ -44,7 +45,7 @@ class NetworkViewModel(
private val filterMethod = MethodFilter()
private val contentState = MutableStateFlow(ContentUiState(selectedRequestId = null))
private val contentState = MutableStateFlow(ContentUiState(selectedRequestId = null, detailJsons = emptySet()))
private val filterUiState = MutableStateFlow(FilterUiState(query = "", methods = NetworkMethodUi.all()))
private val detailState: StateFlow<NetworkDetailViewState?> =
@ -105,6 +106,8 @@ class NetworkViewModel(
is NetworkAction.RemoveLinesAbove -> onRemoveLinesAbove(action)
is NetworkAction.FilterQuery -> onFilterQuery(action)
is NetworkAction.FilterMethod -> onFilterMethod(action)
is NetworkAction.CloseJsonDetail -> onCloseJsonDetail(action)
is NetworkAction.JsonDetail -> onJsonDetail(action)
}
}
@ -129,6 +132,24 @@ class NetworkViewModel(
feedbackDisplayer.displayMessage("copied")
}
private fun onJsonDetail(action: NetworkAction.JsonDetail) {
contentState.update { state ->
if (state.detailJsons.any { it.id == action.id })
return
state.copy(
detailJsons = state.detailJsons + NetworkJsonUi(
id = action.id,
json = action.json
)
)
}
}
private fun onCloseJsonDetail(action: NetworkAction.CloseJsonDetail) {
contentState.update { state -> state.copy(detailJsons = state.detailJsons.filterNot { it.id == action.id }.toSet()) }
}
private fun onReset() {
viewModelScope.launch(dispatcherProvider.viewModel) {
resetCurrentDeviceHttpRequestsUseCase()

View file

@ -8,6 +8,7 @@ import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkDetai
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkStatusUi
fun toDetailUi(request: FloconHttpRequestDomainModel): NetworkDetailViewState = NetworkDetailViewState(
uuid = request.uuid,
fullUrl = request.url,
method = toDetailMethodUi(request),
status = toDetailNetworkStatusUi(request.type),

View file

@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
@Immutable
data class NetworkDetailViewState(
val uuid: String,
val fullUrl: String,
val requestTimeFormatted: String,
val durationFormatted: String,

View file

@ -0,0 +1,9 @@
package io.github.openflocon.flocondesktop.features.network.ui.model
import androidx.compose.runtime.Immutable
@Immutable
data class NetworkJsonUi(
val id: String,
val json: String
)

View file

@ -310,6 +310,7 @@ private fun Response(
onToggle = {
isResponseBodyExpanded = it
},
onDetail = { onAction(NetworkAction.JsonDetail(state.requestTimeFormatted, state.responseBody)) },
modifier = Modifier.fillMaxWidth()
)
FloconSectionExpandable(
@ -330,23 +331,23 @@ private fun Response(
private fun NetworkDetailViewPreview() {
FloconTheme {
NetworkDetailView(
state =
NetworkDetailViewState(
fullUrl = "http://www.google.com",
method = NetworkDetailViewState.Method.Http(NetworkMethodUi.Http.GET),
status =
NetworkStatusUi(
text = "200",
isSuccess = true,
),
requestHeaders =
listOf(
previewNetworkDetailHeaderUi(),
previewNetworkDetailHeaderUi(),
previewNetworkDetailHeaderUi(),
),
requestBody =
"""
state = NetworkDetailViewState(
uuid = "",
fullUrl = "http://www.google.com",
method = NetworkDetailViewState.Method.Http(NetworkMethodUi.Http.GET),
status =
NetworkStatusUi(
text = "200",
isSuccess = true,
),
requestHeaders =
listOf(
previewNetworkDetailHeaderUi(),
previewNetworkDetailHeaderUi(),
previewNetworkDetailHeaderUi(),
),
requestBody =
"""
{
"id": "123",
"name": "Flocon App",
@ -359,18 +360,18 @@ private fun NetworkDetailViewPreview() {
}
}
""".trimIndent(),
responseHeaders =
listOf(
previewNetworkDetailHeaderUi(),
previewNetworkDetailHeaderUi(),
previewNetworkDetailHeaderUi(),
previewNetworkDetailHeaderUi(),
previewNetworkDetailHeaderUi(),
),
requestTimeFormatted = "00:00:00.000",
durationFormatted = "300ms",
responseBody =
"""
responseHeaders =
listOf(
previewNetworkDetailHeaderUi(),
previewNetworkDetailHeaderUi(),
previewNetworkDetailHeaderUi(),
previewNetworkDetailHeaderUi(),
previewNetworkDetailHeaderUi(),
),
requestTimeFormatted = "00:00:00.000",
durationFormatted = "300ms",
responseBody =
"""
{
"networkStatusUi": "success",
"message": "Data received and processed.",
@ -380,10 +381,10 @@ private fun NetworkDetailViewPreview() {
}
}
""".trimIndent(),
requestSize = "0kb",
responseSize = "0kb",
graphQlSection = null,
),
requestSize = "0kb",
responseSize = "0kb",
graphQlSection = null,
),
modifier = Modifier.padding(16.dp), // Padding pour la preview
onAction = {}
)

View file

@ -16,22 +16,33 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowPlacement
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.WindowState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import flocondesktop.composeapp.generated.resources.Res
import flocondesktop.composeapp.generated.resources.app_icon
import io.github.openflocon.flocondesktop.features.network.ui.NetworkAction
import io.github.openflocon.flocondesktop.features.network.ui.NetworkUiState
import io.github.openflocon.flocondesktop.features.network.ui.NetworkViewModel
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkItemViewState
import io.github.openflocon.flocondesktop.features.network.ui.model.NetworkJsonUi
import io.github.openflocon.flocondesktop.features.network.ui.model.previewGraphQlItemViewState
import io.github.openflocon.flocondesktop.features.network.ui.model.previewNetworkItemViewState
import io.github.openflocon.flocondesktop.features.network.ui.previewNetworkUiState
import io.github.openflocon.flocondesktop.features.network.ui.view.components.NetworkItemHeaderView
import io.github.openflocon.flocondesktop.features.network.ui.view.header.NetworkFilter
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconJsonTree
import org.jetbrains.compose.resources.painterResource
import io.github.openflocon.library.designsystem.components.FloconSurface
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.viewmodel.koinViewModel
@ -138,6 +149,44 @@ fun NetworkScreen(
}
}
}
val states = remember { mutableStateMapOf<NetworkJsonUi, WindowState>() }
LaunchedEffect(uiState.contentState.detailJsons) {
val deletedJson = states.keys.filter { key -> uiState.contentState.detailJsons.none { key.id == it.id } }
val addedJson = uiState.contentState.detailJsons.filter { key -> states.keys.none { key.id == it.id } }
deletedJson.forEach { states.remove(it) }
addedJson.forEach {
states.put(
it, WindowState(
placement = WindowPlacement.Floating,
position = WindowPosition(Alignment.Center)
)
)
}
}
uiState.contentState
.detailJsons
.forEach { item ->
val state = states[item]
if (state != null) {
Window(
title = "Json",
icon = painterResource(Res.drawable.app_icon),
state = state,
onCloseRequest = { onAction(NetworkAction.CloseJsonDetail(item.id)) }
) {
FloconJsonTree(
json = item.json,
modifier = Modifier.fillMaxSize()
)
}
}
}
}
@Composable

View file

@ -5,7 +5,11 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CopyAll
import androidx.compose.material.icons.outlined.Details
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material.icons.outlined.OpenInFull
import androidx.compose.material.icons.outlined.Share
import androidx.compose.material.icons.outlined.Visibility
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -22,9 +26,10 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
fun DetailSectionTitleView(
isExpanded: Boolean,
title: String,
onCopy: (() -> Unit)?,
onToggle: ((isExpanded: Boolean) -> Unit)?,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
onDetail: (() -> Unit)? = null,
onCopy: (() -> Unit)? = null,
onToggle: ((isExpanded: Boolean) -> Unit)? = null,
) {
val rotate by animateFloatAsState(
targetValue = if (isExpanded) 180f else 0f,
@ -55,6 +60,13 @@ fun DetailSectionTitleView(
color = FloconTheme.colorPalette.onBackground,
modifier = Modifier.weight(1f), // Takes remaining space
)
if (onDetail != null) {
FloconIconButton(
onClick = onDetail,
imageVector = Icons.Outlined.OpenInFull
)
}
if (onCopy != null) {
FloconIconButton(
imageVector = Icons.Outlined.CopyAll,