From bc92e2cc2f3751bcf980f32f62fc2a79f2bc44ae Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Wed, 23 Nov 2022 16:41:21 +0330 Subject: [PATCH 01/17] implement Spannable Body For Response --- .../internal/data/entity/HttpTransaction.kt | 26 +++++ .../chucker/internal/support/SpanTextUtil.kt | 107 ++++++++++++++++++ .../transaction/TransactionPayloadFragment.kt | 93 ++++++++++----- .../chucker/internal/support/SpanUtilsTest.kt | 85 ++++++++++++++ .../chucker/sample/HttpBinHttpTask.kt | 4 +- 5 files changed, 287 insertions(+), 28 deletions(-) create mode 100644 library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt create mode 100644 library/src/test/kotlin/com/chuckerteam/chucker/internal/support/SpanUtilsTest.kt diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/data/entity/HttpTransaction.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/data/entity/HttpTransaction.kt index e37035d58..85c524c7b 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/data/entity/HttpTransaction.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/data/entity/HttpTransaction.kt @@ -4,6 +4,8 @@ package com.chuckerteam.chucker.internal.data.entity import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.text.SpannableStringBuilder +import android.text.style.ForegroundColorSpan import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Ignore @@ -11,6 +13,7 @@ import androidx.room.PrimaryKey import com.chuckerteam.chucker.internal.support.FormatUtils import com.chuckerteam.chucker.internal.support.FormattedUrl import com.chuckerteam.chucker.internal.support.JsonConverter +import com.chuckerteam.chucker.internal.support.SpanTextUtil import com.google.gson.reflect.TypeToken import okhttp3.Headers import okhttp3.HttpUrl @@ -215,6 +218,23 @@ internal class HttpTransaction( } } + /** + * This method creates [android.text.SpannableString] from body + * and add [ForegroundColorSpan] to text with different colors for better contrast between + * keys and values and etc in the body. + * + * This method just works with json content-type yet, and calls [formatBody] + * for other content-type until parser function will be developed for other content-types. + */ + private fun spanBody(body: CharSequence, contentType: String?): CharSequence { + return when { + //TODO Implement Other Content Types + contentType.isNullOrBlank() -> body + contentType.contains("json", ignoreCase = true) -> SpanTextUtil.spanJson(body) + else -> formatBody(body.toString(), contentType) + } + } + private fun formatBytes(bytes: Long): String { return FormatUtils.formatByteCount(bytes, true) } @@ -227,6 +247,12 @@ internal class HttpTransaction( return responseBody?.let { formatBody(it, responseContentType) } ?: "" } + fun getSpannedResponseBody(): CharSequence { + return responseBody?.let { + spanBody(it, responseContentType) + } ?: SpannableStringBuilder.valueOf("") + } + fun populateUrl(httpUrl: HttpUrl): HttpTransaction { val formattedUrl = FormattedUrl.fromHttpUrl(httpUrl, encoded = false) url = formattedUrl.url diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt new file mode 100644 index 000000000..f33ff7adc --- /dev/null +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt @@ -0,0 +1,107 @@ +package com.chuckerteam.chucker.internal.support + +import android.graphics.Color +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.style.ForegroundColorSpan +import androidx.core.text.isDigitsOnly +import com.google.gson.JsonElement +import com.google.gson.JsonParser + + +public class SpanTextUtil { + public companion object { + private val JSON_KEY_COLOR = Color.parseColor("#8B0057") + private val JSON_STRING_VALUE_COLOR = Color.parseColor("#2F00FF") + private val JSON_DIGIT_AND_NULL_VALUE_COLOR = Color.parseColor("#E84B31") + private val JSON_SIGN_ELEMENTS_COLOR = Color.parseColor("#474747") + + public fun spanJson(input: CharSequence): SpannableStringBuilder { + val jsonElement = try { + JsonParser.parseString(input.toString()) + } catch (e: Exception) { + return SpannableStringBuilder.valueOf(input) + } + val result = SpannableStringBuilder() + result.append(printifyRecursive(jsonElement, "")) + return result + } + + private fun printifyRecursive( + transformedJson: JsonElement, + currentIndent: String + ): SpannableStringBuilder { + var indent = currentIndent + val result = SpannableStringBuilder() + if (transformedJson.isJsonArray) { + if (transformedJson.asJsonArray.size() == 0) + return SpannableStringBuilder().appendWithColor( + "[]", + JSON_SIGN_ELEMENTS_COLOR + ) + result.appendWithColor("$indent[\n", JSON_SIGN_ELEMENTS_COLOR) + indent += "\t\t" + for (index in 0 until transformedJson.asJsonArray.size()) { + result.append(indent) + val item = transformedJson.asJsonArray[index] + if (item.isJsonObject || item.isJsonArray) result.append( + printifyRecursive( + item, + indent + ) + ) + else result.appendJsonValue(item) + if (index != transformedJson.asJsonArray.size()) + result.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") + } + indent = indent.replace("\t".toRegex(), "") + result.appendWithColor("\n$indent]", JSON_SIGN_ELEMENTS_COLOR) + } + if (transformedJson.isJsonObject) { + if (transformedJson.asJsonObject.size() == 0) + return SpannableStringBuilder().appendWithColor( + "{}", + JSON_SIGN_ELEMENTS_COLOR + ) + result.appendWithColor("$indent{\n", JSON_SIGN_ELEMENTS_COLOR) + indent += "\t\t" + var index = 0 + for (item in transformedJson.asJsonObject.entrySet()) { + result.append(indent) + index++ + result.appendWithColor("\"${item.key}\"", JSON_KEY_COLOR) + .appendWithColor(":", JSON_SIGN_ELEMENTS_COLOR) + if (item.value.isJsonObject || item.value.isJsonArray) + result.append(printifyRecursive(item.value, indent)) + else result.appendJsonValue(item.value) + if (index != transformedJson.asJsonObject.size()) + result.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") + } + result.appendWithColor("\n $indent}", JSON_SIGN_ELEMENTS_COLOR) + indent = indent.replace("\t".toRegex(), "") + } + return result + } + + private fun SpannableStringBuilder.appendWithColor(text: CharSequence, color: Int): + SpannableStringBuilder { + this.append( + text, ForegroundColorSpan(color), + Spanned.SPAN_INCLUSIVE_INCLUSIVE + ) + return this + } + + private fun SpannableStringBuilder.appendJsonValue(jsonValue: JsonElement): + SpannableStringBuilder { + val isDigit = jsonValue.isJsonPrimitive && jsonValue.asString.isDigitsOnly() + val value = if (isDigit) jsonValue.asString else jsonValue.toString() + val color = if (isDigit || jsonValue.isJsonNull) JSON_DIGIT_AND_NULL_VALUE_COLOR + else JSON_STRING_VALUE_COLOR + return this.appendWithColor( + value, + color + ) + } + } +} diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt index 81e78c4fe..d1c2dc55a 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt @@ -41,27 +41,28 @@ internal class TransactionPayloadFragment : arguments?.getSerializable(ARG_TYPE) as PayloadType } - private val saveToFile = registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri -> - val transaction = viewModel.transaction.value - val applicationContext = requireContext().applicationContext - if (uri != null && transaction != null) { - lifecycleScope.launch { - val result = saveToFile(payloadType, uri, transaction) - val toastMessageId = if (result) { - R.string.chucker_file_saved - } else { - R.string.chucker_file_not_saved + private val saveToFile = + registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri -> + val transaction = viewModel.transaction.value + val applicationContext = requireContext().applicationContext + if (uri != null && transaction != null) { + lifecycleScope.launch { + val result = saveToFile(payloadType, uri, transaction) + val toastMessageId = if (result) { + R.string.chucker_file_saved + } else { + R.string.chucker_file_not_saved + } + Toast.makeText(applicationContext, toastMessageId, Toast.LENGTH_SHORT).show() } - Toast.makeText(applicationContext, toastMessageId, Toast.LENGTH_SHORT).show() + } else { + Toast.makeText( + applicationContext, + R.string.chucker_save_failed_to_open_document, + Toast.LENGTH_SHORT + ).show() } - } else { - Toast.makeText( - applicationContext, - R.string.chucker_save_failed_to_open_document, - Toast.LENGTH_SHORT - ).show() } - } private lateinit var payloadBinding: ChuckerFragmentTransactionPayloadBinding private val payloadAdapter = TransactionBodyAdapter() @@ -200,7 +201,11 @@ internal class TransactionPayloadFragment : override fun onQueryTextChange(newText: String): Boolean { if (newText.isNotBlank() && newText.length > NUMBER_OF_IGNORED_SYMBOLS) { - payloadAdapter.highlightQueryWithColors(newText, backgroundSpanColor, foregroundSpanColor) + payloadAdapter.highlightQueryWithColors( + newText, + backgroundSpanColor, + foregroundSpanColor + ) } else { payloadAdapter.resetHighlight() } @@ -217,7 +222,7 @@ internal class TransactionPayloadFragment : val headersString: String val isBodyEncoded: Boolean - val bodyString: String + val bodyString: CharSequence if (type == PayloadType.REQUEST) { headersString = transaction.getRequestHeadersString(true) @@ -230,7 +235,7 @@ internal class TransactionPayloadFragment : } else { headersString = transaction.getResponseHeadersString(true) isBodyEncoded = transaction.isResponseBodyEncoded - bodyString = transaction.getFormattedResponseBody() + bodyString = transaction.getSpannedResponseBody() } if (headersString.isNotBlank()) { @@ -256,14 +261,33 @@ internal class TransactionPayloadFragment : when { isBodyEncoded -> { val text = requireContext().getString(R.string.chucker_body_omitted) - result.add(TransactionPayloadItem.BodyLineItem(SpannableStringBuilder.valueOf(text))) + result.add( + TransactionPayloadItem.BodyLineItem( + SpannableStringBuilder.valueOf( + text + ) + ) + ) } bodyString.isBlank() -> { val text = requireContext().getString(R.string.chucker_body_empty) - result.add(TransactionPayloadItem.BodyLineItem(SpannableStringBuilder.valueOf(text))) + result.add( + TransactionPayloadItem.BodyLineItem( + SpannableStringBuilder.valueOf( + text + ) + ) + ) } - else -> bodyString.lines().forEach { - result.add(TransactionPayloadItem.BodyLineItem(SpannableStringBuilder.valueOf(it))) + else -> { + bodyString.lines().forEach { + result.add( + TransactionPayloadItem.BodyLineItem( + if (it is SpannableStringBuilder) + it else SpannableStringBuilder.valueOf(it) + ) + ) + } } } @@ -271,7 +295,11 @@ internal class TransactionPayloadFragment : } } - private suspend fun saveToFile(type: PayloadType, uri: Uri, transaction: HttpTransaction): Boolean { + private suspend fun saveToFile( + type: PayloadType, + uri: Uri, + transaction: HttpTransaction + ): Boolean { return withContext(Dispatchers.IO) { try { requireContext().contentResolver.openFileDescriptor(uri, "w")?.use { @@ -311,4 +339,17 @@ internal class TransactionPayloadFragment : } } } + + private fun CharSequence.lines(): List { + val linesList = this.lineSequence().toList() + val result = mutableListOf() + var lineIndex = 0 + for (index in linesList.indices) { + result.add(subSequence(lineIndex, lineIndex + linesList[index].length)) + lineIndex += linesList[index].length + 1 + } + if (result.isEmpty()) + result.add(subSequence(0, length)) + return result + } } diff --git a/library/src/test/kotlin/com/chuckerteam/chucker/internal/support/SpanUtilsTest.kt b/library/src/test/kotlin/com/chuckerteam/chucker/internal/support/SpanUtilsTest.kt new file mode 100644 index 000000000..82f08a55c --- /dev/null +++ b/library/src/test/kotlin/com/chuckerteam/chucker/internal/support/SpanUtilsTest.kt @@ -0,0 +1,85 @@ +package com.chuckerteam.chucker.internal.support + +import com.google.common.truth.Truth +import org.junit.jupiter.api.Test + +internal class SpanUtilsTest { + @Test + fun `JSON can have null fields`() { + val parsedJson = SpanTextUtil.spanJson( + """{ "field": null }""" + ) + + Truth.assertThat(parsedJson).isEqualTo( + """ + { + "field": null + } + """.trimIndent() + ) + } + + @Test + fun `JSON can have empty fields`() { + val parsedJson = SpanTextUtil.spanJson( + """{ "field": "" }""" + ) + + Truth.assertThat(parsedJson).isEqualTo( + """ + { + "field": "" + } + """.trimIndent() + ) + } + + @Test + fun `JSON can be invalid`() { + val parsedJson = SpanTextUtil.spanJson( + """[{ "field": null }""" + ) + + Truth.assertThat(parsedJson).isEqualTo( + """[{ "field": null }""" + ) + } + + @Test + fun `JSON object is pretty printed`() { + val parsedJson = SpanTextUtil.spanJson( + """{ "field1": "something", "field2": "else" }""" + ) + + Truth.assertThat(parsedJson).isEqualTo( + """ + { + "field1": "something", + "field2": "else" + } + """.trimIndent() + ) + } + + @Test + fun `JSON array is pretty printed`() { + val parsedJson = SpanTextUtil.spanJson( + """[{ "field1": "something1", "field2": "else1" }, { "field1": "something2", "field2": "else2" }]""" + ) + + Truth.assertThat(parsedJson).isEqualTo( + """ + [ + { + "field1": "something1", + "field2": "else1" + }, + { + "field1": "something2", + "field2": "else2" + } + ] + """.trimIndent() + ) + } +} diff --git a/sample/src/main/kotlin/com/chuckerteam/chucker/sample/HttpBinHttpTask.kt b/sample/src/main/kotlin/com/chuckerteam/chucker/sample/HttpBinHttpTask.kt index 213f837b5..397a58cd1 100644 --- a/sample/src/main/kotlin/com/chuckerteam/chucker/sample/HttpBinHttpTask.kt +++ b/sample/src/main/kotlin/com/chuckerteam/chucker/sample/HttpBinHttpTask.kt @@ -46,7 +46,7 @@ class HttpBinHttpTask( @Suppress("MagicNumber") override fun run() = with(api) { get().enqueue(noOpCallback) - post(Data("posted")).enqueue(noOpCallback) + /*post(Data("posted")).enqueue(noOpCallback) patch(Data("patched")).enqueue(noOpCallback) put(Data("put")).enqueue(noOpCallback) delete().enqueue(noOpCallback) @@ -77,7 +77,7 @@ class HttpBinHttpTask( redirectTo("https://ascii.cl?parameter=%22Click+on+%27URL+Encode%27%21%22").enqueue(noOpCallback) redirectTo("https://ascii.cl?parameter=\"Click on 'URL Encode'!\"").enqueue(noOpCallback) postForm("Value 1", "Value with symbols &$%").enqueue(noOpCallback) - postRawRequestBody(oneShotRequestBody()).enqueue(noOpCallback) + postRawRequestBody(oneShotRequestBody()).enqueue(noOpCallback)*/ } private fun oneShotRequestBody() = object : RequestBody() { From 079b1521f81acb80d70ee183ddc9feea4dcb09bc Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Sun, 27 Nov 2022 15:25:02 +0330 Subject: [PATCH 02/17] add some test case --- build.gradle | 3 ++ library/build.gradle | 7 ++++ .../com/chuckerteam/chucker/SpanUtilTest.kt} | 37 +++++++++++-------- .../chucker/internal/support/SpanTextUtil.kt | 29 +++++++++------ 4 files changed, 50 insertions(+), 26 deletions(-) rename library/src/{test/kotlin/com/chuckerteam/chucker/internal/support/SpanUtilsTest.kt => androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt} (62%) diff --git a/build.gradle b/build.gradle index aa78a5e8d..5d12de0b3 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,9 @@ buildscript { mockkVersion = '1.13.2' robolectricVersion = '4.9' truthVersion = '1.1.3' + androidXTestRunner = '1.5.1' + androidXTestRules = '1.5.0' + androidXTestExt = '1.1.4' // Publishing nexusStagingPlugin = '0.30.0' diff --git a/library/build.gradle b/library/build.gradle index fd9799664..38e6ca608 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -16,6 +16,7 @@ android { minSdkVersion rootProject.minSdkVersion consumerProguardFiles 'proguard-rules.pro' resValue("string", "chucker_version", "$VERSION_NAME") + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } kotlinOptions { @@ -90,6 +91,12 @@ dependencies { testImplementation "androidx.arch.core:core-testing:$androidXCoreVersion" testImplementation "com.google.truth:truth:$truthVersion" testImplementation "org.robolectric:robolectric:$robolectricVersion" + + androidTestImplementation "junit:junit:$junit4Version" + androidTestImplementation "androidx.test:runner:$androidXTestRunner" + androidTestImplementation "androidx.test:rules:$androidXTestRules" + androidTestImplementation "com.google.truth:truth:$truthVersion" + androidTestImplementation "androidx.test.ext:junit:$androidXTestExt" } apply from: rootProject.file('gradle/gradle-mvn-push.gradle') diff --git a/library/src/test/kotlin/com/chuckerteam/chucker/internal/support/SpanUtilsTest.kt b/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt similarity index 62% rename from library/src/test/kotlin/com/chuckerteam/chucker/internal/support/SpanUtilsTest.kt rename to library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt index 82f08a55c..2788316e3 100644 --- a/library/src/test/kotlin/com/chuckerteam/chucker/internal/support/SpanUtilsTest.kt +++ b/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt @@ -1,16 +1,24 @@ -package com.chuckerteam.chucker.internal.support +package com.chuckerteam.chucker +import android.annotation.SuppressLint +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.chuckerteam.chucker.internal.support.SpanTextUtil import com.google.common.truth.Truth -import org.junit.jupiter.api.Test +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith -internal class SpanUtilsTest { +@RunWith(AndroidJUnit4::class) +public class SpanUtilTest { + @SuppressLint("CheckResult") @Test - fun `JSON can have null fields`() { + public fun testt() { + val parsedJson = SpanTextUtil.spanJson( """{ "field": null }""" ) - - Truth.assertThat(parsedJson).isEqualTo( + Assert.assertEquals( + parsedJson.toString(), """ { "field": null @@ -18,14 +26,13 @@ internal class SpanUtilsTest { """.trimIndent() ) } - @Test - fun `JSON can have empty fields`() { + public fun json_can_have_empty_fields() { val parsedJson = SpanTextUtil.spanJson( """{ "field": "" }""" ) - Truth.assertThat(parsedJson).isEqualTo( + Truth.assertThat(parsedJson.toString()).isEqualTo( """ { "field": "" @@ -35,23 +42,23 @@ internal class SpanUtilsTest { } @Test - fun `JSON can be invalid`() { + public fun json_can_be_invalid() { val parsedJson = SpanTextUtil.spanJson( """[{ "field": null }""" ) - Truth.assertThat(parsedJson).isEqualTo( + Truth.assertThat(parsedJson.toString()).isEqualTo( """[{ "field": null }""" ) } @Test - fun `JSON object is pretty printed`() { + public fun json_object_is_pretty_printed() { val parsedJson = SpanTextUtil.spanJson( """{ "field1": "something", "field2": "else" }""" ) - Truth.assertThat(parsedJson).isEqualTo( + Truth.assertThat(parsedJson.toString()).isEqualTo( """ { "field1": "something", @@ -62,12 +69,12 @@ internal class SpanUtilsTest { } @Test - fun `JSON array is pretty printed`() { + public fun json_array_is_pretty_printed() { val parsedJson = SpanTextUtil.spanJson( """[{ "field1": "something1", "field2": "else1" }, { "field1": "something2", "field2": "else2" }]""" ) - Truth.assertThat(parsedJson).isEqualTo( + Truth.assertThat(parsedJson.toString()).isEqualTo( """ [ { diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt index f33ff7adc..4a358893b 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt @@ -40,9 +40,8 @@ public class SpanTextUtil { JSON_SIGN_ELEMENTS_COLOR ) result.appendWithColor("$indent[\n", JSON_SIGN_ELEMENTS_COLOR) - indent += "\t\t" + indent += " " for (index in 0 until transformedJson.asJsonArray.size()) { - result.append(indent) val item = transformedJson.asJsonArray[index] if (item.isJsonObject || item.isJsonArray) result.append( printifyRecursive( @@ -50,11 +49,15 @@ public class SpanTextUtil { indent ) ) - else result.appendJsonValue(item) - if (index != transformedJson.asJsonArray.size()) + else { + result.append(indent) + result.appendJsonValue(item) + } + if (index != transformedJson.asJsonArray.size() - 1) result.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") } - indent = indent.replace("\t".toRegex(), "") + if (indent.length > 1) + indent = indent.substring(2) result.appendWithColor("\n$indent]", JSON_SIGN_ELEMENTS_COLOR) } if (transformedJson.isJsonObject) { @@ -64,7 +67,7 @@ public class SpanTextUtil { JSON_SIGN_ELEMENTS_COLOR ) result.appendWithColor("$indent{\n", JSON_SIGN_ELEMENTS_COLOR) - indent += "\t\t" + indent += " " var index = 0 for (item in transformedJson.asJsonObject.entrySet()) { result.append(indent) @@ -72,13 +75,14 @@ public class SpanTextUtil { result.appendWithColor("\"${item.key}\"", JSON_KEY_COLOR) .appendWithColor(":", JSON_SIGN_ELEMENTS_COLOR) if (item.value.isJsonObject || item.value.isJsonArray) - result.append(printifyRecursive(item.value, indent)) + result.append(" " + printifyRecursive(item.value, indent)) else result.appendJsonValue(item.value) if (index != transformedJson.asJsonObject.size()) result.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") } - result.appendWithColor("\n $indent}", JSON_SIGN_ELEMENTS_COLOR) - indent = indent.replace("\t".toRegex(), "") + if (indent.length > 1) + indent = indent.substring(2) + result.appendWithColor("\n$indent}", JSON_SIGN_ELEMENTS_COLOR) } return result } @@ -94,12 +98,15 @@ public class SpanTextUtil { private fun SpannableStringBuilder.appendJsonValue(jsonValue: JsonElement): SpannableStringBuilder { - val isDigit = jsonValue.isJsonPrimitive && jsonValue.asString.isDigitsOnly() + val isDigit = jsonValue.isJsonNull.not() && + jsonValue.asString.isNotEmpty() && + jsonValue.isJsonPrimitive && + jsonValue.asString.isDigitsOnly() val value = if (isDigit) jsonValue.asString else jsonValue.toString() val color = if (isDigit || jsonValue.isJsonNull) JSON_DIGIT_AND_NULL_VALUE_COLOR else JSON_STRING_VALUE_COLOR return this.appendWithColor( - value, + " $value", color ) } From cab7bfc9bc455fcb2ad75531a7006ffd9d0426b1 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Sun, 27 Nov 2022 15:49:13 +0330 Subject: [PATCH 03/17] fix color issue --- .../com/chuckerteam/chucker/internal/support/SpanTextUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt index 4a358893b..59263a29c 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt @@ -75,7 +75,7 @@ public class SpanTextUtil { result.appendWithColor("\"${item.key}\"", JSON_KEY_COLOR) .appendWithColor(":", JSON_SIGN_ELEMENTS_COLOR) if (item.value.isJsonObject || item.value.isJsonArray) - result.append(" " + printifyRecursive(item.value, indent)) + result.append(" ").append(printifyRecursive(item.value, indent)) else result.appendJsonValue(item.value) if (index != transformedJson.asJsonObject.size()) result.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") From 30ee6ed4879ad80bf60aa8efb372eae9facd0549 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Sun, 27 Nov 2022 15:49:58 +0330 Subject: [PATCH 04/17] rename test function --- .../androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt b/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt index 2788316e3..cd7e4e866 100644 --- a/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt +++ b/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt @@ -12,7 +12,7 @@ import org.junit.runner.RunWith public class SpanUtilTest { @SuppressLint("CheckResult") @Test - public fun testt() { + public fun json_can_have_null_value() { val parsedJson = SpanTextUtil.spanJson( """{ "field": null }""" From 7cf8087e7d721094e0d203e88e25c2f21ead6439 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Sun, 27 Nov 2022 17:42:31 +0330 Subject: [PATCH 05/17] add spanned request body --- .../chucker/internal/data/entity/HttpTransaction.kt | 5 +++++ .../internal/ui/transaction/TransactionPayloadFragment.kt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/data/entity/HttpTransaction.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/data/entity/HttpTransaction.kt index 85c524c7b..698d58271 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/data/entity/HttpTransaction.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/data/entity/HttpTransaction.kt @@ -243,6 +243,11 @@ internal class HttpTransaction( return requestBody?.let { formatBody(it, requestContentType) } ?: "" } + fun getSpannedRequestBody(): CharSequence { + return requestBody?.let { spanBody(it, requestContentType) } + ?: SpannableStringBuilder.valueOf("") + } + fun getFormattedResponseBody(): String { return responseBody?.let { formatBody(it, responseContentType) } ?: "" } diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt index d1c2dc55a..4bc1883cd 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt @@ -228,7 +228,7 @@ internal class TransactionPayloadFragment : headersString = transaction.getRequestHeadersString(true) isBodyEncoded = transaction.isRequestBodyEncoded bodyString = if (formatRequestBody) { - transaction.getFormattedRequestBody() + transaction.getSpannedRequestBody() } else { transaction.requestBody ?: "" } From 2d16c8d8f1625785927d88b99abe92891544bc1b Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Mon, 28 Nov 2022 13:35:07 +0330 Subject: [PATCH 06/17] handle adding and removing highlighting spans when search a word --- .../internal/support/SearchHighlightUtil.kt | 8 ++--- .../chucker/internal/support/SpanTextUtil.kt | 4 ++- .../transaction/TransactionPayloadAdapter.kt | 34 ++++++++++++++----- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SearchHighlightUtil.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SearchHighlightUtil.kt index 64f4cd605..ff8e70614 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SearchHighlightUtil.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SearchHighlightUtil.kt @@ -11,12 +11,12 @@ import android.text.style.UnderlineSpan * * @param search the text to highlight */ -internal fun String.highlightWithDefinedColors( +internal fun SpannableStringBuilder.highlightWithDefinedColors( search: String, backgroundColor: Int, foregroundColor: Int ): SpannableStringBuilder { - val startIndexes = indexesOf(this, search) + val startIndexes = indexesOf(this.toString(), search) return applyColoredSpannable(this, startIndexes, search.length, backgroundColor, foregroundColor) } @@ -31,14 +31,14 @@ private fun indexesOf(text: String, search: String): List { } private fun applyColoredSpannable( - text: String, + text: SpannableStringBuilder, indexes: List, length: Int, backgroundColor: Int, foregroundColor: Int ): SpannableStringBuilder { return indexes - .fold(SpannableStringBuilder(text)) { builder, position -> + .fold(text) { builder, position -> builder.setSpan( UnderlineSpan(), position, diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt index 59263a29c..1bdee9550 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt @@ -90,7 +90,7 @@ public class SpanTextUtil { private fun SpannableStringBuilder.appendWithColor(text: CharSequence, color: Int): SpannableStringBuilder { this.append( - text, ForegroundColorSpan(color), + text, ChuckerForegroundColorSpan(color), Spanned.SPAN_INCLUSIVE_INCLUSIVE ) return this @@ -111,4 +111,6 @@ public class SpanTextUtil { ) } } + + public class ChuckerForegroundColorSpan(color: Int) : ForegroundColorSpan(color) } diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt index de7c4dfea..f48803454 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt @@ -7,12 +7,14 @@ import android.text.Spanned import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.text.getSpans import androidx.recyclerview.widget.RecyclerView import com.chuckerteam.chucker.R import com.chuckerteam.chucker.databinding.ChuckerTransactionItemBodyLineBinding import com.chuckerteam.chucker.databinding.ChuckerTransactionItemHeadersBinding import com.chuckerteam.chucker.databinding.ChuckerTransactionItemImageBinding import com.chuckerteam.chucker.internal.support.ChessboardDrawable +import com.chuckerteam.chucker.internal.support.SpanTextUtil import com.chuckerteam.chucker.internal.support.highlightWithDefinedColors /** @@ -69,15 +71,15 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter if (item.line.contains(newText, ignoreCase = true)) { - item.line.clearSpans() - item.line = item.line.toString() - .highlightWithDefinedColors(newText, backgroundColor, foregroundColor) + item.line.clearHighlightSpans() + item.line = + item.line + .highlightWithDefinedColors(newText, backgroundColor, foregroundColor) notifyItemChanged(index + 1) } else { // Let's clear the spans if we haven't found the query string. - val spans = item.line.getSpans(0, item.line.length - 1, Any::class.java) - if (spans.isNotEmpty()) { - item.line.clearSpans() + val removedSpansCount = item.line.clearHighlightSpans() + if (removedSpansCount > 0) { notifyItemChanged(index + 1) } } @@ -88,9 +90,8 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter() .withIndex() .forEach { (index, item) -> - val spans = item.line.getSpans(0, item.line.length - 1, Any::class.java) - if (spans.isNotEmpty()) { - item.line.clearSpans() + val removedSpansCount = item.line.clearHighlightSpans() + if (removedSpansCount > 0) { notifyItemChanged(index + 1) } } @@ -101,6 +102,21 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter(0, length) + for (span in spanList) + if (span !is SpanTextUtil.ChuckerForegroundColorSpan) { + removeSpan(span) + removedSpansCount++ + } + return removedSpansCount + } } internal sealed class TransactionPayloadViewHolder(view: View) : RecyclerView.ViewHolder(view) { From 9d56bd8d5e56bc3a8f437d80ca736fcb4cf880d0 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Mon, 28 Nov 2022 14:15:17 +0330 Subject: [PATCH 07/17] fix a bug when highlighting searched text and there is no header item at TransactionPayloadAdapter.kt --- .../transaction/TransactionPayloadAdapter.kt | 30 +++++++++++++------ .../transaction/TransactionPayloadFragment.kt | 14 ++++----- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt index f48803454..f882e1d02 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt @@ -25,11 +25,13 @@ import com.chuckerteam.chucker.internal.support.highlightWithDefinedColors internal class TransactionBodyAdapter : RecyclerView.Adapter() { private val items = arrayListOf() + private var containsHeader = false - fun setItems(bodyItems: List) { + fun setItems(bodyItems: List, containsHeader: Boolean) { val previousItemCount = items.size items.clear() items.addAll(bodyItems) + this.containsHeader = containsHeader notifyItemRangeRemoved(0, previousItemCount) notifyItemRangeInserted(0, items.size) } @@ -38,19 +40,25 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter { - val headersItemBinding = ChuckerTransactionItemHeadersBinding.inflate(inflater, parent, false) + val headersItemBinding = + ChuckerTransactionItemHeadersBinding.inflate(inflater, parent, false) TransactionPayloadViewHolder.HeaderViewHolder(headersItemBinding) } TYPE_BODY_LINE -> { - val bodyItemBinding = ChuckerTransactionItemBodyLineBinding.inflate(inflater, parent, false) + val bodyItemBinding = + ChuckerTransactionItemBodyLineBinding.inflate(inflater, parent, false) TransactionPayloadViewHolder.BodyLineViewHolder(bodyItemBinding) } else -> { - val imageItemBinding = ChuckerTransactionItemImageBinding.inflate(inflater, parent, false) + val imageItemBinding = + ChuckerTransactionItemImageBinding.inflate(inflater, parent, false) TransactionPayloadViewHolder.ImageViewHolder(imageItemBinding) } } @@ -66,7 +74,11 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter() .withIndex() .forEach { (index, item) -> @@ -75,12 +87,12 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter 0) { - notifyItemChanged(index + 1) + notifyItemChanged(index + if (containsHeader) 1 else 0) } } } @@ -92,7 +104,7 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter val removedSpansCount = item.line.clearHighlightSpans() if (removedSpansCount > 0) { - notifyItemChanged(index + 1) + notifyItemChanged(index + if (containsHeader) 1 else 0) } } } diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt index 4bc1883cd..145cf29fc 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt @@ -104,10 +104,10 @@ internal class TransactionPayloadFragment : payloadBinding.loadingProgress.visibility = View.VISIBLE val result = processPayload(payloadType, transaction, formatRequestBody) - if (result.isEmpty()) { + if (result.second.isEmpty()) { showEmptyState() } else { - payloadAdapter.setItems(result) + payloadAdapter.setItems(result.second, result.first) showPayloadState() } // Invalidating menu, because we need to hide menu items for empty payloads @@ -216,7 +216,7 @@ internal class TransactionPayloadFragment : type: PayloadType, transaction: HttpTransaction, formatRequestBody: Boolean - ): MutableList { + ): Pair> { return withContext(Dispatchers.Default) { val result = mutableListOf() @@ -237,8 +237,9 @@ internal class TransactionPayloadFragment : isBodyEncoded = transaction.isResponseBodyEncoded bodyString = transaction.getSpannedResponseBody() } - + var containsHeader = false if (headersString.isNotBlank()) { + containsHeader = true result.add( TransactionPayloadItem.HeaderItem( HtmlCompat.fromHtml( @@ -255,7 +256,7 @@ internal class TransactionPayloadFragment : if (type == PayloadType.RESPONSE && responseBitmap != null) { val bitmapLuminance = responseBitmap.calculateLuminance() result.add(TransactionPayloadItem.ImageItem(responseBitmap, bitmapLuminance)) - return@withContext result + return@withContext Pair(containsHeader, result) } when { @@ -290,8 +291,7 @@ internal class TransactionPayloadFragment : } } } - - return@withContext result + return@withContext Pair(containsHeader, result) } } From 689f7f043a95a1478a5b80ea6f6ceb833adc0363 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Tue, 29 Nov 2022 11:29:51 +0330 Subject: [PATCH 08/17] try to resolve detekt errors --- .../chucker/internal/support/SpanTextUtil.kt | 10 +++---- .../transaction/TransactionPayloadFragment.kt | 27 +++---------------- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt index 1bdee9550..d3f3c11ca 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt @@ -7,6 +7,7 @@ import android.text.style.ForegroundColorSpan import androidx.core.text.isDigitsOnly import com.google.gson.JsonElement import com.google.gson.JsonParser +import com.google.gson.JsonSyntaxException public class SpanTextUtil { @@ -19,7 +20,8 @@ public class SpanTextUtil { public fun spanJson(input: CharSequence): SpannableStringBuilder { val jsonElement = try { JsonParser.parseString(input.toString()) - } catch (e: Exception) { + } catch (e: JsonSyntaxException) { + e.printStackTrace() return SpannableStringBuilder.valueOf(input) } val result = SpannableStringBuilder() @@ -56,8 +58,7 @@ public class SpanTextUtil { if (index != transformedJson.asJsonArray.size() - 1) result.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") } - if (indent.length > 1) - indent = indent.substring(2) + indent = indent.dropLast(2) result.appendWithColor("\n$indent]", JSON_SIGN_ELEMENTS_COLOR) } if (transformedJson.isJsonObject) { @@ -80,8 +81,7 @@ public class SpanTextUtil { if (index != transformedJson.asJsonObject.size()) result.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") } - if (indent.length > 1) - indent = indent.substring(2) + indent = indent.dropLast(2) result.appendWithColor("\n$indent}", JSON_SIGN_ELEMENTS_COLOR) } return result diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt index 145cf29fc..a024db63e 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt @@ -262,33 +262,14 @@ internal class TransactionPayloadFragment : when { isBodyEncoded -> { val text = requireContext().getString(R.string.chucker_body_omitted) - result.add( - TransactionPayloadItem.BodyLineItem( - SpannableStringBuilder.valueOf( - text - ) - ) - ) + result.add(TransactionPayloadItem.BodyLineItem(SpannableStringBuilder.valueOf(text))) } bodyString.isBlank() -> { val text = requireContext().getString(R.string.chucker_body_empty) - result.add( - TransactionPayloadItem.BodyLineItem( - SpannableStringBuilder.valueOf( - text - ) - ) - ) + result.add(TransactionPayloadItem.BodyLineItem(SpannableStringBuilder.valueOf(text))) } - else -> { - bodyString.lines().forEach { - result.add( - TransactionPayloadItem.BodyLineItem( - if (it is SpannableStringBuilder) - it else SpannableStringBuilder.valueOf(it) - ) - ) - } + else -> bodyString.lines().forEach { + result.add(TransactionPayloadItem.BodyLineItem(if (it is SpannableStringBuilder) it else SpannableStringBuilder.valueOf(it))) } } return@withContext Pair(containsHeader, result) From 86e0221ce56d5963ed1609b3f7d312286b69cf25 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Tue, 29 Nov 2022 12:34:51 +0330 Subject: [PATCH 09/17] resolve detekt errors --- .../com/chuckerteam/chucker/internal/support/SpanTextUtil.kt | 2 +- .../internal/ui/transaction/TransactionPayloadFragment.kt | 4 +++- .../kotlin/com/chuckerteam/chucker/sample/HttpBinHttpTask.kt | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt index d3f3c11ca..f9897c6fc 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt @@ -21,7 +21,7 @@ public class SpanTextUtil { val jsonElement = try { JsonParser.parseString(input.toString()) } catch (e: JsonSyntaxException) { - e.printStackTrace() + Logger.error("Json structure is invalid") return SpannableStringBuilder.valueOf(input) } val result = SpannableStringBuilder() diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt index a024db63e..478a548e2 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt @@ -269,7 +269,9 @@ internal class TransactionPayloadFragment : result.add(TransactionPayloadItem.BodyLineItem(SpannableStringBuilder.valueOf(text))) } else -> bodyString.lines().forEach { - result.add(TransactionPayloadItem.BodyLineItem(if (it is SpannableStringBuilder) it else SpannableStringBuilder.valueOf(it))) + result.add(TransactionPayloadItem.BodyLineItem( + if (it is SpannableStringBuilder) it + else SpannableStringBuilder.valueOf(it))) } } return@withContext Pair(containsHeader, result) diff --git a/sample/src/main/kotlin/com/chuckerteam/chucker/sample/HttpBinHttpTask.kt b/sample/src/main/kotlin/com/chuckerteam/chucker/sample/HttpBinHttpTask.kt index 397a58cd1..213f837b5 100644 --- a/sample/src/main/kotlin/com/chuckerteam/chucker/sample/HttpBinHttpTask.kt +++ b/sample/src/main/kotlin/com/chuckerteam/chucker/sample/HttpBinHttpTask.kt @@ -46,7 +46,7 @@ class HttpBinHttpTask( @Suppress("MagicNumber") override fun run() = with(api) { get().enqueue(noOpCallback) - /*post(Data("posted")).enqueue(noOpCallback) + post(Data("posted")).enqueue(noOpCallback) patch(Data("patched")).enqueue(noOpCallback) put(Data("put")).enqueue(noOpCallback) delete().enqueue(noOpCallback) @@ -77,7 +77,7 @@ class HttpBinHttpTask( redirectTo("https://ascii.cl?parameter=%22Click+on+%27URL+Encode%27%21%22").enqueue(noOpCallback) redirectTo("https://ascii.cl?parameter=\"Click on 'URL Encode'!\"").enqueue(noOpCallback) postForm("Value 1", "Value with symbols &$%").enqueue(noOpCallback) - postRawRequestBody(oneShotRequestBody()).enqueue(noOpCallback)*/ + postRawRequestBody(oneShotRequestBody()).enqueue(noOpCallback) } private fun oneShotRequestBody() = object : RequestBody() { From a1b459e2d6ff0bfc5ba9d42df2c45e4cbb7cea66 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Tue, 29 Nov 2022 12:40:25 +0330 Subject: [PATCH 10/17] resolve detekt errors --- .../com/chuckerteam/chucker/internal/support/SpanTextUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt index f9897c6fc..7bead4cbc 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt @@ -21,7 +21,7 @@ public class SpanTextUtil { val jsonElement = try { JsonParser.parseString(input.toString()) } catch (e: JsonSyntaxException) { - Logger.error("Json structure is invalid") + Logger.warn("Json structure is invalid so it can not be formatted",e) return SpannableStringBuilder.valueOf(input) } val result = SpannableStringBuilder() From ec23c91919d03846f0e06f23ae690e3d528eac12 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Sun, 25 Dec 2022 09:38:05 +0330 Subject: [PATCH 11/17] refactor SpanTextUtil.kt --- .../chucker/internal/support/SpanTextUtil.kt | 121 ++++++++++-------- 1 file changed, 68 insertions(+), 53 deletions(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt index 7bead4cbc..e21043535 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt @@ -21,70 +21,84 @@ public class SpanTextUtil { val jsonElement = try { JsonParser.parseString(input.toString()) } catch (e: JsonSyntaxException) { - Logger.warn("Json structure is invalid so it can not be formatted",e) + Logger.warn("Json structure is invalid so it can not be formatted", e) return SpannableStringBuilder.valueOf(input) } - val result = SpannableStringBuilder() - result.append(printifyRecursive(jsonElement, "")) - return result + val sb = SpannableStringBuilder() + printifyRecursive(jsonElement, StringBuilder(""), sb) + return sb } private fun printifyRecursive( transformedJson: JsonElement, - currentIndent: String - ): SpannableStringBuilder { - var indent = currentIndent - val result = SpannableStringBuilder() + currentIndent: StringBuilder, + sb: SpannableStringBuilder + ) { if (transformedJson.isJsonArray) { - if (transformedJson.asJsonArray.size() == 0) - return SpannableStringBuilder().appendWithColor( - "[]", - JSON_SIGN_ELEMENTS_COLOR - ) - result.appendWithColor("$indent[\n", JSON_SIGN_ELEMENTS_COLOR) - indent += " " - for (index in 0 until transformedJson.asJsonArray.size()) { - val item = transformedJson.asJsonArray[index] - if (item.isJsonObject || item.isJsonArray) result.append( - printifyRecursive( - item, - indent - ) - ) - else { - result.append(indent) - result.appendJsonValue(item) - } - if (index != transformedJson.asJsonArray.size() - 1) - result.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") - } - indent = indent.dropLast(2) - result.appendWithColor("\n$indent]", JSON_SIGN_ELEMENTS_COLOR) + printifyJsonArray(sb, currentIndent, transformedJson) } if (transformedJson.isJsonObject) { - if (transformedJson.asJsonObject.size() == 0) - return SpannableStringBuilder().appendWithColor( - "{}", - JSON_SIGN_ELEMENTS_COLOR - ) - result.appendWithColor("$indent{\n", JSON_SIGN_ELEMENTS_COLOR) - indent += " " - var index = 0 - for (item in transformedJson.asJsonObject.entrySet()) { - result.append(indent) - index++ - result.appendWithColor("\"${item.key}\"", JSON_KEY_COLOR) - .appendWithColor(":", JSON_SIGN_ELEMENTS_COLOR) - if (item.value.isJsonObject || item.value.isJsonArray) - result.append(" ").append(printifyRecursive(item.value, indent)) - else result.appendJsonValue(item.value) - if (index != transformedJson.asJsonObject.size()) - result.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") + printifyJsonObject(sb, currentIndent, transformedJson) + } + } + + private fun printifyJsonArray( + sb: SpannableStringBuilder, + indent: StringBuilder, + transformedJson: JsonElement + ) { + if (transformedJson.asJsonArray.isEmpty) { + sb.appendWithColor( + "[]", + JSON_SIGN_ELEMENTS_COLOR + ) + return + } + sb.appendWithColor("$indent[\n", JSON_SIGN_ELEMENTS_COLOR) + indent.append(" ") + for (index in 0 until transformedJson.asJsonArray.size()) { + val item = transformedJson.asJsonArray[index] + if (item.isJsonObject || item.isJsonArray) + printifyRecursive(item, indent, sb) + else { + sb.append(indent) + sb.appendJsonValue(item) } - indent = indent.dropLast(2) - result.appendWithColor("\n$indent}", JSON_SIGN_ELEMENTS_COLOR) + if (index != transformedJson.asJsonArray.size() - 1) + sb.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") } - return result + val finalIndent = StringBuilder(indent.dropLast(2)) + sb.appendWithColor("\n$finalIndent]", JSON_SIGN_ELEMENTS_COLOR) + } + + private fun printifyJsonObject( + sb: SpannableStringBuilder, + indentBuilder: StringBuilder, + transformedJson: JsonElement + ) { + if (transformedJson.asJsonObject.size() == 0) { + sb.appendWithColor( + "{}", + JSON_SIGN_ELEMENTS_COLOR + ) + return + } + sb.appendWithColor("${indentBuilder}{\n", JSON_SIGN_ELEMENTS_COLOR) + indentBuilder.append(" ") + var index = 0 + for (item in transformedJson.asJsonObject.entrySet()) { + sb.append(indentBuilder) + index++ + sb.appendWithColor("\"${item.key}\"", JSON_KEY_COLOR) + .appendWithColor(":", JSON_SIGN_ELEMENTS_COLOR) + if (item.value.isJsonObject || item.value.isJsonArray) { + sb.append(" ") + printifyRecursive(item.value, indentBuilder, sb) + } else sb.appendJsonValue(item.value) + if (index != transformedJson.asJsonObject.size()) + sb.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") + } + sb.appendWithColor("\n${indentBuilder.dropLast(2)}}", JSON_SIGN_ELEMENTS_COLOR) } private fun SpannableStringBuilder.appendWithColor(text: CharSequence, color: Int): @@ -114,3 +128,4 @@ public class SpanTextUtil { public class ChuckerForegroundColorSpan(color: Int) : ForegroundColorSpan(color) } + From 1943db43793da9a11548571acb607dc4e9bfd339 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Sun, 25 Dec 2022 09:51:29 +0330 Subject: [PATCH 12/17] remove bug fix codes from this branch --- .../ui/transaction/TransactionPayloadAdapter.kt | 10 ++++------ .../ui/transaction/TransactionPayloadFragment.kt | 12 +++++------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt index f882e1d02..f3dcd9cc6 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt @@ -25,13 +25,11 @@ import com.chuckerteam.chucker.internal.support.highlightWithDefinedColors internal class TransactionBodyAdapter : RecyclerView.Adapter() { private val items = arrayListOf() - private var containsHeader = false - fun setItems(bodyItems: List, containsHeader: Boolean) { + fun setItems(bodyItems: List) { val previousItemCount = items.size items.clear() items.addAll(bodyItems) - this.containsHeader = containsHeader notifyItemRangeRemoved(0, previousItemCount) notifyItemRangeInserted(0, items.size) } @@ -87,12 +85,12 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter 0) { - notifyItemChanged(index + if (containsHeader) 1 else 0) + notifyItemChanged(index + 1) } } } @@ -104,7 +102,7 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter val removedSpansCount = item.line.clearHighlightSpans() if (removedSpansCount > 0) { - notifyItemChanged(index + if (containsHeader) 1 else 0) + notifyItemChanged(index + 1) } } } diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt index 478a548e2..def5f4477 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt @@ -104,10 +104,10 @@ internal class TransactionPayloadFragment : payloadBinding.loadingProgress.visibility = View.VISIBLE val result = processPayload(payloadType, transaction, formatRequestBody) - if (result.second.isEmpty()) { + if (result.isEmpty()) { showEmptyState() } else { - payloadAdapter.setItems(result.second, result.first) + payloadAdapter.setItems(result) showPayloadState() } // Invalidating menu, because we need to hide menu items for empty payloads @@ -216,7 +216,7 @@ internal class TransactionPayloadFragment : type: PayloadType, transaction: HttpTransaction, formatRequestBody: Boolean - ): Pair> { + ): MutableList { return withContext(Dispatchers.Default) { val result = mutableListOf() @@ -237,9 +237,7 @@ internal class TransactionPayloadFragment : isBodyEncoded = transaction.isResponseBodyEncoded bodyString = transaction.getSpannedResponseBody() } - var containsHeader = false if (headersString.isNotBlank()) { - containsHeader = true result.add( TransactionPayloadItem.HeaderItem( HtmlCompat.fromHtml( @@ -256,7 +254,7 @@ internal class TransactionPayloadFragment : if (type == PayloadType.RESPONSE && responseBitmap != null) { val bitmapLuminance = responseBitmap.calculateLuminance() result.add(TransactionPayloadItem.ImageItem(responseBitmap, bitmapLuminance)) - return@withContext Pair(containsHeader, result) + return@withContext result } when { @@ -274,7 +272,7 @@ internal class TransactionPayloadFragment : else SpannableStringBuilder.valueOf(it))) } } - return@withContext Pair(containsHeader, result) + return@withContext result } } From f39612ff31978aeb0bcf945d89f5edb4b987bd27 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Sun, 25 Dec 2022 10:22:55 +0330 Subject: [PATCH 13/17] fix indent issue --- .../com/chuckerteam/chucker/internal/support/SpanTextUtil.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt index e21043535..eae1a1cec 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt @@ -34,11 +34,12 @@ public class SpanTextUtil { currentIndent: StringBuilder, sb: SpannableStringBuilder ) { + val indent = StringBuilder(currentIndent) if (transformedJson.isJsonArray) { - printifyJsonArray(sb, currentIndent, transformedJson) + printifyJsonArray(sb, indent, transformedJson) } if (transformedJson.isJsonObject) { - printifyJsonObject(sb, currentIndent, transformedJson) + printifyJsonObject(sb, indent, transformedJson) } } From 3b77798664bd94f79aaa2f34e79fb2d084b3bbc1 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Tue, 7 Feb 2023 10:22:15 +0330 Subject: [PATCH 14/17] fix merge request comments --- .../com/chuckerteam/chucker/SpanUtilTest.kt | 11 +++++----- .../transaction/TransactionPayloadAdapter.kt | 20 +++++-------------- .../transaction/TransactionPayloadFragment.kt | 8 ++------ 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt b/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt index cd7e4e866..3573d6d47 100644 --- a/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt +++ b/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt @@ -12,8 +12,7 @@ import org.junit.runner.RunWith public class SpanUtilTest { @SuppressLint("CheckResult") @Test - public fun json_can_have_null_value() { - + public fun `json can have null value`() { val parsedJson = SpanTextUtil.spanJson( """{ "field": null }""" ) @@ -27,7 +26,7 @@ public class SpanUtilTest { ) } @Test - public fun json_can_have_empty_fields() { + public fun `json can have empty fields`() { val parsedJson = SpanTextUtil.spanJson( """{ "field": "" }""" ) @@ -42,7 +41,7 @@ public class SpanUtilTest { } @Test - public fun json_can_be_invalid() { + public fun `json can be invalid`() { val parsedJson = SpanTextUtil.spanJson( """[{ "field": null }""" ) @@ -53,7 +52,7 @@ public class SpanUtilTest { } @Test - public fun json_object_is_pretty_printed() { + public fun `json object is pretty printed`() { val parsedJson = SpanTextUtil.spanJson( """{ "field1": "something", "field2": "else" }""" ) @@ -69,7 +68,7 @@ public class SpanUtilTest { } @Test - public fun json_array_is_pretty_printed() { + public fun `json array is pretty printed`() { val parsedJson = SpanTextUtil.spanJson( """[{ "field1": "something1", "field2": "else1" }, { "field1": "something2", "field2": "else2" }]""" ) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt index f3dcd9cc6..f48803454 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadAdapter.kt @@ -38,25 +38,19 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter { - val headersItemBinding = - ChuckerTransactionItemHeadersBinding.inflate(inflater, parent, false) + val headersItemBinding = ChuckerTransactionItemHeadersBinding.inflate(inflater, parent, false) TransactionPayloadViewHolder.HeaderViewHolder(headersItemBinding) } TYPE_BODY_LINE -> { - val bodyItemBinding = - ChuckerTransactionItemBodyLineBinding.inflate(inflater, parent, false) + val bodyItemBinding = ChuckerTransactionItemBodyLineBinding.inflate(inflater, parent, false) TransactionPayloadViewHolder.BodyLineViewHolder(bodyItemBinding) } else -> { - val imageItemBinding = - ChuckerTransactionItemImageBinding.inflate(inflater, parent, false) + val imageItemBinding = ChuckerTransactionItemImageBinding.inflate(inflater, parent, false) TransactionPayloadViewHolder.ImageViewHolder(imageItemBinding) } } @@ -72,11 +66,7 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter() .withIndex() .forEach { (index, item) -> diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt index def5f4477..665aa67c2 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt @@ -254,7 +254,7 @@ internal class TransactionPayloadFragment : if (type == PayloadType.RESPONSE && responseBitmap != null) { val bitmapLuminance = responseBitmap.calculateLuminance() result.add(TransactionPayloadItem.ImageItem(responseBitmap, bitmapLuminance)) - return@withContext result + return@withContext result } when { @@ -276,11 +276,7 @@ internal class TransactionPayloadFragment : } } - private suspend fun saveToFile( - type: PayloadType, - uri: Uri, - transaction: HttpTransaction - ): Boolean { + private suspend fun saveToFile(type: PayloadType, uri: Uri, transaction: HttpTransaction): Boolean { return withContext(Dispatchers.IO) { try { requireContext().contentResolver.openFileDescriptor(uri, "w")?.use { From aefcb9f49f8be7210d087ba206c779d0bf65860d Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Wed, 15 Feb 2023 13:10:58 +0330 Subject: [PATCH 15/17] some improvement --- .../chucker/internal/support/SpanTextUtil.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt index eae1a1cec..c968912c1 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt @@ -25,14 +25,14 @@ public class SpanTextUtil { return SpannableStringBuilder.valueOf(input) } val sb = SpannableStringBuilder() - printifyRecursive(jsonElement, StringBuilder(""), sb) + printifyRecursive(sb, StringBuilder(""), jsonElement) return sb } private fun printifyRecursive( - transformedJson: JsonElement, + sb: SpannableStringBuilder, currentIndent: StringBuilder, - sb: SpannableStringBuilder + transformedJson: JsonElement ) { val indent = StringBuilder(currentIndent) if (transformedJson.isJsonArray) { @@ -55,12 +55,12 @@ public class SpanTextUtil { ) return } - sb.appendWithColor("$indent[\n", JSON_SIGN_ELEMENTS_COLOR) + sb.appendWithColor("${indent}[\n", JSON_SIGN_ELEMENTS_COLOR) indent.append(" ") for (index in 0 until transformedJson.asJsonArray.size()) { val item = transformedJson.asJsonArray[index] if (item.isJsonObject || item.isJsonArray) - printifyRecursive(item, indent, sb) + printifyRecursive(sb, indent, item) else { sb.append(indent) sb.appendJsonValue(item) @@ -69,7 +69,7 @@ public class SpanTextUtil { sb.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") } val finalIndent = StringBuilder(indent.dropLast(2)) - sb.appendWithColor("\n$finalIndent]", JSON_SIGN_ELEMENTS_COLOR) + sb.appendWithColor("\n${finalIndent}]", JSON_SIGN_ELEMENTS_COLOR) } private fun printifyJsonObject( @@ -94,7 +94,7 @@ public class SpanTextUtil { .appendWithColor(":", JSON_SIGN_ELEMENTS_COLOR) if (item.value.isJsonObject || item.value.isJsonArray) { sb.append(" ") - printifyRecursive(item.value, indentBuilder, sb) + printifyRecursive(sb, indentBuilder, item.value) } else sb.appendJsonValue(item.value) if (index != transformedJson.asJsonObject.size()) sb.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") From bee1044967cc0ae7abec833ffb25720781126b79 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Wed, 15 Feb 2023 14:20:57 +0330 Subject: [PATCH 16/17] change test method's name --- .../kotlin/com/chuckerteam/chucker/SpanUtilTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt b/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt index 3573d6d47..06e8d1be9 100644 --- a/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt +++ b/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt @@ -12,7 +12,7 @@ import org.junit.runner.RunWith public class SpanUtilTest { @SuppressLint("CheckResult") @Test - public fun `json can have null value`() { + public fun json_can_have_null_value() { val parsedJson = SpanTextUtil.spanJson( """{ "field": null }""" ) @@ -26,7 +26,7 @@ public class SpanUtilTest { ) } @Test - public fun `json can have empty fields`() { + public fun json_can_have_empty_fields() { val parsedJson = SpanTextUtil.spanJson( """{ "field": "" }""" ) @@ -41,7 +41,7 @@ public class SpanUtilTest { } @Test - public fun `json can be invalid`() { + public fun json_can_be_invalid() { val parsedJson = SpanTextUtil.spanJson( """[{ "field": null }""" ) @@ -52,7 +52,7 @@ public class SpanUtilTest { } @Test - public fun `json object is pretty printed`() { + public fun json_object_is_pretty_printed() { val parsedJson = SpanTextUtil.spanJson( """{ "field1": "something", "field2": "else" }""" ) @@ -68,7 +68,7 @@ public class SpanUtilTest { } @Test - public fun `json array is pretty printed`() { + public fun json_array_is_pretty_printed() { val parsedJson = SpanTextUtil.spanJson( """[{ "field1": "something1", "field2": "else1" }, { "field1": "something2", "field2": "else2" }]""" ) From edb81b163e08b38c97f786ac1383d35ac7290618 Mon Sep 17 00:00:00 2001 From: AmirHosein Hoseini Date: Tue, 28 Feb 2023 11:47:01 +0330 Subject: [PATCH 17/17] handle dark mode in json colors --- .../com/chuckerteam/chucker/SpanUtilTest.kt | 20 +- .../internal/data/entity/HttpTransaction.kt | 15 +- .../chucker/internal/support/SpanTextUtil.kt | 217 +++++++++--------- .../transaction/TransactionPayloadFragment.kt | 4 +- library/src/main/res/values-night/colors.xml | 7 +- library/src/main/res/values/colors.xml | 7 + 6 files changed, 151 insertions(+), 119 deletions(-) diff --git a/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt b/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt index 06e8d1be9..c4276b212 100644 --- a/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt +++ b/library/src/androidTest/kotlin/com/chuckerteam/chucker/SpanUtilTest.kt @@ -1,19 +1,29 @@ package com.chuckerteam.chucker import android.annotation.SuppressLint +import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import com.chuckerteam.chucker.internal.support.SpanTextUtil import com.google.common.truth.Truth import org.junit.Assert +import org.junit.Before +import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) public class SpanUtilTest { + private lateinit var context: Context + + @Before + public fun init() { + context = InstrumentationRegistry.getInstrumentation().context + } @SuppressLint("CheckResult") @Test public fun json_can_have_null_value() { - val parsedJson = SpanTextUtil.spanJson( + val parsedJson = SpanTextUtil(context).spanJson( """{ "field": null }""" ) Assert.assertEquals( @@ -27,7 +37,7 @@ public class SpanUtilTest { } @Test public fun json_can_have_empty_fields() { - val parsedJson = SpanTextUtil.spanJson( + val parsedJson = SpanTextUtil(context).spanJson( """{ "field": "" }""" ) @@ -42,7 +52,7 @@ public class SpanUtilTest { @Test public fun json_can_be_invalid() { - val parsedJson = SpanTextUtil.spanJson( + val parsedJson = SpanTextUtil(context).spanJson( """[{ "field": null }""" ) @@ -53,7 +63,7 @@ public class SpanUtilTest { @Test public fun json_object_is_pretty_printed() { - val parsedJson = SpanTextUtil.spanJson( + val parsedJson = SpanTextUtil(context).spanJson( """{ "field1": "something", "field2": "else" }""" ) @@ -69,7 +79,7 @@ public class SpanUtilTest { @Test public fun json_array_is_pretty_printed() { - val parsedJson = SpanTextUtil.spanJson( + val parsedJson = SpanTextUtil(context).spanJson( """[{ "field1": "something1", "field2": "else1" }, { "field1": "something2", "field2": "else2" }]""" ) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/data/entity/HttpTransaction.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/data/entity/HttpTransaction.kt index 698d58271..c636a20a1 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/data/entity/HttpTransaction.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/data/entity/HttpTransaction.kt @@ -2,6 +2,7 @@ package com.chuckerteam.chucker.internal.data.entity +import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.text.SpannableStringBuilder @@ -226,11 +227,13 @@ internal class HttpTransaction( * This method just works with json content-type yet, and calls [formatBody] * for other content-type until parser function will be developed for other content-types. */ - private fun spanBody(body: CharSequence, contentType: String?): CharSequence { + private fun spanBody(body: CharSequence, contentType: String?, context: Context?): CharSequence { return when { //TODO Implement Other Content Types contentType.isNullOrBlank() -> body - contentType.contains("json", ignoreCase = true) -> SpanTextUtil.spanJson(body) + contentType.contains("json", ignoreCase = true) && context != null -> { + SpanTextUtil(context).spanJson(body) + } else -> formatBody(body.toString(), contentType) } } @@ -243,8 +246,8 @@ internal class HttpTransaction( return requestBody?.let { formatBody(it, requestContentType) } ?: "" } - fun getSpannedRequestBody(): CharSequence { - return requestBody?.let { spanBody(it, requestContentType) } + fun getSpannedRequestBody(context: Context?): CharSequence { + return requestBody?.let { spanBody(it, requestContentType,context) } ?: SpannableStringBuilder.valueOf("") } @@ -252,9 +255,9 @@ internal class HttpTransaction( return responseBody?.let { formatBody(it, responseContentType) } ?: "" } - fun getSpannedResponseBody(): CharSequence { + fun getSpannedResponseBody(context: Context?): CharSequence { return responseBody?.let { - spanBody(it, responseContentType) + spanBody(it, responseContentType, context) } ?: SpannableStringBuilder.valueOf("") } diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt index c968912c1..bff79a4e0 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/SpanTextUtil.kt @@ -1,130 +1,137 @@ package com.chuckerteam.chucker.internal.support -import android.graphics.Color +import android.content.Context import android.text.SpannableStringBuilder import android.text.Spanned import android.text.style.ForegroundColorSpan +import androidx.core.content.ContextCompat import androidx.core.text.isDigitsOnly +import com.chuckerteam.chucker.R import com.google.gson.JsonElement import com.google.gson.JsonParser import com.google.gson.JsonSyntaxException -public class SpanTextUtil { - public companion object { - private val JSON_KEY_COLOR = Color.parseColor("#8B0057") - private val JSON_STRING_VALUE_COLOR = Color.parseColor("#2F00FF") - private val JSON_DIGIT_AND_NULL_VALUE_COLOR = Color.parseColor("#E84B31") - private val JSON_SIGN_ELEMENTS_COLOR = Color.parseColor("#474747") +public class SpanTextUtil(context: Context) { + private val jsonKeyColor: Int + private val jsonValueColor: Int + private val jsonDigitsAndNullValueColor: Int + private val jsonSignElementsColor: Int - public fun spanJson(input: CharSequence): SpannableStringBuilder { - val jsonElement = try { - JsonParser.parseString(input.toString()) - } catch (e: JsonSyntaxException) { - Logger.warn("Json structure is invalid so it can not be formatted", e) - return SpannableStringBuilder.valueOf(input) - } - val sb = SpannableStringBuilder() - printifyRecursive(sb, StringBuilder(""), jsonElement) - return sb - } + init { + jsonKeyColor = ContextCompat.getColor(context, R.color.chucker_json_key_color) + jsonValueColor = ContextCompat.getColor(context, R.color.chucker_json_value_color) + jsonDigitsAndNullValueColor = + ContextCompat.getColor(context, R.color.chucker_json_digit_and_null_value_color) + jsonSignElementsColor = ContextCompat.getColor(context, R.color.chucker_json_elements_color) + } - private fun printifyRecursive( - sb: SpannableStringBuilder, - currentIndent: StringBuilder, - transformedJson: JsonElement - ) { - val indent = StringBuilder(currentIndent) - if (transformedJson.isJsonArray) { - printifyJsonArray(sb, indent, transformedJson) - } - if (transformedJson.isJsonObject) { - printifyJsonObject(sb, indent, transformedJson) - } + public fun spanJson(input: CharSequence): SpannableStringBuilder { + val jsonElement = try { + JsonParser.parseString(input.toString()) + } catch (e: JsonSyntaxException) { + Logger.warn("Json structure is invalid so it can not be formatted", e) + return SpannableStringBuilder.valueOf(input) } - - private fun printifyJsonArray( - sb: SpannableStringBuilder, - indent: StringBuilder, - transformedJson: JsonElement - ) { - if (transformedJson.asJsonArray.isEmpty) { - sb.appendWithColor( - "[]", - JSON_SIGN_ELEMENTS_COLOR - ) - return - } - sb.appendWithColor("${indent}[\n", JSON_SIGN_ELEMENTS_COLOR) - indent.append(" ") - for (index in 0 until transformedJson.asJsonArray.size()) { - val item = transformedJson.asJsonArray[index] - if (item.isJsonObject || item.isJsonArray) - printifyRecursive(sb, indent, item) - else { - sb.append(indent) - sb.appendJsonValue(item) - } - if (index != transformedJson.asJsonArray.size() - 1) - sb.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") - } - val finalIndent = StringBuilder(indent.dropLast(2)) - sb.appendWithColor("\n${finalIndent}]", JSON_SIGN_ELEMENTS_COLOR) + return SpannableStringBuilder().also { + printifyRecursive(it, StringBuilder(""), jsonElement) } - - private fun printifyJsonObject( - sb: SpannableStringBuilder, - indentBuilder: StringBuilder, - transformedJson: JsonElement - ) { - if (transformedJson.asJsonObject.size() == 0) { - sb.appendWithColor( - "{}", - JSON_SIGN_ELEMENTS_COLOR - ) - return - } - sb.appendWithColor("${indentBuilder}{\n", JSON_SIGN_ELEMENTS_COLOR) - indentBuilder.append(" ") - var index = 0 - for (item in transformedJson.asJsonObject.entrySet()) { - sb.append(indentBuilder) - index++ - sb.appendWithColor("\"${item.key}\"", JSON_KEY_COLOR) - .appendWithColor(":", JSON_SIGN_ELEMENTS_COLOR) - if (item.value.isJsonObject || item.value.isJsonArray) { - sb.append(" ") - printifyRecursive(sb, indentBuilder, item.value) - } else sb.appendJsonValue(item.value) - if (index != transformedJson.asJsonObject.size()) - sb.appendWithColor(",", JSON_SIGN_ELEMENTS_COLOR).append("\n") - } - sb.appendWithColor("\n${indentBuilder.dropLast(2)}}", JSON_SIGN_ELEMENTS_COLOR) + } + private fun printifyRecursive( + sb: SpannableStringBuilder, + currentIndent: StringBuilder, + transformedJson: JsonElement + ) { + val indent = StringBuilder(currentIndent) + if (transformedJson.isJsonArray) { + printifyJsonArray(sb, indent, transformedJson) + } + if (transformedJson.isJsonObject) { + printifyJsonObject(sb, indent, transformedJson) } + } - private fun SpannableStringBuilder.appendWithColor(text: CharSequence, color: Int): - SpannableStringBuilder { - this.append( - text, ChuckerForegroundColorSpan(color), - Spanned.SPAN_INCLUSIVE_INCLUSIVE + private fun printifyJsonArray( + sb: SpannableStringBuilder, + indent: StringBuilder, + transformedJson: JsonElement + ) { + if (transformedJson.asJsonArray.isEmpty) { + sb.appendWithColor( + "[]", + jsonSignElementsColor ) - return this + return } + sb.appendWithColor("${indent}[\n", jsonSignElementsColor) + indent.append(" ") + for (index in 0 until transformedJson.asJsonArray.size()) { + val item = transformedJson.asJsonArray[index] + if (item.isJsonObject || item.isJsonArray) + printifyRecursive(sb, indent, item) + else { + sb.append(indent) + sb.appendJsonValue(item) + } + if (index != transformedJson.asJsonArray.size() - 1) + sb.appendWithColor(",", jsonSignElementsColor).append("\n") + } + val finalIndent = StringBuilder(indent.dropLast(2)) + sb.appendWithColor("\n${finalIndent}]", jsonSignElementsColor) + } - private fun SpannableStringBuilder.appendJsonValue(jsonValue: JsonElement): - SpannableStringBuilder { - val isDigit = jsonValue.isJsonNull.not() && - jsonValue.asString.isNotEmpty() && - jsonValue.isJsonPrimitive && - jsonValue.asString.isDigitsOnly() - val value = if (isDigit) jsonValue.asString else jsonValue.toString() - val color = if (isDigit || jsonValue.isJsonNull) JSON_DIGIT_AND_NULL_VALUE_COLOR - else JSON_STRING_VALUE_COLOR - return this.appendWithColor( - " $value", - color + private fun printifyJsonObject( + sb: SpannableStringBuilder, + indentBuilder: StringBuilder, + transformedJson: JsonElement + ) { + if (transformedJson.asJsonObject.size() == 0) { + sb.appendWithColor( + "{}", + jsonSignElementsColor ) + return } + sb.appendWithColor("${indentBuilder}{\n", jsonSignElementsColor) + indentBuilder.append(" ") + var index = 0 + for (item in transformedJson.asJsonObject.entrySet()) { + sb.append(indentBuilder) + index++ + sb.appendWithColor("\"${item.key}\"", jsonKeyColor) + .appendWithColor(":", jsonSignElementsColor) + if (item.value.isJsonObject || item.value.isJsonArray) { + sb.append(" ") + printifyRecursive(sb, indentBuilder, item.value) + } else sb.appendJsonValue(item.value) + if (index != transformedJson.asJsonObject.size()) + sb.appendWithColor(",", jsonSignElementsColor).append("\n") + } + sb.appendWithColor("\n${indentBuilder.dropLast(2)}}", jsonSignElementsColor) + } + + private fun SpannableStringBuilder.appendWithColor(text: CharSequence, color: Int): + SpannableStringBuilder { + this.append( + text, ChuckerForegroundColorSpan(color), + Spanned.SPAN_INCLUSIVE_INCLUSIVE + ) + return this + } + + private fun SpannableStringBuilder.appendJsonValue(jsonValue: JsonElement): + SpannableStringBuilder { + val isDigit = jsonValue.isJsonNull.not() && + jsonValue.asString.isNotEmpty() && + jsonValue.isJsonPrimitive && + jsonValue.asString.isDigitsOnly() + val value = if (isDigit) jsonValue.asString else jsonValue.toString() + val color = if (isDigit || jsonValue.isJsonNull) jsonDigitsAndNullValueColor + else jsonValueColor + return this.appendWithColor( + " $value", + color + ) } public class ChuckerForegroundColorSpan(color: Int) : ForegroundColorSpan(color) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt index 665aa67c2..b343058d7 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt @@ -228,14 +228,14 @@ internal class TransactionPayloadFragment : headersString = transaction.getRequestHeadersString(true) isBodyEncoded = transaction.isRequestBodyEncoded bodyString = if (formatRequestBody) { - transaction.getSpannedRequestBody() + transaction.getSpannedRequestBody(context) } else { transaction.requestBody ?: "" } } else { headersString = transaction.getResponseHeadersString(true) isBodyEncoded = transaction.isResponseBodyEncoded - bodyString = transaction.getSpannedResponseBody() + bodyString = transaction.getSpannedResponseBody(context) } if (headersString.isNotBlank()) { result.add( diff --git a/library/src/main/res/values-night/colors.xml b/library/src/main/res/values-night/colors.xml index e610df9ce..724e43fac 100644 --- a/library/src/main/res/values-night/colors.xml +++ b/library/src/main/res/values-night/colors.xml @@ -21,4 +21,9 @@ #ffe082 #ef5350 - \ No newline at end of file + + #75bec4 + #9e7162 + #669f50 + #777777 + diff --git a/library/src/main/res/values/colors.xml b/library/src/main/res/values/colors.xml index e3f8f0068..a22301005 100644 --- a/library/src/main/res/values/colors.xml +++ b/library/src/main/res/values/colors.xml @@ -26,4 +26,11 @@ #D2DADF #182531 #01101D + + #8B0057 + #2F00FF + #E84B31 + #474747 + +