Skip to content

Commit

Permalink
Render JSON arrays as table if possible
Browse files Browse the repository at this point in the history
  • Loading branch information
Waboodoo committed May 24, 2024
1 parent c22805e commit 7bf4238
Show file tree
Hide file tree
Showing 21 changed files with 215 additions and 45 deletions.
1 change: 1 addition & 0 deletions HTTPShortcuts/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.reorderable)
implementation(libs.composeCodeEditor)
implementation(libs.composableTable)
implementation(libs.accompanist.systemuicontroller)

/* Image cropping */
Expand Down
2 changes: 2 additions & 0 deletions HTTPShortcuts/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
<!-- Integration with Wireguard -->
<uses-permission android:name="com.wireguard.android.permission.CONTROL_TUNNELS" />

<uses-sdk tools:overrideLibrary="com.sunnychung.lib.android.composabletable" />

<uses-feature
android:name="android.hardware.location"
android:required="false" />
Expand Down
5 changes: 5 additions & 0 deletions HTTPShortcuts/app/src/main/assets/acknowledgments.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ <h2>coil</h2>
An image loading library for Android backed by Kotlin Coroutines.<br>
Licensed under Apache Version 2.0 by <a href="https://github.com/coil-kt">coil-kt</a>
</p>
<h2>Composable Table</h2>
<p>
A multiplatform Jetpack Compose library that provides a @Composable table with automatic layouts.<br>
Licensed under MIT License by <a href="https://github.com/sunny-chung">sunny-chung</a>
</p>
<h2>ColorPickerView</h2>
<p>
Android color picker for getting colors from any image by tapping on the desired color.<br>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ch.rmy.android.http_shortcuts.activities.editor.response

import android.os.Build
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
Expand Down Expand Up @@ -36,13 +37,15 @@ fun ResponseDisplayContent(
responseDisplayActions: List<ResponseDisplayAction>,
useMonospaceFont: Boolean,
fontSize: Int?,
jsonArrayAsTable: Boolean,
onResponseContentTypeChanged: (ResponseContentType?) -> Unit,
onResponseCharsetChanged: (Charset?) -> Unit,
onDialogActionChanged: (ResponseDisplayAction?) -> Unit,
onIncludeMetaInformationChanged: (Boolean) -> Unit,
onWindowActionsButtonClicked: () -> Unit,
onUseMonospaceFontChanged: (Boolean) -> Unit,
onFontSizeChanged: (Int?) -> Unit,
onJsonArrayAsTableChanged: (Boolean) -> Unit,
) {
Column(
modifier = Modifier
Expand Down Expand Up @@ -91,21 +94,6 @@ fun ResponseDisplayContent(
items = DIALOG_ACTIONS.toItems(),
onItemSelected = onDialogActionChanged,
)

AnimatedVisibility(visible = responseContentType != ResponseContentType.HTML) {
FontSizeSelection(
fontSize = fontSize,
onFontSizeChanged = onFontSizeChanged,
)
}

AnimatedVisibility(visible = responseContentType == ResponseContentType.PLAIN_TEXT) {
Checkbox(
label = stringResource(R.string.label_monospace_response),
checked = useMonospaceFont,
onCheckedChange = onUseMonospaceFontChanged,
)
}
}
ResponseHandling.UI_TYPE_WINDOW -> {
SettingsButton(
Expand All @@ -124,22 +112,30 @@ fun ResponseDisplayContent(
checked = includeMetaInformation,
onCheckedChange = onIncludeMetaInformationChanged,
)
}
}

AnimatedVisibility(visible = responseContentType != ResponseContentType.HTML) {
FontSizeSelection(
fontSize = fontSize,
onFontSizeChanged = onFontSizeChanged,
)
}
AnimatedVisibility(visible = responseContentType != ResponseContentType.HTML) {
FontSizeSelection(
fontSize = fontSize,
onFontSizeChanged = onFontSizeChanged,
)
}

AnimatedVisibility(visible = responseContentType == ResponseContentType.PLAIN_TEXT) {
Checkbox(
label = stringResource(R.string.label_monospace_response),
checked = useMonospaceFont,
onCheckedChange = onUseMonospaceFontChanged,
)
}
}
AnimatedVisibility(visible = responseContentType == ResponseContentType.PLAIN_TEXT) {
Checkbox(
label = stringResource(R.string.label_monospace_response),
checked = useMonospaceFont,
onCheckedChange = onUseMonospaceFontChanged,
)
}

AnimatedVisibility(visible = responseContentType == ResponseContentType.JSON && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Checkbox(
label = stringResource(R.string.label_json_array_as_table),
checked = jsonArrayAsTable,
onCheckedChange = onJsonArrayAsTableChanged,
)
}
}
}
Expand All @@ -151,7 +147,7 @@ private fun FontSizeSelection(
) {
SelectionField(
modifier = Modifier
.padding(top = Spacing.SMALL)
.padding(vertical = Spacing.SMALL)
.padding(horizontal = Spacing.MEDIUM),
title = stringResource(R.string.label_font_size),
selectedKey = fontSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ fun ResponseDisplayScreen() {
responseDisplayActions = viewState.responseDisplayActions,
useMonospaceFont = viewState.useMonospaceFont,
fontSize = viewState.fontSize,
jsonArrayAsTable = viewState.jsonArrayAsTable,
onResponseContentTypeChanged = viewModel::onResponseContentTypeChanged,
onResponseCharsetChanged = viewModel::onResponseCharsetChanged,
onDialogActionChanged = viewModel::onDialogActionChanged,
onIncludeMetaInformationChanged = viewModel::onIncludeMetaInformationChanged,
onWindowActionsButtonClicked = viewModel::onWindowActionsButtonClicked,
onUseMonospaceFontChanged = viewModel::onUseMonospaceFontChanged,
onFontSizeChanged = viewModel::onFontSizeChanged,
onJsonArrayAsTableChanged = viewModel::onJsonArrayAsTableChanged,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ constructor(
responseDisplayActions = responseHandling.displayActions,
useMonospaceFont = responseHandling.monospace,
fontSize = responseHandling.fontSize,
jsonArrayAsTable = responseHandling.jsonArrayAsTable,
)
}

Expand Down Expand Up @@ -136,6 +137,15 @@ constructor(
}
}

fun onJsonArrayAsTableChanged(jsonArrayAsTable: Boolean) = runAction {
updateViewState {
copy(jsonArrayAsTable = jsonArrayAsTable)
}
withProgressTracking {
temporaryShortcutRepository.setJsonArrayAsTable(jsonArrayAsTable)
}
}

fun onDismissDialog() = runAction {
updateDialogState(null)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ data class ResponseDisplayViewState(
val fontSize: Int?,
val includeMetaInformation: Boolean,
val responseDisplayActions: List<ResponseDisplayAction>,
val jsonArrayAsTable: Boolean,
)
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ class Execution(
monospace = shortcut.responseHandling?.monospace == true,
fontSize = shortcut.responseHandling?.fontSize,
actions = shortcut.responseHandling?.displayActions ?: emptyList(),
jsonArrayAsTable = shortcut.responseHandling?.jsonArrayAsTable == true,
)
val responseDataId = navigationArgStore.storeArg(responseData)
DisplayResponseActivity.IntentBuilder(shortcutName, responseDataId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
Expand All @@ -54,12 +55,15 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isUnspecified
import androidx.compose.ui.unit.sp
import ch.rmy.android.framework.extensions.openURL
import ch.rmy.android.http_shortcuts.R
import ch.rmy.android.http_shortcuts.activities.response.models.DetailInfo
import ch.rmy.android.http_shortcuts.activities.response.models.TableData
import ch.rmy.android.http_shortcuts.components.FontSize
import ch.rmy.android.http_shortcuts.components.LoadingIndicator
import ch.rmy.android.http_shortcuts.components.Spacing
import ch.rmy.android.http_shortcuts.extensions.runIf
import ch.rmy.android.http_shortcuts.http.HttpHeaders
Expand All @@ -69,6 +73,7 @@ import ch.rmy.android.http_shortcuts.utils.rememberSyntaxHighlighter
import coil.compose.AsyncImage
import coil.request.CachePolicy
import coil.request.ImageRequest
import com.sunnychung.lib.android.composabletable.ux.Table
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.engawapg.lib.zoomable.rememberZoomState
Expand All @@ -85,6 +90,8 @@ fun DisplayResponseContent(
monospace: Boolean,
fontSize: Int?,
showExternalUrlWarning: Boolean,
tableData: TableData?,
processing: Boolean,
onExternalUrlWarningHidden: (Boolean) -> Unit,
) {
var detailsExpanded by remember {
Expand Down Expand Up @@ -113,6 +120,8 @@ fun DisplayResponseContent(
monospace = monospace,
fontSize = fontSize?.sp ?: TextUnit.Unspecified,
showExternalUrlWarning = showExternalUrlWarning,
tableData = tableData,
processing = processing,
onExternalUrlWarningHidden = onExternalUrlWarningHidden,
)

Expand Down Expand Up @@ -254,6 +263,8 @@ private fun ResponseDisplay(
monospace: Boolean,
fontSize: TextUnit,
showExternalUrlWarning: Boolean,
processing: Boolean,
tableData: TableData?,
onExternalUrlWarningHidden: (Boolean) -> Unit,
) {
val context = LocalContext.current
Expand Down Expand Up @@ -319,7 +330,13 @@ private fun ResponseDisplay(
}
}
FileTypeUtil.TYPE_JSON -> {
SyntaxHighlightedText(text, language = "json", fontSize = fontSize)
if (tableData != null) {
TableView(tableData, fontSize)
} else if (!processing && text.isNotEmpty()) {
SyntaxHighlightedText(text, language = "json", fontSize = fontSize)
} else {
LoadingIndicator()
}
}
FileTypeUtil.TYPE_XML -> {
SyntaxHighlightedText(text, language = "xml", fontSize = fontSize)
Expand Down Expand Up @@ -406,12 +423,51 @@ private fun SyntaxHighlightedText(text: String, language: String, fontSize: Text
style = TextStyle(
fontFamily = FontFamily.Monospace,
fontSize = fontSize,
lineHeight = if (fontSize.isUnspecified) TextUnit.Unspecified else fontSize * 1.2f,
),
)
}
}
}

@Composable
private fun TableView(tableData: TableData, fontSize: TextUnit) {
SelectionContainer {
Table(
modifier = Modifier
.padding(4.dp)
.fillMaxWidth(),
rowCount = tableData.rows.size + 1,
columnCount = tableData.columns.size,
maxCellWidthDp = 300.dp,
stickyRowCount = 1,
) { rowIndex, columnIndex ->
if (rowIndex == 0) {
Text(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.padding(horizontal = 4.dp, vertical = 2.dp),
text = tableData.columns[columnIndex],
fontWeight = FontWeight.SemiBold,
fontSize = fontSize,
lineHeight = if (fontSize.isUnspecified) TextUnit.Unspecified else fontSize * 1.2f,
)
} else {
Text(
modifier = Modifier
.runIf(rowIndex % 2 == 0) {
background(Color(0x10909090))
}
.padding(horizontal = 4.dp, vertical = 2.dp),
text = tableData.rows[rowIndex - 1][tableData.columns[columnIndex]] ?: "",
fontSize = fontSize,
lineHeight = if (fontSize.isUnspecified) TextUnit.Unspecified else fontSize * 1.2f,
)
}
}
}
}

@Composable
private fun OpenExternalUrlDialog(
url: Uri,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ fun DisplayResponseScreen(
monospace = viewState.monospace,
fontSize = viewState.fontSize,
showExternalUrlWarning = viewState.showExternalUrlWarning,
tableData = viewState.tableData,
processing = viewState.processing,
onExternalUrlWarningHidden = viewModel::onExternalUrlWarningHidden,
)
}
Expand Down
Loading

0 comments on commit 7bf4238

Please sign in to comment.