From 976ca9596ee30ac2146092477d22af2ce7bede4c Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Mon, 27 Jul 2026 20:30:53 +0000 Subject: [PATCH] Preserve log position when collapsing rows --- .../notificationlog/EventSettingsFragment.kt | 2 +- .../notificationlog/ViewLogsFragment.kt | 50 +++++++++++++++++++ .../settings/CaptureSettingsStore.kt | 2 +- .../res/layout/fragment_event_settings.xml | 2 +- .../capture/GroupSummaryPolicyTest.kt | 2 +- 5 files changed, 54 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/se/ajpanton/notificationlog/EventSettingsFragment.kt b/app/src/main/java/se/ajpanton/notificationlog/EventSettingsFragment.kt index 19fd8e1..231deb5 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/EventSettingsFragment.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/EventSettingsFragment.kt @@ -96,7 +96,7 @@ class FilterAppsFragment : Fragment(R.layout.fragment_event_settings) { private fun updateRuleLabels(rule: AppFilterSettings) { binding!!.appRuleToggle.text = modeLabel(rule.mode) - binding!!.onlySeenToggle.text = "Show only seen and checked apps" + binding!!.onlySeenToggle.text = "Show only seen and edited apps" binding!!.seenFirstToggle.text = "Show seen apps first" } diff --git a/app/src/main/java/se/ajpanton/notificationlog/ViewLogsFragment.kt b/app/src/main/java/se/ajpanton/notificationlog/ViewLogsFragment.kt index be3739c..9c40cb1 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/ViewLogsFragment.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/ViewLogsFragment.kt @@ -21,6 +21,7 @@ import se.ajpanton.notificationlog.settings.DisplayEvent import java.text.DateFormat import java.util.Date import androidx.fragment.app.Fragment +import androidx.core.view.children class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) { private var binding: FragmentViewLogsBinding? = null @@ -107,9 +108,12 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) { val metadata = metadataValues(row.entry, settings) val contents = fieldValue(LogField.CONTENTS, row.entry, settings) val hasContents = LogField.CONTENTS in settings.visibleFields && !contents.isNullOrEmpty() + val collapseAnchor = if (expanded) captureCollapseAnchor(this@apply) else null val toggleExpanded = { if (!expandedIds.add(row.entry.id)) expandedIds.remove(row.entry.id) renderExpanded(row.entry.id in expandedIds) + collapseAnchor?.let(::restoreCollapseAnchor) + Unit } val metadataPill = metadataPill(metadata, expanded, toggleExpanded, row.entry.id).apply { layoutParams = LinearLayout.LayoutParams( @@ -293,6 +297,45 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) { } } + private fun captureCollapseAnchor(collapsingRow: View): CollapseAnchor { + val currentBinding = binding ?: return CollapseAnchor.NONE + val scroll = currentBinding.logScroll + val rowsTop = currentBinding.logRows.top + val topEdge = scroll.scrollY - rowsTop + val bottomEdge = topEdge + scroll.height + val topRow = rowAt(currentBinding.logRows, topEdge) + val bottomRow = rowAt(currentBinding.logRows, bottomEdge) + return when { + topRow === collapsingRow && bottomRow === collapsingRow -> CollapseAnchor.CENTER_COLLAPSED_ROW(collapsingRow) + topRow === collapsingRow && bottomRow != null -> + CollapseAnchor.KEEP_BOTTOM_EDGE(bottomRow, bottomRow.bottom - bottomEdge) + bottomRow === collapsingRow && topRow != null -> + CollapseAnchor.KEEP_TOP_EDGE(topRow, topEdge - topRow.top) + else -> CollapseAnchor.NONE + } + } + + private fun restoreCollapseAnchor(anchor: CollapseAnchor) { + val currentBinding = binding ?: return + val scroll = currentBinding.logScroll + val rows = currentBinding.logRows + rows.post { + if (binding !== currentBinding) return@post + val target = when (anchor) { + CollapseAnchor.NONE -> return@post + is CollapseAnchor.CENTER_COLLAPSED_ROW -> rows.top + anchor.row.top + anchor.row.height / 2 - scroll.height / 2 + is CollapseAnchor.KEEP_TOP_EDGE -> rows.top + anchor.row.top + anchor.offset + is CollapseAnchor.KEEP_BOTTOM_EDGE -> rows.top + anchor.row.bottom - anchor.offset - scroll.height + } + val maximum = (scroll.getChildAt(0).height - scroll.height).coerceAtLeast(0) + scroll.scrollTo(0, target.coerceIn(0, maximum)) + } + } + + private fun rowAt(rows: LinearLayout, edge: Int): View? = rows.children.firstOrNull { row -> + edge >= row.top && edge <= row.bottom + } + private fun dp(value: Int): Int = (value * resources.displayMetrics.density).toInt() private data class LogRow(val entry: NotificationLogEntry) @@ -304,6 +347,13 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) { val breakBeforePeriods: Boolean = false, ) + private sealed interface CollapseAnchor { + data object NONE : CollapseAnchor + data class CENTER_COLLAPSED_ROW(val row: View) : CollapseAnchor + data class KEEP_TOP_EDGE(val row: View, val offset: Int) : CollapseAnchor + data class KEEP_BOTTOM_EDGE(val row: View, val offset: Int) : CollapseAnchor + } + private companion object { const val LEFT_PILL_WEIGHT = 0.42f const val RIGHT_PILL_WEIGHT = 0.58f diff --git a/app/src/main/java/se/ajpanton/notificationlog/settings/CaptureSettingsStore.kt b/app/src/main/java/se/ajpanton/notificationlog/settings/CaptureSettingsStore.kt index 6092d75..095c337 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/settings/CaptureSettingsStore.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/settings/CaptureSettingsStore.kt @@ -8,7 +8,7 @@ class CaptureSettingsStore(context: Context) { private val preferences = context.applicationContext.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) var logGroupSummaries: Boolean - get() = preferences.getBoolean(LOG_GROUP_SUMMARIES, true) + get() = preferences.getBoolean(LOG_GROUP_SUMMARIES, false) set(value) = preferences.edit { putBoolean(LOG_GROUP_SUMMARIES, value) } private companion object { diff --git a/app/src/main/res/layout/fragment_event_settings.xml b/app/src/main/res/layout/fragment_event_settings.xml index f18d51d..cb634ad 100644 --- a/app/src/main/res/layout/fragment_event_settings.xml +++ b/app/src/main/res/layout/fragment_event_settings.xml @@ -14,7 +14,7 @@ android:id="@+id/only_seen_toggle" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="Show only seen apps" /> + android:text="Show only seen and edited apps" />