diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b419a3f..d8c62e9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,6 +37,7 @@ paging-common = "3.1.1" paging-runtime = "3.1.1" preference = "1.2.0" recyclerview = "1.3.0" +robolectric = "4.16" room = "2.7.2" room-common = "2.7.2" rx-android = "3.0.2" @@ -78,6 +79,7 @@ androidx-paging-runtime-ktx = { group = "androidx.paging", name = "paging-runtim androidx-preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preference" } androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" } androidx-room-common = { module = "androidx.room:room-common", version.ref = "room-common" } +robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } diff --git a/libraries/analytics-logger/build.gradle.kts b/libraries/analytics-logger/build.gradle.kts index 31889d5..034d58b 100644 --- a/libraries/analytics-logger/build.gradle.kts +++ b/libraries/analytics-logger/build.gradle.kts @@ -39,7 +39,7 @@ android { } group = "com.trendyol.android.devtools" -version = "0.8.0" +version = "0.9.0" publishConfig { defaultConfiguration( @@ -59,4 +59,7 @@ dependencies { ksp(libs.androidx.room.compiler) implementation(libs.embeddedKoinCore) implementation(libs.embeddedKoinAndroid) + + testImplementation(libs.junit) + testImplementation(libs.robolectric) } diff --git a/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ext/TextExtensions.kt b/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ext/TextExtensions.kt new file mode 100644 index 0000000..f27a8e1 --- /dev/null +++ b/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ext/TextExtensions.kt @@ -0,0 +1,107 @@ +package com.trendyol.android.devtools.analyticslogger.internal.ext + +import android.graphics.Typeface +import android.text.SpannableString +import android.text.style.BackgroundColorSpan +import android.text.style.ForegroundColorSpan +import android.text.style.StyleSpan + +/** + * Highlights all occurrences of the query string in the text with background and foreground colors + * Safe implementation that handles edge cases and prevents crashes + */ +internal fun String.highlightQuery(query: String?, highlightBackgroundColor: Int, defaultTextColor: Int): SpannableString { + val spannable = SpannableString(this) + + if (query.isNullOrEmpty() || query.length < 2) { + return spannable + } + + try { + var startIndex = 0 + while (startIndex < this.length) { + val index = this.indexOf(query, startIndex, ignoreCase = true) + if (index == -1) break + + // Safety check: ensure end index doesn't exceed text length + val endIndex = (index + query.length).coerceAtMost(this.length) + + // Only apply span if we have a valid range + if (index >= 0 && endIndex <= this.length && index < endIndex) { + spannable.setSpan( + BackgroundColorSpan(highlightBackgroundColor), + index, + endIndex, + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE + ) + spannable.setSpan( + ForegroundColorSpan(defaultTextColor), + index, + endIndex, + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE + ) + spannable.setSpan( + StyleSpan(Typeface.BOLD), + index, + endIndex, + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + + startIndex = index + query.length + } + } catch (e: Exception) { + // If anything goes wrong, just return the non-highlighted text + android.util.Log.w("TextExtensions", "Error highlighting query: ${e.message}") + return spannable + } + + return spannable +} + +/** + * Finds the first line in the text that contains the query and returns a preview with context + */ +internal fun String?.findMatchingLinePreview(query: String?): String? { + if (this.isNullOrEmpty() || query.isNullOrEmpty() || query.length < 2) { + return null + } + + // Split by common JSON separators to get individual lines/fields + val lines = this.split("\n", ",", "{", "}", "[", "]") + .map { it.trim() } + .filter { it.isNotEmpty() } + + // Find the first line that contains the query + val matchingLine = lines.firstOrNull { + it.contains(query, ignoreCase = true) + } ?: return null + + // Create a preview with ellipsis if needed + val maxPreviewLength = 100 + return if (matchingLine.length > maxPreviewLength) { + val queryIndex = matchingLine.indexOf(query, ignoreCase = true) + + // Try to center the query in the preview + val start = (queryIndex - 30).coerceAtLeast(0) + val end = (start + maxPreviewLength).coerceAtMost(matchingLine.length) + + val preview = matchingLine.substring(start, end) + val prefix = if (start > 0) "..." else "" + val suffix = if (end < matchingLine.length) "..." else "" + + "$prefix$preview$suffix" + } else { + matchingLine + } +} + +/** + * Checks if the text contains the query string (case insensitive) + */ +internal fun String?.containsQuery(query: String?): Boolean { + if (this.isNullOrEmpty() || query.isNullOrEmpty()) { + return false + } + return this.contains(query, ignoreCase = true) +} diff --git a/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ext/ViewExtensions.kt b/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ext/ViewExtensions.kt new file mode 100644 index 0000000..b2ec1d8 --- /dev/null +++ b/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ext/ViewExtensions.kt @@ -0,0 +1,111 @@ +package com.trendyol.android.devtools.analyticslogger.internal.ext + +import android.annotation.SuppressLint +import android.content.Context +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import androidx.appcompat.widget.SearchView + +/** + * Hides the soft keyboard from the current view + */ +internal fun View.hideKeyboard() { + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.hideSoftInputFromWindow(windowToken, 0) +} + +/** + * Sets up RecyclerView to hide keyboard when scrolling or touching + */ +@SuppressLint("ClickableViewAccessibility") +internal fun androidx.recyclerview.widget.RecyclerView.setupHideKeyboardOnScroll() { + addOnScrollListener(object : androidx.recyclerview.widget.RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: androidx.recyclerview.widget.RecyclerView, newState: Int) { + if (newState == androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_DRAGGING) { + hideKeyboard() + } + } + }) + + // Also hide on touch to handle taps on items + setOnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + v.hideKeyboard() + } + false // Don't consume the event + } +} + +/** + * Sets up NestedScrollView to hide keyboard when scrolling or touching + */ +@SuppressLint("ClickableViewAccessibility") +internal fun androidx.core.widget.NestedScrollView.setupHideKeyboardOnScroll() { + setOnScrollChangeListener { _: androidx.core.widget.NestedScrollView, _: Int, _: Int, _: Int, _: Int -> + hideKeyboard() + } + + // Also hide on touch + setOnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + v.hideKeyboard() + } + false // Don't consume the event + } +} + +/** + * Sets up touch listener to hide keyboard when clicking outside of SearchView + * + * Note: SuppressLint is used because we're not handling clicks - we're only hiding the keyboard + * and letting the event propagate normally (returning false). This is a UX enhancement, not + * an accessibility feature, so performClick() is not needed here. + */ +@SuppressLint("ClickableViewAccessibility") +internal fun View.setupHideKeyboardOnTouch() { + // Skip if not a container + if (this !is ViewGroup) return + + setOnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + // Check if the touch is outside any SearchView + if (!isTouchInsideSearchView(this, event)) { + v.hideKeyboard() + v.clearFocus() + } + } + false // Don't consume the event, let it propagate + } +} + +/** + * Recursively checks if the touch event is inside a SearchView or its children + */ +private fun isTouchInsideSearchView(viewGroup: ViewGroup, event: MotionEvent): Boolean { + for (i in 0 until viewGroup.childCount) { + val child = viewGroup.getChildAt(i) + + if (child is SearchView) { + // Check if touch is within SearchView bounds + val location = IntArray(2) + child.getLocationOnScreen(location) + val x = location[0] + val y = location[1] + + if (event.rawX >= x && event.rawX <= x + child.width && + event.rawY >= y && event.rawY <= y + child.height + ) { + return true + } + } + + if (child is ViewGroup) { + if (isTouchInsideSearchView(child, event)) { + return true + } + } + } + return false +} diff --git a/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/EventAdapter.kt b/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/EventAdapter.kt index e041512..e363804 100644 --- a/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/EventAdapter.kt +++ b/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/EventAdapter.kt @@ -7,6 +7,7 @@ import android.graphics.drawable.GradientDrawable import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView @@ -14,6 +15,7 @@ import com.trendyol.android.devtools.analyticslogger.R import com.trendyol.android.devtools.analyticslogger.databinding.AnalyticsLoggerItemEventBinding import com.trendyol.android.devtools.analyticslogger.internal.domain.model.Event import com.trendyol.android.devtools.analyticslogger.internal.factory.ColorFactory +import com.trendyol.android.devtools.analyticslogger.internal.ui.model.EventItemViewState internal class EventAdapter : PagingDataAdapter( diffCallback = object : DiffUtil.ItemCallback() { @@ -28,6 +30,7 @@ internal class EventAdapter : PagingDataAdapter Unit)? = null + var searchQuery: String? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EventViewHolder { return EventViewHolder( @@ -58,12 +61,20 @@ internal class EventAdapter : PagingDataAdapter("") + val queryState: StateFlow = _queryState - private val queryState = MutableStateFlow("") private val platformState = MutableStateFlow("") private val _detailState = MutableStateFlow(DetailState.Initial) @@ -34,20 +30,35 @@ internal class MainViewModel( private val _platformsState = MutableStateFlow>(emptyList()) val platformsState: StateFlow> = _platformsState + init { + viewModelScope.launch { + _platformsState.value = eventManager.getPlatforms() + } + } + val eventsFlow: Flow> = Pager(PagingConfig(pageSize = PAGE_SIZE)) { EventPagingSource( eventManager = eventManager, - query = queryState.value, + query = _queryState.value, platform = platformState.value ) } .flow .cachedIn(viewModelScope) - fun setQuery(query: String?) { - queryState.value = query.orEmpty() + /** + * Updates the search query + * This is the main entry point for search functionality + */ + fun setQuery(query: String) { + _queryState.value = query } + /** + * Gets the current search query + */ + fun getQuery(): String = _queryState.value + fun setFilterState(platform: String) { platformState.value = if (platform == "All") "" else platform } diff --git a/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/detail/DetailFragment.kt b/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/detail/DetailFragment.kt index 3e0599b..c864bb3 100644 --- a/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/detail/DetailFragment.kt +++ b/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/detail/DetailFragment.kt @@ -9,6 +9,7 @@ import android.graphics.drawable.GradientDrawable import android.os.Bundle import android.text.SpannableString import android.text.style.BackgroundColorSpan +import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -25,6 +26,8 @@ import com.trendyol.android.devtools.analyticslogger.R import com.trendyol.android.devtools.analyticslogger.databinding.AnalyticsLoggerFragmentDetailBinding import com.trendyol.android.devtools.analyticslogger.internal.di.AnalyticsLoggerKoinComponent import com.trendyol.android.devtools.analyticslogger.internal.domain.usecase.ExcludeKeysUseCase +import com.trendyol.android.devtools.analyticslogger.internal.ext.setupHideKeyboardOnScroll +import com.trendyol.android.devtools.analyticslogger.internal.ext.setupHideKeyboardOnTouch import com.trendyol.android.devtools.analyticslogger.internal.factory.ColorFactory import com.trendyol.android.devtools.analyticslogger.internal.ui.MainViewModel import com.trendyol.android.devtools.analyticslogger.internal.util.executeJS @@ -59,6 +62,9 @@ internal class DetailFragment : Fragment(), AnalyticsLoggerKoinComponent { } private fun initializeViews() = with(binding) { + root.setupHideKeyboardOnTouch() + root.setupHideKeyboardOnScroll() + webViewJsExecutor.settings.javaScriptEnabled = true editTextjsTransformFunction.setText(AnalyticsLogger.getEventTransformFunction()) editTextjsTransformFunction.doAfterTextChanged { @@ -129,26 +135,39 @@ internal class DetailFragment : Fragment(), AnalyticsLoggerKoinComponent { return } - val spannableString = SpannableString(originalJsonText) - val searchTextLower = searchText.lowercase() - val originalTextLower = originalJsonText.lowercase() - val highlightColor = Color.YELLOW - - var startIndex = 0 - while (true) { - val index = originalTextLower.indexOf(searchTextLower, startIndex) - if (index == -1) break - - spannableString.setSpan( - BackgroundColorSpan(highlightColor), - index, - index + searchText.length, - SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE - ) - startIndex = index + 1 - } + runCatching { + val spannableString = SpannableString(originalJsonText) + val searchTextLower = searchText.lowercase() + val originalTextLower = originalJsonText.lowercase() + val highlightColor = Color.YELLOW + + var startIndex = 0 + while (startIndex < originalTextLower.length) { + val index = originalTextLower.indexOf(searchTextLower, startIndex) + if (index == -1) break + + // Safety check: calculate actual matching length in case of case-insensitive differences + val endIndex = (index + searchTextLower.length).coerceAtMost(originalJsonText.length) + + // Only apply span if we have a valid range + if (index >= 0 && endIndex <= originalJsonText.length && index < endIndex) { + spannableString.setSpan( + BackgroundColorSpan(highlightColor), + index, + endIndex, + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + + startIndex = index + searchTextLower.length + } - binding.textViewValue.text = spannableString + binding.textViewValue.text = spannableString + }.onFailure { e -> + // If highlighting fails, just show the original text without highlighting + Log.w("DetailFragment", "Error highlighting text: ${e.message}") + binding.textViewValue.text = originalJsonText + } } private fun copyToClipboard() { diff --git a/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/events/EventsFragment.kt b/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/events/EventsFragment.kt index 19c361e..ba97ae0 100644 --- a/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/events/EventsFragment.kt +++ b/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/events/EventsFragment.kt @@ -1,7 +1,5 @@ package com.trendyol.android.devtools.analyticslogger.internal.ui.events -import android.app.SearchManager -import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.Menu @@ -22,6 +20,8 @@ import com.trendyol.android.devtools.analyticslogger.internal.ui.EventAdapter import com.trendyol.android.devtools.analyticslogger.internal.ui.MainActivity import com.trendyol.android.devtools.analyticslogger.internal.ui.MainViewModel import com.trendyol.android.devtools.analyticslogger.internal.ui.detail.DetailFragment +import com.trendyol.android.devtools.analyticslogger.internal.ext.setupHideKeyboardOnScroll +import com.trendyol.android.devtools.analyticslogger.internal.ext.setupHideKeyboardOnTouch import embedded.koin.android.ext.android.inject import embedded.koin.androidx.viewmodel.ext.android.activityViewModel import kotlinx.coroutines.flow.collectLatest @@ -37,6 +37,7 @@ internal class EventsFragment : Fragment(), AnalyticsLoggerKoinComponent { private val binding get() = _binding!! private var eventAdapter: EventAdapter? = null + private var searchView: SearchView? = null private lateinit var eventPlatformAdapter: EventPlatformAdapter @@ -52,7 +53,24 @@ internal class EventsFragment : Fragment(), AnalyticsLoggerKoinComponent { observeData() } + override fun onPause() { + super.onPause() + // CRITICAL: Remove listeners to prevent SearchView from clearing query during collapse + searchView?.setOnQueryTextListener(null) + searchView?.setOnCloseListener(null) + } + + override fun onDestroyView() { + _binding = null + eventAdapter = null + searchView = null + super.onDestroyView() + } + private fun initView() { + binding.root.setupHideKeyboardOnTouch() + binding.recyclerView.setupHideKeyboardOnScroll() + eventPlatformAdapter = EventPlatformAdapter() binding.platformsRecyclerView.adapter = eventPlatformAdapter @@ -83,11 +101,32 @@ internal class EventsFragment : Fragment(), AnalyticsLoggerKoinComponent { eventPlatformAdapter.submitData(it) } } + + // Observe query changes from ViewModel (single source of truth) + launch { + viewModel.queryState.collectLatest { query -> + // Update adapter's search query for highlighting + eventAdapter?.searchQuery = query + + // Update SearchView if different + if (searchView?.query?.toString() != query) { + searchView?.setQuery(query, false) + } + + // Trigger rebind for highlighting + if (query.isNotEmpty()) { + val itemCount = eventAdapter?.snapshot()?.items?.size ?: 0 + if (itemCount > 0) { + eventAdapter?.notifyItemRangeChanged(0, itemCount, "HIGHLIGHT_UPDATE") + } + } + } + } } } private fun setQuery(query: String?) { - viewModel.setQuery(query) + viewModel.setQuery(query.orEmpty()) eventAdapter?.refresh() } @@ -104,23 +143,17 @@ internal class EventsFragment : Fragment(), AnalyticsLoggerKoinComponent { private fun initSearchView(menu: Menu) { val searchItem = menu.findItem(R.id.action_search) - val searchManager = requireActivity().getSystemService(Context.SEARCH_SERVICE) as SearchManager - val searchView = searchItem.actionView as SearchView - - searchView.setSearchableInfo( - searchManager.getSearchableInfo(requireActivity().componentName) - ) + searchView = searchItem.actionView as SearchView - searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String?): Boolean { - return true - } + // Attach listeners + initSearchViewListeners(searchItem) - override fun onQueryTextChange(newText: String?): Boolean { - setQuery(newText) - return true - } - }) + // Restore query from ViewModel + val currentQuery = viewModel.getQuery() + if (currentQuery.isNotEmpty()) { + searchItem.expandActionView() + searchView?.setQuery(currentQuery, false) + } } @Deprecated("Deprecated in Java") @@ -138,10 +171,53 @@ internal class EventsFragment : Fragment(), AnalyticsLoggerKoinComponent { super.onCreateOptionsMenu(menu, inflater) } - override fun onDestroyView() { - _binding = null - eventAdapter = null - super.onDestroyView() + @Deprecated("Deprecated in Java") + override fun onPrepareOptionsMenu(menu: Menu) { + super.onPrepareOptionsMenu(menu) + + // Get SearchView reference and re-attach listeners + val searchItem = menu.findItem(R.id.action_search) + if (searchView == null && searchItem != null) { + searchView = searchItem.actionView as? SearchView + initSearchViewListeners(searchItem) + } + + // Restore query from ViewModel + val currentQuery = viewModel.getQuery() + if (currentQuery.isNotEmpty() && searchView?.query?.toString() != currentQuery) { + searchItem?.expandActionView() + searchView?.setQuery(currentQuery, false) + } + } + + private fun initSearchViewListeners(searchItem: MenuItem) { + // Prevent query loss when SearchView collapses + searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem): Boolean = true + + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + searchView?.query?.toString()?.let { viewModel.setQuery(it) } + return true + } + }) + + // Prevent close button from clearing query + searchView?.setOnCloseListener { + searchView?.query?.toString()?.let { viewModel.setQuery(it) } + false + } + + searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean = true + + override fun onQueryTextChange(newText: String?): Boolean { + // Only accept query changes when SearchView is actively expanded + if (searchItem.isActionViewExpanded) { + setQuery(newText) + } + return true + } + }) } companion object { diff --git a/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/model/EventItemViewState.kt b/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/model/EventItemViewState.kt new file mode 100644 index 0000000..5febe4e --- /dev/null +++ b/libraries/analytics-logger/src/main/java/com/trendyol/android/devtools/analyticslogger/internal/ui/model/EventItemViewState.kt @@ -0,0 +1,74 @@ +package com.trendyol.android.devtools.analyticslogger.internal.ui.model + +import android.text.SpannableString +import androidx.core.graphics.toColorInt +import com.trendyol.android.devtools.analyticslogger.internal.domain.model.Event +import com.trendyol.android.devtools.analyticslogger.internal.ext.containsQuery +import com.trendyol.android.devtools.analyticslogger.internal.ext.findMatchingLinePreview +import com.trendyol.android.devtools.analyticslogger.internal.ext.highlightQuery + +/** + * ViewState for an Event item in the list + * Contains both data and presentation logic + */ +internal data class EventItemViewState( + val event: Event, + val searchQuery: String +) { + private val keyMatchesQuery: Boolean + get() = event.key.containsQuery(searchQuery) + + private val valueMatchesQuery: Boolean + get() = event.value.containsQuery(searchQuery) && !keyMatchesQuery + + private val bodyPreviewRawText: String? + get() = if (valueMatchesQuery) { + event.value.findMatchingLinePreview(searchQuery) + } else { + null + } + + /** + * Whether the event key should be highlighted + */ + val shouldHighlightKey: Boolean + get() = keyMatchesQuery && searchQuery.isNotEmpty() + + /** + * Whether the body preview should be visible + */ + val isBodyPreviewVisible: Boolean + get() = valueMatchesQuery && bodyPreviewRawText != null + + /** + * Gets the event key text (plain or highlighted) + */ + fun getKeyText(): CharSequence { + return if (shouldHighlightKey) { + event.key.orEmpty().highlightQuery(searchQuery, highlightColor, defaultTextColor) + } else { + event.key.orEmpty() + } + } + + /** + * Gets the body preview text with highlighting + * Returns null if preview should not be shown + */ + fun getBodyPreviewText(): SpannableString? { + return if (isBodyPreviewVisible && bodyPreviewRawText != null) { + bodyPreviewRawText?.highlightQuery(searchQuery, highlightColor, defaultTextColor) + } else { + null + } + } + + companion object { + private val highlightColor = "#FFD54F".toColorInt() + private val defaultTextColor = "#000000".toColorInt() + + fun createDefault(event: Event): EventItemViewState { + return EventItemViewState(event = event, searchQuery = "") + } + } +} diff --git a/libraries/analytics-logger/src/main/res/layout/analytics_logger_item_event.xml b/libraries/analytics-logger/src/main/res/layout/analytics_logger_item_event.xml index e7a75cc..8238a8c 100644 --- a/libraries/analytics-logger/src/main/res/layout/analytics_logger_item_event.xml +++ b/libraries/analytics-logger/src/main/res/layout/analytics_logger_item_event.xml @@ -35,11 +35,29 @@ android:textSize="13sp" android:textColor="@color/textColorSecondary" app:layout_constraintTop_toBottomOf="@id/textViewKey" - app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/textViewPlatform" tools:text="com.example.package.ClassName" /> + +