Skip to content

Commit

Permalink
Merge branch 'feature/copy-response-buttons' into 'main'
Browse files Browse the repository at this point in the history
copy response buttons

See merge request products/hello-http!16
  • Loading branch information
Sunny Chung committed Feb 13, 2024
2 parents f759db9 + e3c8e00 commit 4b7b727
Show file tree
Hide file tree
Showing 9 changed files with 396 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,15 @@ fun <T> MutableList<T>.upsert(entity: T, condition: (T) -> Boolean, update: (T,
}
}
}

/**
* This method is NOT thread-safe.
*/
fun <K, V> MutableMap<K, V>.getAndDoSomethingOrPut(key: K, doIfExists: (V) -> Unit, putIfNotExists: () -> V): V {
return this[key]?.also(doIfExists)
?: run {
val newValue = putIfNotExists()
this[key] = newValue
newValue
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.sunnychung.application.multiplatform.hellohttp.AppContext
import com.sunnychung.application.multiplatform.hellohttp.document.ApiSpecDI
Expand Down Expand Up @@ -77,6 +78,7 @@ import com.sunnychung.application.multiplatform.hellohttp.ux.local.LocalColor
import com.sunnychung.application.multiplatform.hellohttp.ux.local.darkColorScheme
import com.sunnychung.application.multiplatform.hellohttp.ux.local.lightColorScheme
import com.sunnychung.application.multiplatform.hellohttp.ux.viewmodel.EditNameViewModel
import com.sunnychung.application.multiplatform.hellohttp.ux.viewmodel.ErrorMessagePromptViewModel
import com.sunnychung.lib.multiplatform.kdatetime.extension.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -177,8 +179,14 @@ fun AppView() {
) {
AppText(
text = errorMessageState.message,
textAlign = TextAlign.Center,
modifier = Modifier
.background(colors.errorResponseBackground)
.background(
when (errorMessageState.type) {
ErrorMessagePromptViewModel.MessageType.Error -> colors.errorResponseBackground
ErrorMessagePromptViewModel.MessageType.Success -> colors.successfulResponseBackground
}
)
.widthIn(min = 200.dp, max = 600.dp)
.onPointerEvent(PointerEventType.Enter) {
errorMessageVM.lockDismissTime()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.sunnychung.application.multiplatform.hellohttp.ux

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.sunnychung.application.multiplatform.hellohttp.AppContext
import com.sunnychung.application.multiplatform.hellohttp.util.log
import com.sunnychung.application.multiplatform.hellohttp.ux.local.LocalColor

@Composable
fun FloatingCopyButton(textToCopy: String, size: Dp = 20.dp, innerPadding: Dp = 4.dp, modifier: Modifier = Modifier) {
val colours = LocalColor.current
val clipboardManager = LocalClipboardManager.current
AppImageButton(
resource = "copy-to-clipboard.svg",
size = size + innerPadding * 2,
innerPadding = PaddingValues(innerPadding),
color = colours.copyButton,
onClick = {
log.d { "Copied $textToCopy" }
clipboardManager.setText(AnnotatedString(textToCopy))
AppContext.ErrorMessagePromptViewModel.showSuccessMessage("Copied text")
},
modifier = modifier
.background(colours.backgroundFloatingButton, RoundedCornerShape(4.dp))
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import androidx.compose.ui.unit.dp
import com.sunnychung.application.multiplatform.hellohttp.ux.local.LocalColor

@Composable
fun KeyValueTableView(modifier: Modifier = Modifier, keyValues: List<Pair<String, String>>) {
fun KeyValueTableView(modifier: Modifier = Modifier, keyValues: List<Pair<String, String>>, isCopyable: Boolean = false) {
val colors = LocalColor.current

Column(modifier) {
Expand All @@ -30,8 +30,42 @@ fun KeyValueTableView(modifier: Modifier = Modifier, keyValues: List<Pair<String
LazyColumn {
items(items = keyValues) {
Row(modifier = Modifier.height(IntrinsicSize.Max)) {
AppTextField(value = it.first, readOnly = true, onValueChange = {}, backgroundColor = Color.Transparent, contentPadding = PaddingValues(0.dp), modifier = Modifier.weight(0.4f).fillMaxHeight().border(width = 1.dp, color = colors.placeholder, RectangleShape).padding(all = 8.dp))
AppTextField(value = it.second, readOnly = true, onValueChange = {}, backgroundColor = Color.Transparent, contentPadding = PaddingValues(0.dp), modifier = Modifier.weight(0.6f).fillMaxHeight().border(width = 1.dp, color = colors.placeholder, RectangleShape).padding(all = 8.dp))
CopyableContentContainer(
textToCopy = it.first,
isEnabled = isCopyable,
size = 16.dp,
innerPadding = 2.dp,
outerPadding = PaddingValues(top = 6.dp, end = 2.dp),
modifier = Modifier.weight(0.4f).fillMaxHeight()
.border(width = 1.dp, color = colors.placeholder, RectangleShape)
) {
AppTextField(
value = it.first,
readOnly = true,
onValueChange = {},
backgroundColor = Color.Transparent,
contentPadding = PaddingValues(0.dp),
modifier = Modifier.padding(all = 8.dp),
)
}
CopyableContentContainer(
textToCopy = it.second,
isEnabled = isCopyable,
size = 16.dp,
innerPadding = 2.dp,
outerPadding = PaddingValues(top = 6.dp, end = 2.dp),
modifier = Modifier.weight(0.6f).fillMaxHeight()
.border(width = 1.dp, color = colors.placeholder, RectangleShape)
) {
AppTextField(
value = it.second,
readOnly = true,
onValueChange = {},
backgroundColor = Color.Transparent,
contentPadding = PaddingValues(0.dp),
modifier = Modifier.padding(all = 8.dp),
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
Expand All @@ -30,17 +31,20 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.jayway.jsonpath.JsonPath
import com.sunnychung.application.multiplatform.hellohttp.AppContext
import com.sunnychung.application.multiplatform.hellohttp.network.ConnectionStatus
import com.sunnychung.application.multiplatform.hellohttp.manager.Prettifier
import com.sunnychung.application.multiplatform.hellohttp.model.Certificate
import com.sunnychung.application.multiplatform.hellohttp.model.ConnectionSecurityType
Expand All @@ -49,6 +53,7 @@ import com.sunnychung.application.multiplatform.hellohttp.model.PrettifyResult
import com.sunnychung.application.multiplatform.hellohttp.model.ProtocolApplication
import com.sunnychung.application.multiplatform.hellohttp.model.RawExchange
import com.sunnychung.application.multiplatform.hellohttp.model.UserResponse
import com.sunnychung.application.multiplatform.hellohttp.network.ConnectionStatus
import com.sunnychung.application.multiplatform.hellohttp.util.log
import com.sunnychung.application.multiplatform.hellohttp.ux.compose.rememberLast
import com.sunnychung.application.multiplatform.hellohttp.ux.local.LocalColor
Expand Down Expand Up @@ -169,7 +174,7 @@ fun ResponseViewerView(response: UserResponse, connectionStatus: ConnectionStatu
ResponseTab.Stream -> ResponseStreamView(response)

ResponseTab.Header -> if (response.headers != null) {
KeyValueTableView(keyValues = response.headers!!, modifier = Modifier.fillMaxSize().padding(8.dp))
KeyValueTableView(keyValues = response.headers!!, isCopyable = true, modifier = Modifier.fillMaxSize().padding(8.dp))
} else {
ResponseEmptyView(type = "header", isCommunicating = connectionStatus.isConnectionActive(), modifier = Modifier.fillMaxSize().padding(8.dp))
}
Expand Down Expand Up @@ -507,25 +512,28 @@ fun BodyViewerView(
PrettifyResult(contentToUse.decodeToString() ?: "")
}

CodeEditorView(
isReadOnly = true,
text = prettifyResult.prettyString,
collapsableLines = prettifyResult.collapsableLineRange,
collapsableChars = prettifyResult.collapsableCharRange,
transformations = if (selectedView.prettifier!!.formatName.contains("JSON")) {
listOf(JsonSyntaxHighlightTransformation(colours = colours))
} else {
emptyList()
},
modifier = modifier,
)
CopyableContentContainer(textToCopy = prettifyResult.prettyString, modifier = modifier) {
CodeEditorView(
isReadOnly = true,
text = prettifyResult.prettyString,
collapsableLines = prettifyResult.collapsableLineRange,
collapsableChars = prettifyResult.collapsableCharRange,
transformations = if (selectedView.prettifier!!.formatName.contains("JSON")) {
listOf(JsonSyntaxHighlightTransformation(colours = colours))
} else {
emptyList()
},
)
}
} else {
CodeEditorView(
isReadOnly = true,
text = errorMessage ?: content.decodeToString(),
textColor = colours.warning,
modifier = modifier,
)
val text = errorMessage ?: content.decodeToString()
CopyableContentContainer(textToCopy = text, modifier = modifier) {
CodeEditorView(
isReadOnly = true,
text = text,
textColor = colours.warning,
)
}
}
if (isEnableJsonPath) {
AppTextFieldWithPlaceholder(
Expand Down Expand Up @@ -591,6 +599,49 @@ fun ResponseBodyView(response: UserResponse) {
}
}

@Composable
@OptIn(ExperimentalComposeUiApi::class)
fun CopyableContentContainer(
modifier: Modifier = Modifier,
textToCopy: String,
isEnabled: Boolean = true,
size: Dp = 20.dp,
innerPadding: Dp = 4.dp,
outerPadding: PaddingValues = PaddingValues(top = 4.dp, end = 12.dp),
contentView: @Composable () -> Unit
) {
if (!isEnabled) {
Box(modifier = modifier) {
contentView()
}
return
}

var isShowCopyButton by remember { mutableStateOf(false) }

Box(
modifier = modifier
.onPointerEvent(PointerEventType.Enter) {
isShowCopyButton = true
}
.onPointerEvent(PointerEventType.Exit) {
isShowCopyButton = false
}
) {
contentView()
if (isShowCopyButton) {
FloatingCopyButton(
textToCopy = textToCopy,
size = size,
innerPadding = innerPadding,
modifier = Modifier
.align(Alignment.TopEnd)
.padding(outerPadding)
)
}
}
}

private val DATE_TIME_FORMAT = KDateTimeFormat("HH:mm:ss.lll")
private val TIMESTAMP_COLUMN_WIDTH_DP = 120.dp
private val TYPE_COLUMN_WIDTH_DP = 20.dp
Expand Down
Loading

0 comments on commit 4b7b727

Please sign in to comment.