From eb31f8edd10f9c1b9060885ac4b4f5b45430cb20 Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Sat, 18 Mar 2023 16:39:29 +0530 Subject: [PATCH 1/9] scroll on search added --- .../chucker/internal/support/ViewUtils.kt | 16 ++++ .../transaction/TransactionPayloadAdapter.kt | 6 +- .../transaction/TransactionPayloadFragment.kt | 78 ++++++++++++++++++- .../color/chucker_fab_background_colour.xml | 5 ++ .../res/drawable/chucker_ic_arrow_down.xml | 10 +++ .../chucker_fragment_transaction_payload.xml | 32 ++++++++ library/src/main/res/values/strings.xml | 1 + 7 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 library/src/main/kotlin/com/chuckerteam/chucker/internal/support/ViewUtils.kt create mode 100644 library/src/main/res/color/chucker_fab_background_colour.xml create mode 100644 library/src/main/res/drawable/chucker_ic_arrow_down.xml diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/ViewUtils.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/ViewUtils.kt new file mode 100644 index 000000000..6e468970d --- /dev/null +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/support/ViewUtils.kt @@ -0,0 +1,16 @@ +package com.chuckerteam.chucker.internal.support + +import android.view.View + + +internal fun View.visible() { + if (this.visibility != View.VISIBLE) { + this.visibility = View.VISIBLE + } +} + +internal fun View.gone() { + if (this.visibility != View.GONE) { + this.visibility = View.GONE + } +} 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..ba840f0f2 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 @@ -66,7 +66,8 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter { + val listOfFoundQueryIndices = mutableListOf() items.filterIsInstance() .withIndex() .forEach { (index, item) -> @@ -76,14 +77,17 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter 0) { notifyItemChanged(index + 1) + listOfFoundQueryIndices.remove(index + 1) } } } + return listOfFoundQueryIndices } internal fun resetHighlight() { 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 b343058d7..1a4e229b1 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 @@ -1,16 +1,20 @@ package com.chuckerteam.chucker.internal.ui.transaction import android.annotation.SuppressLint +import android.app.Activity import android.content.Context import android.graphics.Color import android.net.Uri import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.text.SpannableStringBuilder import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.SearchView @@ -26,6 +30,8 @@ import com.chuckerteam.chucker.internal.data.entity.HttpTransaction import com.chuckerteam.chucker.internal.support.Logger import com.chuckerteam.chucker.internal.support.calculateLuminance import com.chuckerteam.chucker.internal.support.combineLatest +import com.chuckerteam.chucker.internal.support.gone +import com.chuckerteam.chucker.internal.support.visible import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -70,6 +76,9 @@ internal class TransactionPayloadFragment : private var backgroundSpanColor: Int = Color.YELLOW private var foregroundSpanColor: Int = Color.RED + private val scrollableIndices by lazy { mutableListOf() } + private var currentSearchScrollIndex = -1 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -78,7 +87,7 @@ internal class TransactionPayloadFragment : override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View { payloadBinding = ChuckerFragmentTransactionPayloadBinding.inflate( inflater, @@ -117,6 +126,32 @@ internal class TransactionPayloadFragment : } } ) + + payloadBinding.fabToNext.setOnClickListener { + onSearchScrollerButtonClick(true) + } + payloadBinding.fabToPrevious.setOnClickListener { + onSearchScrollerButtonClick(false) + } + } + + private fun onSearchScrollerButtonClick(goNext: Boolean) { + // hide the keyboard if visible + val inputMethodManager = activity?.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + if (inputMethodManager.isAcceptingText) { + activity?.currentFocus?.clearFocus() + inputMethodManager.hideSoftInputFromWindow(view?.windowToken, 0) + } + + val scrollToIndex = if (goNext) currentSearchScrollIndex + 1 else currentSearchScrollIndex - 1 + val scrollTo = scrollableIndices.getOrNull(scrollToIndex) + if (scrollTo != null) { + payloadBinding.payloadRecyclerView.smoothScrollToPosition(scrollTo) + currentSearchScrollIndex = scrollToIndex + } + + payloadBinding.fabToNext.isEnabled = scrollTo != scrollableIndices.last() + payloadBinding.fabToPrevious.isEnabled = scrollTo != scrollableIndices.first() } private fun showEmptyState() { @@ -200,8 +235,9 @@ internal class TransactionPayloadFragment : override fun onQueryTextSubmit(query: String): Boolean = false override fun onQueryTextChange(newText: String): Boolean { + var listOfScrollableIndex = listOf() if (newText.isNotBlank() && newText.length > NUMBER_OF_IGNORED_SYMBOLS) { - payloadAdapter.highlightQueryWithColors( + listOfScrollableIndex = payloadAdapter.highlightQueryWithColors( newText, backgroundSpanColor, foregroundSpanColor @@ -209,6 +245,44 @@ internal class TransactionPayloadFragment : } else { payloadAdapter.resetHighlight() } + + scrollableIndices.clear() + when { + listOfScrollableIndex.isEmpty() -> { + // scroll to top + currentSearchScrollIndex = 0 + payloadBinding.payloadRecyclerView.smoothScrollToPosition(0) + + payloadBinding.fabToNext.gone() + payloadBinding.fabToPrevious.gone() + } + listOfScrollableIndex.size == 1 -> { + payloadBinding.fabToNext.gone() + payloadBinding.fabToPrevious.gone() + + scrollableIndices.addAll(listOfScrollableIndex) + } + else -> { + payloadBinding.fabToNext.visible() + payloadBinding.fabToPrevious.visible() + + payloadBinding.fabToPrevious.isEnabled = false + payloadBinding.fabToNext.isEnabled = true + + scrollableIndices.addAll(listOfScrollableIndex) + } + } + + Handler(Looper.getMainLooper()).postDelayed( + { + if (scrollableIndices.isNotEmpty()) { + currentSearchScrollIndex = 0 + payloadBinding.payloadRecyclerView.smoothScrollToPosition(scrollableIndices[currentSearchScrollIndex]) + } else scrollableIndices.clear() + }, + 600 + ) + return true } diff --git a/library/src/main/res/color/chucker_fab_background_colour.xml b/library/src/main/res/color/chucker_fab_background_colour.xml new file mode 100644 index 000000000..19152c5c3 --- /dev/null +++ b/library/src/main/res/color/chucker_fab_background_colour.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/library/src/main/res/drawable/chucker_ic_arrow_down.xml b/library/src/main/res/drawable/chucker_ic_arrow_down.xml new file mode 100644 index 000000000..f57b4eec5 --- /dev/null +++ b/library/src/main/res/drawable/chucker_ic_arrow_down.xml @@ -0,0 +1,10 @@ + + + diff --git a/library/src/main/res/layout/chucker_fragment_transaction_payload.xml b/library/src/main/res/layout/chucker_fragment_transaction_payload.xml index 35abe09a8..269e68f28 100755 --- a/library/src/main/res/layout/chucker_fragment_transaction_payload.xml +++ b/library/src/main/res/layout/chucker_fragment_transaction_payload.xml @@ -68,4 +68,36 @@ app:constraint_referenced_ids="emptyPayloadImage,emptyPayloadTextView" tools:visibility="visible" /> + + + + diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml index 961849b64..55c8ae092 100644 --- a/library/src/main/res/values/strings.xml +++ b/library/src/main/res/values/strings.xml @@ -61,4 +61,5 @@ Chucker can\'t show notifications without permission Change <Unable to discover GraphQL operation name> + Buttons to scroll the search items From 06b7c150e4f0c6ef663afcc5fc00cb2b19f5776d Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Sat, 18 Mar 2023 16:55:15 +0530 Subject: [PATCH 2/9] scroll to search item added --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d6e9f58f..aba575b39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ This file follows [Keepachangelog](https://keepachangelog.com/) format. Please add your entries according to this format. ## Unreleased +* added scroll to highlighted text search in response screen ### Added * Decoding of request and response bodies can now be customized. In order to do this a `BodyDecoder` interface needs to be implemented and installed in the `ChuckerInterceptor` via `ChuckerInterceptor.addBinaryDecoder(decoder)` method. Decoded bodies are then displayed in the Chucker UI. From 57127d11fd908d58186a9fa1ab27c00e833768da Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Thu, 23 Mar 2023 11:34:57 +0530 Subject: [PATCH 3/9] detekt fix code improvement --- .../internal/ui/transaction/TransactionPayloadFragment.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 1a4e229b1..76f2c6b79 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 @@ -277,10 +277,12 @@ internal class TransactionPayloadFragment : { if (scrollableIndices.isNotEmpty()) { currentSearchScrollIndex = 0 - payloadBinding.payloadRecyclerView.smoothScrollToPosition(scrollableIndices[currentSearchScrollIndex]) + payloadBinding.payloadRecyclerView.smoothScrollToPosition( + scrollableIndices[currentSearchScrollIndex] + ) } else scrollableIndices.clear() }, - 600 + DELAY_FOR_SEARCH_SCROLL ) return true @@ -378,6 +380,7 @@ internal class TransactionPayloadFragment : companion object { private const val ARG_TYPE = "type" private const val TRANSACTION_EXCEPTION = "Transaction not ready" + private const val DELAY_FOR_SEARCH_SCROLL: Long = 600L private const val NUMBER_OF_IGNORED_SYMBOLS = 1 From 42a303b5bf8497734165dabe97d7cfb323487fd9 Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Sat, 29 Apr 2023 17:33:02 +0530 Subject: [PATCH 4/9] fab removed, search summary added in appbar, coroutines used --- CHANGELOG.md | 4 +- .../transaction/TransactionPayloadFragment.kt | 85 +++++++++++++------ .../layout/chucker_activity_transaction.xml | 61 ++++++++++++- .../chucker_fragment_transaction_payload.xml | 32 ------- library/src/main/res/values/strings.xml | 2 + library/src/main/res/values/styles.xml | 4 + 6 files changed, 125 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aba575b39..66dc76159 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ This file follows [Keepachangelog](https://keepachangelog.com/) format. Please add your entries according to this format. ## Unreleased -* added scroll to highlighted text search in response screen ### Added * Decoding of request and response bodies can now be customized. In order to do this a `BodyDecoder` interface needs to be implemented and installed in the `ChuckerInterceptor` via `ChuckerInterceptor.addBinaryDecoder(decoder)` method. Decoded bodies are then displayed in the Chucker UI. @@ -15,6 +14,7 @@ Please add your entries according to this format. * Added ability to export transactions to a file programmatically, LOG or HAR. * GraphQL OperationName header to transaction title [#69], [#116] * Added support for Android 13 and notifications permission handling +* Added scroll to highlighted text search in response screen [#988]eye ### Fixed @@ -528,3 +528,5 @@ Initial release. [#593]: https://github.com/ChuckerTeam/chucker/issues/593 [#653]: https://github.com/ChuckerTeam/chucker/pull/653 [#737]: https://github.com/ChuckerTeam/chucker/issues/737 +[#388]: https://github.com/ChuckerTeam/chucker/issues/388 +[#988]: https://github.com/ChuckerTeam/chucker/pull/988 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 76f2c6b79..fe378ea18 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 @@ -1,3 +1,5 @@ +@file:Suppress("TooManyFunctions") + package com.chuckerteam.chucker.internal.ui.transaction import android.annotation.SuppressLint @@ -6,8 +8,6 @@ import android.content.Context import android.graphics.Color import android.net.Uri import android.os.Bundle -import android.os.Handler -import android.os.Looper import android.text.SpannableStringBuilder import android.view.LayoutInflater import android.view.Menu @@ -15,15 +15,20 @@ import android.view.MenuInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager +import android.widget.ImageButton +import android.widget.TextView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.SearchView +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.text.HtmlCompat +import androidx.core.text.bold import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.withResumed import com.chuckerteam.chucker.R import com.chuckerteam.chucker.databinding.ChuckerFragmentTransactionPayloadBinding import com.chuckerteam.chucker.internal.data.entity.HttpTransaction @@ -33,10 +38,12 @@ import com.chuckerteam.chucker.internal.support.combineLatest import com.chuckerteam.chucker.internal.support.gone import com.chuckerteam.chucker.internal.support.visible import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.FileOutputStream import java.io.IOException +import kotlin.math.abs internal class TransactionPayloadFragment : Fragment(), SearchView.OnQueryTextListener { @@ -78,6 +85,24 @@ internal class TransactionPayloadFragment : private val scrollableIndices by lazy { mutableListOf() } private var currentSearchScrollIndex = -1 + private var currentSearchQuery: String = "" + + private val searchViewSummaryRootLayout by lazy { + requireActivity().findViewById(R.id.constraintToolbar) + } + + private val searchTextViewSummary by lazy { + requireActivity().findViewById(R.id.toolbarSearchSummary) + } + + private val searchNavButton by lazy { + requireActivity().findViewById(R.id.toolbarSearchNavButton) + } + + private val searchNavButtonUp by lazy { + requireActivity().findViewById(R.id.toolbarSearchNavButtonUp) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -126,11 +151,10 @@ internal class TransactionPayloadFragment : } } ) - - payloadBinding.fabToNext.setOnClickListener { + searchNavButton.setOnClickListener { onSearchScrollerButtonClick(true) } - payloadBinding.fabToPrevious.setOnClickListener { + searchNavButtonUp.setOnClickListener { onSearchScrollerButtonClick(false) } } @@ -143,15 +167,16 @@ internal class TransactionPayloadFragment : inputMethodManager.hideSoftInputFromWindow(view?.windowToken, 0) } - val scrollToIndex = if (goNext) currentSearchScrollIndex + 1 else currentSearchScrollIndex - 1 + val scrollToIndex = + if (goNext) ((currentSearchScrollIndex + 1) % scrollableIndices.size) + else (abs(currentSearchScrollIndex - 1 + scrollableIndices.size) % scrollableIndices.size) val scrollTo = scrollableIndices.getOrNull(scrollToIndex) if (scrollTo != null) { payloadBinding.payloadRecyclerView.smoothScrollToPosition(scrollTo) currentSearchScrollIndex = scrollToIndex + updateToolbarText(currentSearchQuery, scrollableIndices.size, scrollToIndex + 1) } - payloadBinding.fabToNext.isEnabled = scrollTo != scrollableIndices.last() - payloadBinding.fabToPrevious.isEnabled = scrollTo != scrollableIndices.first() } private fun showEmptyState() { @@ -249,49 +274,57 @@ internal class TransactionPayloadFragment : scrollableIndices.clear() when { listOfScrollableIndex.isEmpty() -> { - // scroll to top currentSearchScrollIndex = 0 - payloadBinding.payloadRecyclerView.smoothScrollToPosition(0) + makeToolbarSearchSummaryVisible(false) - payloadBinding.fabToNext.gone() - payloadBinding.fabToPrevious.gone() } listOfScrollableIndex.size == 1 -> { - payloadBinding.fabToNext.gone() - payloadBinding.fabToPrevious.gone() scrollableIndices.addAll(listOfScrollableIndex) } else -> { - payloadBinding.fabToNext.visible() - payloadBinding.fabToPrevious.visible() - - payloadBinding.fabToPrevious.isEnabled = false - payloadBinding.fabToNext.isEnabled = true + makeToolbarSearchSummaryVisible(true) scrollableIndices.addAll(listOfScrollableIndex) + updateToolbarText(newText, listOfScrollableIndex.size, 1) } } - Handler(Looper.getMainLooper()).postDelayed( - { + lifecycleScope.launch { + delay(DELAY_FOR_SEARCH_SCROLL) + lifecycle.withResumed { if (scrollableIndices.isNotEmpty()) { currentSearchScrollIndex = 0 payloadBinding.payloadRecyclerView.smoothScrollToPosition( scrollableIndices[currentSearchScrollIndex] ) } else scrollableIndices.clear() - }, - DELAY_FOR_SEARCH_SCROLL - ) - + } + } return true } + private fun makeToolbarSearchSummaryVisible(visible: Boolean = true) { + with(searchViewSummaryRootLayout) { + if (visible) visible() else gone() + } + } + + private fun updateToolbarText(searchQuery: String, searchResultsCount: Int, currentIndex: Int = 1) { + currentSearchQuery = searchQuery + searchTextViewSummary.text = SpannableStringBuilder().apply { + append(getString(R.string.chucker_search_results_title)) + bold { + append(" $searchQuery ") + append("$currentIndex/$searchResultsCount") + } + } + } + private suspend fun processPayload( type: PayloadType, transaction: HttpTransaction, - formatRequestBody: Boolean + formatRequestBody: Boolean, ): MutableList { return withContext(Dispatchers.Default) { val result = mutableListOf() diff --git a/library/src/main/res/layout/chucker_activity_transaction.xml b/library/src/main/res/layout/chucker_activity_transaction.xml index 04199e278..801052b38 100644 --- a/library/src/main/res/layout/chucker_activity_transaction.xml +++ b/library/src/main/res/layout/chucker_activity_transaction.xml @@ -15,18 +15,71 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - app:popupTheme="@style/Chucker.Theme" > + app:popupTheme="@style/Chucker.Theme"> + tools:text="Title" /> + + + + + + + + + + - \ No newline at end of file + diff --git a/library/src/main/res/layout/chucker_fragment_transaction_payload.xml b/library/src/main/res/layout/chucker_fragment_transaction_payload.xml index 269e68f28..35abe09a8 100755 --- a/library/src/main/res/layout/chucker_fragment_transaction_payload.xml +++ b/library/src/main/res/layout/chucker_fragment_transaction_payload.xml @@ -68,36 +68,4 @@ app:constraint_referenced_ids="emptyPayloadImage,emptyPayloadTextView" tools:visibility="visible" /> - - - - diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml index 55c8ae092..698cca992 100644 --- a/library/src/main/res/values/strings.xml +++ b/library/src/main/res/values/strings.xml @@ -62,4 +62,6 @@ Change <Unable to discover GraphQL operation name> Buttons to scroll the search items + Search Results: + Search navigator buttons diff --git a/library/src/main/res/values/styles.xml b/library/src/main/res/values/styles.xml index fbd1011cb..b224594f7 100644 --- a/library/src/main/res/values/styles.xml +++ b/library/src/main/res/values/styles.xml @@ -37,4 +37,8 @@ 2 end + + From 29825b9c45ab78d2677b5ce6830e84e9fc2d2e5e Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Sat, 29 Apr 2023 18:10:47 +0530 Subject: [PATCH 5/9] removed unused string, green bg colour added for search navigation item --- .../transaction/TransactionPayloadAdapter.kt | 23 ++++++- .../transaction/TransactionPayloadFragment.kt | 63 ++++++++++++------- .../layout/chucker_activity_transaction.xml | 4 +- library/src/main/res/values/strings.xml | 1 - 4 files changed, 64 insertions(+), 27 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 ba840f0f2..6e4339a6d 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 @@ -90,6 +90,23 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter() .withIndex() @@ -127,7 +144,7 @@ internal sealed class TransactionPayloadViewHolder(view: View) : RecyclerView.Vi abstract fun bind(item: TransactionPayloadItem) internal class HeaderViewHolder( - private val headerBinding: ChuckerTransactionItemHeadersBinding + private val headerBinding: ChuckerTransactionItemHeadersBinding, ) : TransactionPayloadViewHolder(headerBinding.root) { override fun bind(item: TransactionPayloadItem) { if (item is TransactionPayloadItem.HeaderItem) { @@ -137,7 +154,7 @@ internal sealed class TransactionPayloadViewHolder(view: View) : RecyclerView.Vi } internal class BodyLineViewHolder( - private val bodyBinding: ChuckerTransactionItemBodyLineBinding + private val bodyBinding: ChuckerTransactionItemBodyLineBinding, ) : TransactionPayloadViewHolder(bodyBinding.root) { override fun bind(item: TransactionPayloadItem) { if (item is TransactionPayloadItem.BodyLineItem) { @@ -147,7 +164,7 @@ internal sealed class TransactionPayloadViewHolder(view: View) : RecyclerView.Vi } internal class ImageViewHolder( - private val imageBinding: ChuckerTransactionItemImageBinding + private val imageBinding: ChuckerTransactionItemImageBinding, ) : TransactionPayloadViewHolder(imageBinding.root) { override fun bind(item: TransactionPayloadItem) { 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 fe378ea18..55f167e7c 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 @@ -82,6 +82,7 @@ internal class TransactionPayloadFragment : private var backgroundSpanColor: Int = Color.YELLOW private var foregroundSpanColor: Int = Color.RED + private var backgroundSpanColorSearchItem: Int = Color.GREEN private val scrollableIndices by lazy { mutableListOf() } private var currentSearchScrollIndex = -1 @@ -170,12 +171,8 @@ internal class TransactionPayloadFragment : val scrollToIndex = if (goNext) ((currentSearchScrollIndex + 1) % scrollableIndices.size) else (abs(currentSearchScrollIndex - 1 + scrollableIndices.size) % scrollableIndices.size) - val scrollTo = scrollableIndices.getOrNull(scrollToIndex) - if (scrollTo != null) { - payloadBinding.payloadRecyclerView.smoothScrollToPosition(scrollTo) - currentSearchScrollIndex = scrollToIndex - updateToolbarText(currentSearchQuery, scrollableIndices.size, scrollToIndex + 1) - } + + scrollToSearchedItemPosition(scrollToIndex) } @@ -274,17 +271,11 @@ internal class TransactionPayloadFragment : scrollableIndices.clear() when { listOfScrollableIndex.isEmpty() -> { - currentSearchScrollIndex = 0 + currentSearchScrollIndex = -1 makeToolbarSearchSummaryVisible(false) - - } - listOfScrollableIndex.size == 1 -> { - - scrollableIndices.addAll(listOfScrollableIndex) } else -> { makeToolbarSearchSummaryVisible(true) - scrollableIndices.addAll(listOfScrollableIndex) updateToolbarText(newText, listOfScrollableIndex.size, 1) } @@ -293,12 +284,11 @@ internal class TransactionPayloadFragment : lifecycleScope.launch { delay(DELAY_FOR_SEARCH_SCROLL) lifecycle.withResumed { - if (scrollableIndices.isNotEmpty()) { - currentSearchScrollIndex = 0 - payloadBinding.payloadRecyclerView.smoothScrollToPosition( - scrollableIndices[currentSearchScrollIndex] - ) - } else scrollableIndices.clear() + if (scrollableIndices.isNotEmpty()) scrollToSearchedItemPosition(0) + else { + currentSearchScrollIndex = -1 + scrollableIndices.clear() + } } } return true @@ -321,6 +311,34 @@ internal class TransactionPayloadFragment : } } + private fun scrollToSearchedItemPosition(positionOfScrollableIndices: Int) { + // reset the last searched item highlight if done + scrollableIndices.getOrNull(currentSearchScrollIndex)?.let { + payloadAdapter.highlightItemWithColorOnPosition( + it, + currentSearchQuery, + backgroundSpanColor, + foregroundSpanColor + ) + } + + currentSearchScrollIndex = positionOfScrollableIndices + val scrollTo = scrollableIndices.getOrNull(positionOfScrollableIndices) + if (scrollTo != null) { + // highlight the next navigated item and update toolbar summary text + payloadAdapter.highlightItemWithColorOnPosition( + scrollTo, + currentSearchQuery, + backgroundSpanColorSearchItem, + foregroundSpanColor + ) + updateToolbarText(currentSearchQuery, scrollableIndices.size, positionOfScrollableIndices + 1) + + payloadBinding.payloadRecyclerView.smoothScrollToPosition(scrollTo) + currentSearchScrollIndex = positionOfScrollableIndices + } + } + private suspend fun processPayload( type: PayloadType, transaction: HttpTransaction, @@ -376,9 +394,12 @@ internal class TransactionPayloadFragment : result.add(TransactionPayloadItem.BodyLineItem(SpannableStringBuilder.valueOf(text))) } else -> bodyString.lines().forEach { - result.add(TransactionPayloadItem.BodyLineItem( + result.add( + TransactionPayloadItem.BodyLineItem( if (it is SpannableStringBuilder) it - else SpannableStringBuilder.valueOf(it))) + else SpannableStringBuilder.valueOf(it) + ) + ) } } return@withContext result diff --git a/library/src/main/res/layout/chucker_activity_transaction.xml b/library/src/main/res/layout/chucker_activity_transaction.xml index 801052b38..939710d82 100644 --- a/library/src/main/res/layout/chucker_activity_transaction.xml +++ b/library/src/main/res/layout/chucker_activity_transaction.xml @@ -55,7 +55,7 @@ android:layout_height="match_parent" android:layout_marginEnd="8dp" android:background="?attr/selectableItemBackground" - android:contentDescription="@string/chucker_search_navigation_buttons" + android:contentDescription="@string/chucker_scroll_buttons_for_search" android:padding="@dimen/chucker_half_grid" android:rotation="180" android:src="@drawable/chucker_ic_arrow_down" @@ -71,7 +71,7 @@ android:layout_height="match_parent" android:layout_marginEnd="@dimen/chucker_base_grid" android:background="?attr/selectableItemBackground" - android:contentDescription="@string/chucker_search_navigation_buttons" + android:contentDescription="@string/chucker_scroll_buttons_for_search" android:padding="@dimen/chucker_half_grid" android:src="@drawable/chucker_ic_arrow_down" app:layout_constraintBottom_toBottomOf="@id/toolbarSearchSummary" diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml index 698cca992..43b0be5f1 100644 --- a/library/src/main/res/values/strings.xml +++ b/library/src/main/res/values/strings.xml @@ -63,5 +63,4 @@ <Unable to discover GraphQL operation name> Buttons to scroll the search items Search Results: - Search navigator buttons From 20db3fa525b400f4f4fd7c874283294a47511b74 Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Sun, 30 Apr 2023 03:12:11 +0530 Subject: [PATCH 6/9] code cleanup --- .../transaction/TransactionPayloadAdapter.kt | 1 - .../transaction/TransactionPayloadFragment.kt | 24 ++++++++++--------- 2 files changed, 13 insertions(+), 12 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 beb98673c..f4aa65fa2 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 @@ -83,7 +83,6 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter 0) { notifyItemChanged(index + 1) - listOfFoundQueryIndices.remove(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 7750c3ee6..685561b13 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 @@ -167,14 +167,16 @@ internal class TransactionPayloadFragment : inputMethodManager.hideSoftInputFromWindow(view?.windowToken, 0) } - val scrollToIndex = - if (goNext) { - ((currentSearchScrollIndex + 1) % scrollableIndices.size) - } else { - (abs(currentSearchScrollIndex - 1 + scrollableIndices.size) % scrollableIndices.size) - } + if (scrollableIndices.isNotEmpty()) { + val scrollToIndex = + if (goNext) { + ((currentSearchScrollIndex + 1) % scrollableIndices.size) + } else { + (abs(currentSearchScrollIndex - 1 + scrollableIndices.size) % scrollableIndices.size) + } - scrollToSearchedItemPosition(scrollToIndex) + scrollToSearchedItemPosition(scrollToIndex) + } } private fun showEmptyState() { @@ -269,16 +271,16 @@ internal class TransactionPayloadFragment : payloadAdapter.resetHighlight() } + currentSearchQuery = newText + currentSearchScrollIndex = -1 scrollableIndices.clear() + when { listOfScrollableIndex.isEmpty() -> { - currentSearchScrollIndex = -1 makeToolbarSearchSummaryVisible(false) } else -> { - makeToolbarSearchSummaryVisible(true) scrollableIndices.addAll(listOfScrollableIndex) - updateToolbarText(newText, listOfScrollableIndex.size, 1) } } @@ -289,7 +291,6 @@ internal class TransactionPayloadFragment : scrollToSearchedItemPosition(0) } else { currentSearchScrollIndex = -1 - scrollableIndices.clear() } } } @@ -335,6 +336,7 @@ internal class TransactionPayloadFragment : foregroundSpanColor ) updateToolbarText(currentSearchQuery, scrollableIndices.size, positionOfScrollableIndices + 1) + makeToolbarSearchSummaryVisible() payloadBinding.payloadRecyclerView.smoothScrollToPosition(scrollTo) currentSearchScrollIndex = positionOfScrollableIndices From e07a2075bbec3f744846cc6ce82b0446b7ef7380 Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Sun, 30 Apr 2023 16:12:12 +0530 Subject: [PATCH 7/9] viewpager updated, highlighting implemented for substrings in single line --- .../internal/support/SearchHighlightUtil.kt | 78 ++++++++++++------- .../ui/transaction/TransactionActivity.kt | 26 +++---- .../ui/transaction/TransactionPagerAdapter.kt | 24 +++--- .../transaction/TransactionPayloadAdapter.kt | 56 +++++++++---- .../transaction/TransactionPayloadFragment.kt | 34 ++++---- .../layout/chucker_activity_transaction.xml | 2 +- .../chucker_fragment_transaction_payload.xml | 2 +- 7 files changed, 127 insertions(+), 95 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 ff8e70614..c36c9fc28 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 @@ -5,6 +5,7 @@ import android.text.Spanned import android.text.style.BackgroundColorSpan import android.text.style.ForegroundColorSpan import android.text.style.UnderlineSpan +import java.util.regex.Pattern /** * Highlight parts of the String when it matches the search. @@ -13,21 +14,55 @@ import android.text.style.UnderlineSpan */ internal fun SpannableStringBuilder.highlightWithDefinedColors( search: String, + startIndices: List, backgroundColor: Int, foregroundColor: Int ): SpannableStringBuilder { - val startIndexes = indexesOf(this.toString(), search) - return applyColoredSpannable(this, startIndexes, search.length, backgroundColor, foregroundColor) + return applyColoredSpannable(this, startIndices, search.length, backgroundColor, foregroundColor) } -private fun indexesOf(text: String, search: String): List { - val startPositions = mutableListOf() - var index = text.indexOf(search, 0, true) - while (index >= 0) { - startPositions.add(index) - index = text.indexOf(search, index + 1, true) +internal fun CharSequence.indicesOf(input: String): List = + Pattern.compile(input, Pattern.CASE_INSENSITIVE).toRegex() + .findAll(this) + .map { it.range.first } + .toCollection(mutableListOf()) + +internal fun SpannableStringBuilder.highlightWithDefinedColorsSubstring( + search: String, + startIndex: Int, + backgroundColor: Int, + foregroundColor: Int +): SpannableStringBuilder { + return applyColoredSpannableSubstring(this, startIndex, search.length, backgroundColor, foregroundColor) +} + +private fun applyColoredSpannableSubstring( + text: SpannableStringBuilder, + subStringStartPosition: Int, + subStringLength: Int, + backgroundColor: Int, + foregroundColor: Int +): SpannableStringBuilder { + return text.apply { + setSpan( + UnderlineSpan(), + subStringStartPosition, + subStringStartPosition + subStringLength, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + setSpan( + ForegroundColorSpan(foregroundColor), + subStringStartPosition, + subStringStartPosition + subStringLength, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + setSpan( + BackgroundColorSpan(backgroundColor), + subStringStartPosition, + subStringStartPosition + subStringLength, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) } - return startPositions } private fun applyColoredSpannable( @@ -37,26 +72,9 @@ private fun applyColoredSpannable( backgroundColor: Int, foregroundColor: Int ): SpannableStringBuilder { - return indexes - .fold(text) { builder, position -> - builder.setSpan( - UnderlineSpan(), - position, - position + length, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - builder.setSpan( - ForegroundColorSpan(foregroundColor), - position, - position + length, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - builder.setSpan( - BackgroundColorSpan(backgroundColor), - position, - position + length, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - builder + return text.apply { + indexes.forEach { + applyColoredSpannableSubstring(text, it, length, backgroundColor, foregroundColor) } + } } diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionActivity.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionActivity.kt index babe7203e..c1b7aab42 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionActivity.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionActivity.kt @@ -9,7 +9,6 @@ import android.widget.Toast import androidx.activity.viewModels import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope -import androidx.viewpager.widget.ViewPager import com.chuckerteam.chucker.R import com.chuckerteam.chucker.databinding.ChuckerActivityTransactionBinding import com.chuckerteam.chucker.internal.data.entity.HttpTransaction @@ -21,6 +20,7 @@ import com.chuckerteam.chucker.internal.support.TransactionDetailsSharable import com.chuckerteam.chucker.internal.support.shareAsFile import com.chuckerteam.chucker.internal.support.shareAsUtf8Text import com.chuckerteam.chucker.internal.ui.BaseChuckerActivity +import com.google.android.material.tabs.TabLayoutMediator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -32,6 +32,7 @@ internal class TransactionActivity : BaseChuckerActivity() { } private lateinit var transactionBinding: ChuckerActivityTransactionBinding + private val pagerAdapter by lazy { TransactionPagerAdapter(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -40,8 +41,10 @@ internal class TransactionActivity : BaseChuckerActivity() { with(transactionBinding) { setContentView(root) setSupportActionBar(toolbar) - setupViewPager(viewPager) - tabLayout.setupWithViewPager(viewPager) + viewPager.adapter = pagerAdapter + TabLayoutMediator(tabLayout, viewPager) { tab, position -> + tab.text = pagerAdapter.titles[position] + }.attach() } supportActionBar?.setDisplayHomeAsUpEnabled(true) @@ -82,13 +85,16 @@ internal class TransactionActivity : BaseChuckerActivity() { val encodeUrls = viewModel.encodeUrl.value!! TransactionDetailsSharable(transaction, encodeUrls) } + R.id.share_curl -> shareTransactionAsText { transaction -> TransactionCurlCommandSharable(transaction) } + R.id.share_file -> shareTransactionAsFile(EXPORT_TXT_FILE_NAME) { transaction -> val encodeUrls = viewModel.encodeUrl.value!! TransactionDetailsSharable(transaction, encodeUrls) } + R.id.share_har -> shareTransactionAsFile(EXPORT_HAR_FILE_NAME) { transaction -> TransactionDetailsHarSharable( HarUtils.harStringFromTransactions( @@ -98,6 +104,7 @@ internal class TransactionActivity : BaseChuckerActivity() { ) ) } + else -> super.onOptionsItemSelected(item) } @@ -146,23 +153,10 @@ internal class TransactionActivity : BaseChuckerActivity() { return true } - private fun setupViewPager(viewPager: ViewPager) { - viewPager.adapter = TransactionPagerAdapter(this, supportFragmentManager) - viewPager.addOnPageChangeListener( - object : ViewPager.SimpleOnPageChangeListener() { - override fun onPageSelected(position: Int) { - selectedTabPosition = position - } - } - ) - viewPager.currentItem = selectedTabPosition - } - companion object { private const val EXPORT_TXT_FILE_NAME = "transaction.txt" private const val EXPORT_HAR_FILE_NAME = "transaction.har" private const val EXTRA_TRANSACTION_ID = "transaction_id" - private var selectedTabPosition = 0 fun start(context: Context, transactionId: Long) { val intent = Intent(context, TransactionActivity::class.java) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPagerAdapter.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPagerAdapter.kt index c5224cc92..c94cb5af2 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPagerAdapter.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPagerAdapter.kt @@ -1,28 +1,24 @@ package com.chuckerteam.chucker.internal.ui.transaction -import android.content.Context import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentStatePagerAdapter +import androidx.viewpager2.adapter.FragmentStateAdapter import com.chuckerteam.chucker.R -internal class TransactionPagerAdapter(context: Context, fm: FragmentManager) : - FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { +internal class TransactionPagerAdapter(activity: TransactionActivity) : + FragmentStateAdapter(activity) { - private val titles = arrayOf( - context.getString(R.string.chucker_overview), - context.getString(R.string.chucker_request), - context.getString(R.string.chucker_response) + internal val titles = arrayOf( + activity.getString(R.string.chucker_overview), + activity.getString(R.string.chucker_request), + activity.getString(R.string.chucker_response) ) - override fun getItem(position: Int): Fragment = when (position) { + override fun getItemCount(): Int = titles.size + + override fun createFragment(position: Int): Fragment = when (position) { 0 -> TransactionOverviewFragment() 1 -> TransactionPayloadFragment.newInstance(PayloadType.REQUEST) 2 -> TransactionPayloadFragment.newInstance(PayloadType.RESPONSE) else -> throw IllegalArgumentException("no item") } - - override fun getCount(): Int = titles.size - - override fun getPageTitle(position: Int): CharSequence? = titles[position] } 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 f4aa65fa2..f8809e3b3 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 @@ -16,6 +16,8 @@ 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 +import com.chuckerteam.chucker.internal.support.highlightWithDefinedColorsSubstring +import com.chuckerteam.chucker.internal.support.indicesOf /** * Adapter responsible of showing the content of the Transaction Request/Response body. @@ -45,10 +47,12 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter { val bodyItemBinding = ChuckerTransactionItemBodyLineBinding.inflate(inflater, parent, false) TransactionPayloadViewHolder.BodyLineViewHolder(bodyItemBinding) } + else -> { val imageItemBinding = ChuckerTransactionItemImageBinding.inflate(inflater, parent, false) TransactionPayloadViewHolder.ImageViewHolder(imageItemBinding) @@ -66,18 +70,36 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter { - val listOfFoundQueryIndices = mutableListOf() + internal fun highlightQueryWithColors( + newText: String, + backgroundColor: Int, + foregroundColor: Int + ): List { + val listOfSearchItems = arrayListOf() items.filterIsInstance() .withIndex() .forEach { (index, item) -> - if (item.line.contains(newText, ignoreCase = true)) { + val listOfOccurrences = item.line.indicesOf(newText) + if (listOfOccurrences.isNotEmpty()) { + // storing the occurrences and their positions + listOfOccurrences.forEach { + listOfSearchItems.add( + SearchItemBodyLine( + indexBodyLine = index + 1, + queryStartPosition = it + ) + ) + } + + // highlighting the occurrences item.line.clearHighlightSpans() - item.line = - item.line - .highlightWithDefinedColors(newText, backgroundColor, foregroundColor) + item.line = item.line.highlightWithDefinedColors( + newText, + listOfOccurrences, + backgroundColor, + foregroundColor + ) notifyItemChanged(index + 1) - listOfFoundQueryIndices.add(index + 1) } else { // Let's clear the spans if we haven't found the query string. val removedSpansCount = item.line.clearHighlightSpans() @@ -86,22 +108,25 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter() } + private val scrollableIndices by lazy { arrayListOf() } private var currentSearchScrollIndex = -1 private var currentSearchQuery: String = "" @@ -242,6 +242,7 @@ internal class TransactionPayloadFragment : PayloadType.REQUEST -> { (false == transaction?.isRequestBodyEncoded) && (0L != (transaction.requestPayloadSize)) } + PayloadType.RESPONSE -> { (false == transaction?.isResponseBodyEncoded) && (0L != (transaction.responsePayloadSize)) } @@ -260,29 +261,18 @@ internal class TransactionPayloadFragment : override fun onQueryTextSubmit(query: String): Boolean = false override fun onQueryTextChange(newText: String): Boolean { - var listOfScrollableIndex = listOf() + scrollableIndices.clear() if (newText.isNotBlank() && newText.length > NUMBER_OF_IGNORED_SYMBOLS) { - listOfScrollableIndex = payloadAdapter.highlightQueryWithColors( - newText, - backgroundSpanColor, - foregroundSpanColor + scrollableIndices.addAll( + payloadAdapter.highlightQueryWithColors(newText, backgroundSpanColor, foregroundSpanColor) ) } else { payloadAdapter.resetHighlight() + makeToolbarSearchSummaryVisible(false) } currentSearchQuery = newText currentSearchScrollIndex = -1 - scrollableIndices.clear() - - when { - listOfScrollableIndex.isEmpty() -> { - makeToolbarSearchSummaryVisible(false) - } - else -> { - scrollableIndices.addAll(listOfScrollableIndex) - } - } lifecycleScope.launch { delay(DELAY_FOR_SEARCH_SCROLL) @@ -304,7 +294,6 @@ internal class TransactionPayloadFragment : } private fun updateToolbarText(searchQuery: String, searchResultsCount: Int, currentIndex: Int = 1) { - currentSearchQuery = searchQuery searchTextViewSummary.text = SpannableStringBuilder().apply { append(getString(R.string.chucker_search_results_title)) bold { @@ -318,7 +307,8 @@ internal class TransactionPayloadFragment : // reset the last searched item highlight if done scrollableIndices.getOrNull(currentSearchScrollIndex)?.let { payloadAdapter.highlightItemWithColorOnPosition( - it, + it.indexBodyLine, + it.queryStartPosition, currentSearchQuery, backgroundSpanColor, foregroundSpanColor @@ -330,7 +320,8 @@ internal class TransactionPayloadFragment : if (scrollTo != null) { // highlight the next navigated item and update toolbar summary text payloadAdapter.highlightItemWithColorOnPosition( - scrollTo, + scrollTo.indexBodyLine, + scrollTo.queryStartPosition, currentSearchQuery, backgroundSpanColorSearchItem, foregroundSpanColor @@ -338,7 +329,7 @@ internal class TransactionPayloadFragment : updateToolbarText(currentSearchQuery, scrollableIndices.size, positionOfScrollableIndices + 1) makeToolbarSearchSummaryVisible() - payloadBinding.payloadRecyclerView.smoothScrollToPosition(scrollTo) + payloadBinding.payloadRecyclerView.smoothScrollToPosition(scrollTo.indexBodyLine) currentSearchScrollIndex = positionOfScrollableIndices } } @@ -393,10 +384,12 @@ internal class TransactionPayloadFragment : val text = requireContext().getString(R.string.chucker_body_omitted) 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))) } + else -> bodyString.lines().forEach { result.add( TransactionPayloadItem.BodyLineItem( @@ -423,6 +416,7 @@ internal class TransactionPayloadFragment : transaction.requestBody?.byteInputStream()?.copyTo(fos) ?: throw IOException(TRANSACTION_EXCEPTION) } + PayloadType.RESPONSE -> { transaction.responseBody?.byteInputStream()?.copyTo(fos) ?: throw IOException(TRANSACTION_EXCEPTION) diff --git a/library/src/main/res/layout/chucker_activity_transaction.xml b/library/src/main/res/layout/chucker_activity_transaction.xml index 939710d82..5e741d751 100644 --- a/library/src/main/res/layout/chucker_activity_transaction.xml +++ b/library/src/main/res/layout/chucker_activity_transaction.xml @@ -88,7 +88,7 @@ - Date: Sun, 30 Apr 2023 17:08:36 +0530 Subject: [PATCH 8/9] better param names --- .../internal/ui/transaction/TransactionActivity.kt | 4 ---- .../ui/transaction/TransactionPayloadAdapter.kt | 4 ++-- .../ui/transaction/TransactionPayloadFragment.kt | 11 +++++------ 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionActivity.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionActivity.kt index c1b7aab42..f79151e81 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionActivity.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionActivity.kt @@ -85,16 +85,13 @@ internal class TransactionActivity : BaseChuckerActivity() { val encodeUrls = viewModel.encodeUrl.value!! TransactionDetailsSharable(transaction, encodeUrls) } - R.id.share_curl -> shareTransactionAsText { transaction -> TransactionCurlCommandSharable(transaction) } - R.id.share_file -> shareTransactionAsFile(EXPORT_TXT_FILE_NAME) { transaction -> val encodeUrls = viewModel.encodeUrl.value!! TransactionDetailsSharable(transaction, encodeUrls) } - R.id.share_har -> shareTransactionAsFile(EXPORT_HAR_FILE_NAME) { transaction -> TransactionDetailsHarSharable( HarUtils.harStringFromTransactions( @@ -104,7 +101,6 @@ internal class TransactionActivity : BaseChuckerActivity() { ) ) } - else -> super.onOptionsItemSelected(item) } 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 f8809e3b3..e4483fd38 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 @@ -86,7 +86,7 @@ internal class TransactionBodyAdapter : RecyclerView.Adapter { (false == transaction?.isRequestBodyEncoded) && (0L != (transaction.requestPayloadSize)) } - PayloadType.RESPONSE -> { (false == transaction?.isResponseBodyEncoded) && (0L != (transaction.responsePayloadSize)) } @@ -262,6 +261,9 @@ internal class TransactionPayloadFragment : override fun onQueryTextChange(newText: String): Boolean { scrollableIndices.clear() + currentSearchQuery = newText + currentSearchScrollIndex = -1 + if (newText.isNotBlank() && newText.length > NUMBER_OF_IGNORED_SYMBOLS) { scrollableIndices.addAll( payloadAdapter.highlightQueryWithColors(newText, backgroundSpanColor, foregroundSpanColor) @@ -271,9 +273,6 @@ internal class TransactionPayloadFragment : makeToolbarSearchSummaryVisible(false) } - currentSearchQuery = newText - currentSearchScrollIndex = -1 - lifecycleScope.launch { delay(DELAY_FOR_SEARCH_SCROLL) lifecycle.withResumed { @@ -308,7 +307,7 @@ internal class TransactionPayloadFragment : scrollableIndices.getOrNull(currentSearchScrollIndex)?.let { payloadAdapter.highlightItemWithColorOnPosition( it.indexBodyLine, - it.queryStartPosition, + it.indexStartOfQuerySubString, currentSearchQuery, backgroundSpanColor, foregroundSpanColor @@ -321,7 +320,7 @@ internal class TransactionPayloadFragment : // highlight the next navigated item and update toolbar summary text payloadAdapter.highlightItemWithColorOnPosition( scrollTo.indexBodyLine, - scrollTo.queryStartPosition, + scrollTo.indexStartOfQuerySubString, currentSearchQuery, backgroundSpanColorSearchItem, foregroundSpanColor From dbff4bcc2911f66d852ffb94f4575a3f3c772713 Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Wed, 3 May 2023 17:37:35 +0530 Subject: [PATCH 9/9] reverted to viewpager, search summary moved to fragment --- .../ui/transaction/TransactionActivity.kt | 22 +++++-- .../ui/transaction/TransactionPagerAdapter.kt | 24 ++++---- .../transaction/TransactionPayloadFragment.kt | 37 +++--------- .../layout/chucker_activity_transaction.xml | 55 +---------------- .../chucker_fragment_transaction_payload.xml | 60 ++++++++++++++++++- 5 files changed, 98 insertions(+), 100 deletions(-) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionActivity.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionActivity.kt index f79151e81..babe7203e 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionActivity.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionActivity.kt @@ -9,6 +9,7 @@ import android.widget.Toast import androidx.activity.viewModels import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope +import androidx.viewpager.widget.ViewPager import com.chuckerteam.chucker.R import com.chuckerteam.chucker.databinding.ChuckerActivityTransactionBinding import com.chuckerteam.chucker.internal.data.entity.HttpTransaction @@ -20,7 +21,6 @@ import com.chuckerteam.chucker.internal.support.TransactionDetailsSharable import com.chuckerteam.chucker.internal.support.shareAsFile import com.chuckerteam.chucker.internal.support.shareAsUtf8Text import com.chuckerteam.chucker.internal.ui.BaseChuckerActivity -import com.google.android.material.tabs.TabLayoutMediator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -32,7 +32,6 @@ internal class TransactionActivity : BaseChuckerActivity() { } private lateinit var transactionBinding: ChuckerActivityTransactionBinding - private val pagerAdapter by lazy { TransactionPagerAdapter(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -41,10 +40,8 @@ internal class TransactionActivity : BaseChuckerActivity() { with(transactionBinding) { setContentView(root) setSupportActionBar(toolbar) - viewPager.adapter = pagerAdapter - TabLayoutMediator(tabLayout, viewPager) { tab, position -> - tab.text = pagerAdapter.titles[position] - }.attach() + setupViewPager(viewPager) + tabLayout.setupWithViewPager(viewPager) } supportActionBar?.setDisplayHomeAsUpEnabled(true) @@ -149,10 +146,23 @@ internal class TransactionActivity : BaseChuckerActivity() { return true } + private fun setupViewPager(viewPager: ViewPager) { + viewPager.adapter = TransactionPagerAdapter(this, supportFragmentManager) + viewPager.addOnPageChangeListener( + object : ViewPager.SimpleOnPageChangeListener() { + override fun onPageSelected(position: Int) { + selectedTabPosition = position + } + } + ) + viewPager.currentItem = selectedTabPosition + } + companion object { private const val EXPORT_TXT_FILE_NAME = "transaction.txt" private const val EXPORT_HAR_FILE_NAME = "transaction.har" private const val EXTRA_TRANSACTION_ID = "transaction_id" + private var selectedTabPosition = 0 fun start(context: Context, transactionId: Long) { val intent = Intent(context, TransactionActivity::class.java) diff --git a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPagerAdapter.kt b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPagerAdapter.kt index c94cb5af2..7c88ce5d3 100644 --- a/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPagerAdapter.kt +++ b/library/src/main/kotlin/com/chuckerteam/chucker/internal/ui/transaction/TransactionPagerAdapter.kt @@ -1,24 +1,28 @@ package com.chuckerteam.chucker.internal.ui.transaction +import android.content.Context import androidx.fragment.app.Fragment -import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentStatePagerAdapter import com.chuckerteam.chucker.R -internal class TransactionPagerAdapter(activity: TransactionActivity) : - FragmentStateAdapter(activity) { +internal class TransactionPagerAdapter(context: Context, fm: FragmentManager) : + FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { - internal val titles = arrayOf( - activity.getString(R.string.chucker_overview), - activity.getString(R.string.chucker_request), - activity.getString(R.string.chucker_response) + private val titles = arrayOf( + context.getString(R.string.chucker_overview), + context.getString(R.string.chucker_request), + context.getString(R.string.chucker_response) ) - override fun getItemCount(): Int = titles.size - - override fun createFragment(position: Int): Fragment = when (position) { + override fun getItem(position: Int): Fragment = when (position) { 0 -> TransactionOverviewFragment() 1 -> TransactionPayloadFragment.newInstance(PayloadType.REQUEST) 2 -> TransactionPayloadFragment.newInstance(PayloadType.RESPONSE) else -> throw IllegalArgumentException("no item") } + + override fun getCount(): Int = titles.size + + override fun getPageTitle(position: Int): CharSequence = titles[position] } 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 65738ee51..c4faa46a3 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 @@ -15,12 +15,9 @@ import android.view.MenuInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager -import android.widget.ImageButton -import android.widget.TextView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.SearchView -import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.text.HtmlCompat import androidx.core.text.bold @@ -84,26 +81,10 @@ internal class TransactionPayloadFragment : private var foregroundSpanColor: Int = Color.RED private var backgroundSpanColorSearchItem: Int = Color.GREEN - private val scrollableIndices by lazy { arrayListOf() } + private val scrollableIndices = arrayListOf() private var currentSearchScrollIndex = -1 private var currentSearchQuery: String = "" - private val searchViewSummaryRootLayout by lazy { - requireActivity().findViewById(R.id.constraintToolbar) - } - - private val searchTextViewSummary by lazy { - requireActivity().findViewById(R.id.toolbarSearchSummary) - } - - private val searchNavButton by lazy { - requireActivity().findViewById(R.id.toolbarSearchNavButton) - } - - private val searchNavButtonUp by lazy { - requireActivity().findViewById(R.id.toolbarSearchNavButtonUp) - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -151,10 +132,10 @@ internal class TransactionPayloadFragment : } } ) - searchNavButton.setOnClickListener { + payloadBinding.searchNavButton.setOnClickListener { onSearchScrollerButtonClick(true) } - searchNavButtonUp.setOnClickListener { + payloadBinding.searchNavButtonUp.setOnClickListener { onSearchScrollerButtonClick(false) } } @@ -287,17 +268,15 @@ internal class TransactionPayloadFragment : } private fun makeToolbarSearchSummaryVisible(visible: Boolean = true) { - with(searchViewSummaryRootLayout) { + with(payloadBinding.rootSearchSummary) { if (visible) visible() else gone() } } - private fun updateToolbarText(searchQuery: String, searchResultsCount: Int, currentIndex: Int = 1) { - searchTextViewSummary.text = SpannableStringBuilder().apply { - append(getString(R.string.chucker_search_results_title)) + private fun updateToolbarText(searchResultsCount: Int, currentIndex: Int = 1) { + payloadBinding.searchSummary.text = SpannableStringBuilder().apply { bold { - append(" $searchQuery ") - append("$currentIndex/$searchResultsCount") + append("$currentIndex / $searchResultsCount") } } } @@ -325,7 +304,7 @@ internal class TransactionPayloadFragment : backgroundSpanColorSearchItem, foregroundSpanColor ) - updateToolbarText(currentSearchQuery, scrollableIndices.size, positionOfScrollableIndices + 1) + updateToolbarText(scrollableIndices.size, positionOfScrollableIndices + 1) makeToolbarSearchSummaryVisible() payloadBinding.payloadRecyclerView.smoothScrollToPosition(scrollTo.indexBodyLine) diff --git a/library/src/main/res/layout/chucker_activity_transaction.xml b/library/src/main/res/layout/chucker_activity_transaction.xml index 5e741d751..6c203fa0c 100644 --- a/library/src/main/res/layout/chucker_activity_transaction.xml +++ b/library/src/main/res/layout/chucker_activity_transaction.xml @@ -27,59 +27,6 @@ - - - - - - - - - - - + + + + + + + + + +