Skip to content

Add scroll to top/bottom in note view for long notes#866

Merged
Crustack merged 2 commits intomainfrom
feat/189
Feb 23, 2026
Merged

Add scroll to top/bottom in note view for long notes#866
Crustack merged 2 commits intomainfrom
feat/189

Conversation

@Crustack
Copy link
Copy Markdown
Owner

@Crustack Crustack commented Feb 23, 2026

Closes #189

Adds scroll to top/bottom buttons for "long" notes:

  • for text notes >= 75 visual text lines (in landscape mode >= 30)
  • for list notes >= 25 items (in landscape mode >= 15)

Summary by CodeRabbit

  • New Features

    • Jump-to-top and jump-to-bottom buttons added to the note editor with new icons and labels.
    • Buttons auto-show/hide based on device orientation and content size; list edits and text changes update their visibility in real time.
  • Documentation

    • Updated translation coverage table and language percentage totals to reflect new overall counts.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

Adds scroll-to-top and scroll-to-bottom controls: new jump button views, orientation-aware visibility logic in EditActivity, ListManager callbacks to report item count changes, new vector icons and strings, and updated translation totals in TRANSLATIONS.md.

Changes

Cohort / File(s) Summary
Jump Button UI
app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt, app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditListActivity.kt, app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditNoteActivity.kt
Added jumpToTop/jumpToBottom View properties, updateJumpButtonsVisibility(manualSize: Int? = null) with orientation- and content-size logic, wired buttons into BottomAppBar, and invoke visibility updates on content changes.
ListManager Size Notifications
app/src/main/java/com/philkes/notallyx/presentation/view/note/listitem/ListManager.kt, app/src/test/kotlin/com/philkes/notallyx/recyclerview/listmanager/ListManagerTestBase.kt
Added onItemSizeChanged: ((items: Int) -> Unit)? constructor parameter; invoke callback after add/delete operations; updated test construction to pass new parameters.
Resources & Helpers
app/src/main/res/drawable/vertical_align_top.xml, app/src/main/res/drawable/vertical_align_bottom.xml, app/src/main/res/values/strings.xml, app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt
Added two vector drawable icons, jump_to_top/jump_to_bottom string resources, and Activity.isInLandscapeMode extension property for orientation detection.
Translations
TRANSLATIONS.md
Adjusted translation coverage totals from 331 → 333 and updated numerators/percentages across language rows.

Sequence Diagram

sequenceDiagram
    actor User
    participant EditActivity
    participant ListManager
    participant BottomAppBar
    participant ScrollView

    User->>EditActivity: edit content / type
    EditActivity->>ListManager: notify text/item change
    ListManager->>EditActivity: onItemSizeChanged(size)
    EditActivity->>EditActivity: updateJumpButtonsVisibility()
    EditActivity->>BottomAppBar: set jump button visibility
    User->>BottomAppBar: tap Jump To Top / Bottom
    BottomAppBar->>ScrollView: scroll to top / bottom
    ScrollView->>User: content moved
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hop and I peek, from start to the end,
Two tiny doors where big scrolls once spent.
Top and bottom, a single quick clap,
No more long journeys — just a hop and a zap!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add scroll to top/bottom in note view for long notes' accurately summarizes the main change in the PR, which adds scroll-to-top and scroll-to-bottom buttons to the note view.
Linked Issues check ✅ Passed The PR fully addresses issue #189 by adding 'To top' and 'To bottom' buttons on the bottom toolbar with appropriate visibility thresholds for both text and list notes in landscape and portrait modes.
Out of Scope Changes check ✅ Passed All code changes are directly related to implementing the scroll-to-top/bottom feature, including UI components, visibility logic, vector drawables, and necessary test updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/189

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/src/main/java/com/philkes/notallyx/presentation/view/note/listitem/ListManager.kt (2)

84-89: ⚠️ Potential issue | 🟠 Major

Undo/redo of list-item add/delete leaves jump button visibility stale.

setState is the entry point for every undo and redo operation on list notes. It updates the adapter and itemsChecked but never calls onItemSizeChanged, so after undoing a delete (item count increases) or undoing an add (item count decreases), the jump buttons won't show or hide as expected.

For text notes this is already handled because EditNoteActivity.setupListeners wires updateJumpButtonsVisibility() into the history callback. List notes have no equivalent hook.

The simplest fix is to notify in setState:

🐛 Proposed fix
 internal fun setState(state: ListState) {
     adapter.submitList(state.items) {
         state.focusedItemPos?.let { itemPos -> focusItem(itemPos, state.cursorPos) }
     }
     this.itemsChecked?.setItems(state.checkedItems!!)
+    onItemSizeChanged?.invoke(state.items.size + (state.checkedItems?.size ?: 0))
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/philkes/notallyx/presentation/view/note/listitem/ListManager.kt`
around lines 84 - 89, setState in ListManager updates the adapter and
itemsChecked but never triggers onItemSizeChanged, so undo/redo that changes
item count leaves jump-button visibility stale; after
adapter.submitList(state.items) completes (i.e., inside its completion callback
where you already call focusItem), invoke onItemSizeChanged() to recalculate and
update jump buttons (ensure you do this before or after calling focusItem as
appropriate), and also ensure itemsChecked.setItems(state.checkedItems!!) is
called after this update if ordering matters.

367-378: ⚠️ Potential issue | 🟠 Major

items.size in deleteCheckedItems reads the potentially-stale adapter property.

items here is the property getter (get() = adapter.items). adapter.submitList(itemsUpdated) is submitted earlier on line 372, but if ListItemAdapter uses AsyncListDiffer / DiffUtil the underlying list reference may not have swapped by the time line 377 executes. The companion method delete() correctly avoids this by using its local copy; deleteCheckedItems should do the same.

🐛 Proposed fix
     adapter.submitList(itemsUpdated)
     itemsChecked?.deleteCheckedItems()
     if (pushChange) {
         changeHistory.push(DeleteCheckedChange(stateBefore, getState(), this))
     }
-    onItemSizeChanged?.invoke(items.size)
+    onItemSizeChanged?.invoke(itemsUpdated.size)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/philkes/notallyx/presentation/view/note/listitem/ListManager.kt`
around lines 367 - 378, The method deleteCheckedItems reads the adapter-backed
property items after submitting a new list which can be asynchronously swapped;
replace the stale adapter access with the local cloned list: compute the new
size from itemsUpdated and call onItemSizeChanged?.invoke(itemsUpdated.size)
(mirroring the pattern used in delete()), and ensure any other uses that rely on
the post-submit list state use itemsUpdated instead of the adapter-backed items.
🧹 Nitpick comments (4)
app/src/main/java/com/philkes/notallyx/presentation/view/note/listitem/ListManager.kt (1)

43-51: Misleading parameter name in callback type.

((items: Int) -> Unit)? names the lambda parameter items, but the value is an item count (Int), not the list. This is confusing at every call site and especially at declaration. itemCount or count would be clearer.

♻️ Proposed rename
-    val onItemSizeChanged: ((items: Int) -> Unit)?,
+    val onItemSizeChanged: ((itemCount: Int) -> Unit)?,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/philkes/notallyx/presentation/view/note/listitem/ListManager.kt`
around lines 43 - 51, The callback parameter name onItemSizeChanged in class
ListManager is misleading—rename the lambda parameter from items to itemCount
(or count) in the ListManager constructor signature and update all call sites
and any anonymous/lambda implementations to use the new name (onItemSizeChanged:
((itemCount: Int) -> Unit)?). Locate references by the symbol onItemSizeChanged
and the ListManager constructor and update parameter documentation/comments if
present.
app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt (1)

644-663: getChildAt(0) null-guard is dead code.

binding.ScrollView is the single-child NestedScrollView whose child layout is always inflated before the button can be pressed; getChildAt(0) will never return null here. The else branch is unreachable, and the explicit binding.ScrollView.* calls inside apply { post { } } are redundant since this already refers to the ScrollView.

♻️ Suggested simplification
-binding.ScrollView.apply {
-    post {
-        val lastChild: View? = binding.ScrollView.getChildAt(0)
-        if (lastChild != null) {
-            val bottom: Int =
-                lastChild.bottom + binding.ScrollView.paddingBottom
-            binding.ScrollView.smoothScrollTo(0, bottom)
-        } else {
-            fullScroll(View.FOCUS_DOWN)
-        }
-    }
-}
+binding.ScrollView.post {
+    val lastChild = binding.ScrollView.getChildAt(0) ?: return@post
+    val bottom = lastChild.bottom + binding.ScrollView.paddingBottom
+    binding.ScrollView.smoothScrollTo(0, bottom)
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt`
around lines 644 - 663, The null-check for getChildAt(0) in the jumpToBottom
button handler is dead code and you can simplify the apply/post block: inside
the addIconButton lambda (the jumpToBottom creation) remove the unreachable else
branch and the explicit binding.ScrollView qualifiers, use the ScrollView
instance referenced by this (from apply) and assume its single child exists
(e.g., val lastChild = getChildAt(0)), compute bottom from lastChild.bottom +
paddingBottom, then call smoothScrollTo(0, bottom); keep
updateJumpButtonsVisibility() as-is.
app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditListActivity.kt (1)

244-250: Lambda parameter items shadows the outer class property.

The { items -> ... } binding shadows EditListActivity.items: MutableList<ListItem>. Since the parameter is actually an item count (Int), renaming it avoids confusion for future readers.

♻️ Suggested rename
-                { items -> updateJumpButtonsVisibility(items) },
+                { count -> updateJumpButtonsVisibility(count) },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditListActivity.kt`
around lines 244 - 250, The lambda passed to updateJumpButtonsVisibility
currently names its parameter "items", which shadows the EditListActivity.items
property; change the lambda parameter name (e.g., to "itemCount" or "count") so
it reflects that it is an Int and does not shadow the class property, updating
the usage inside the lambda accordingly (the call to updateJumpButtonsVisibility
should remain unchanged).
app/src/test/kotlin/com/philkes/notallyx/recyclerview/listmanager/ListManagerTestBase.kt (1)

61-63: Constructor update is correct; consider capturing onItemSizeChanged to enable callback verification.

Passing null for onItemSizeChanged is safe for the existing tests, but it means no test currently asserts that the callback is invoked (or invoked with the correct count) after item additions/deletions. Given that this callback drives the jump-button visibility in production, having at least one test that wires in a capturing lambda would protect against regressions.

🔬 Suggested approach for capturing the callback in tests
+    var lastReportedItemSize: Int? = null
+
     listManager =
-        ListManager(recyclerView, changeHistory, preferences, inputMethodManager, {}, {}, null)
+        ListManager(recyclerView, changeHistory, preferences, inputMethodManager, {}, {}) { count ->
+            lastReportedItemSize = count
+        }

Individual test cases can then assert:

// e.g. after listManager.add(...)
assertEquals(expectedCount, lastReportedItemSize)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/test/kotlin/com/philkes/notallyx/recyclerview/listmanager/ListManagerTestBase.kt`
around lines 61 - 63, The tests currently pass null for the ListManager
constructor's onItemSizeChanged parameter, so no test can verify the callback;
update ListManagerTestBase to provide a capturing lambda to the ListManager(...)
call (reference: ListManager constructor and listManager variable) that writes
the reported count into a test-scoped var (e.g. lastReportedItemSize)
initialized in the test base, and then update/add tests to assert expected
values after operations (e.g. after listManager.add/... or remove) to validate
the callback is invoked with the correct count.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt`:
- Around line 552-567: Remove the debug Log.d call in
updateJumpButtonsVisibility and avoid reading binding.EnterBody.lineCount for
LIST-type notes; compute the candidate size as manualSize ?: if
(notallyModel.type == Type.NOTE) binding.EnterBody.lineCount else
notallyModel.items.size, then use that value in the existing show calculation
and set jumpToTop/isVisible and jumpToBottom/isVisible as before, leaving
function name updateJumpButtonsVisibility, binding.EnterBody.lineCount,
notallyModel.type, Type.LIST, and notallyModel.items references to locate the
change.

In
`@app/src/main/java/com/philkes/notallyx/presentation/view/note/listitem/ListManager.kt`:
- Around line 154-155: onItemSizeChanged currently passes only unchecked item
count because items (which delegates to adapter.items) excludes checked items
when autoSortByChecked is enabled; update every onItemSizeChanged?.invoke(...)
call (e.g., the ones near usages of updateJumpButtonsVisibility and manualSize)
to pass the combined total of unchecked plus checked items by summing items.size
and (itemsChecked?.size() ?: 0) so the visibility logic uses the true total item
count.

---

Outside diff comments:
In
`@app/src/main/java/com/philkes/notallyx/presentation/view/note/listitem/ListManager.kt`:
- Around line 84-89: setState in ListManager updates the adapter and
itemsChecked but never triggers onItemSizeChanged, so undo/redo that changes
item count leaves jump-button visibility stale; after
adapter.submitList(state.items) completes (i.e., inside its completion callback
where you already call focusItem), invoke onItemSizeChanged() to recalculate and
update jump buttons (ensure you do this before or after calling focusItem as
appropriate), and also ensure itemsChecked.setItems(state.checkedItems!!) is
called after this update if ordering matters.
- Around line 367-378: The method deleteCheckedItems reads the adapter-backed
property items after submitting a new list which can be asynchronously swapped;
replace the stale adapter access with the local cloned list: compute the new
size from itemsUpdated and call onItemSizeChanged?.invoke(itemsUpdated.size)
(mirroring the pattern used in delete()), and ensure any other uses that rely on
the post-submit list state use itemsUpdated instead of the adapter-backed items.

---

Nitpick comments:
In
`@app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt`:
- Around line 644-663: The null-check for getChildAt(0) in the jumpToBottom
button handler is dead code and you can simplify the apply/post block: inside
the addIconButton lambda (the jumpToBottom creation) remove the unreachable else
branch and the explicit binding.ScrollView qualifiers, use the ScrollView
instance referenced by this (from apply) and assume its single child exists
(e.g., val lastChild = getChildAt(0)), compute bottom from lastChild.bottom +
paddingBottom, then call smoothScrollTo(0, bottom); keep
updateJumpButtonsVisibility() as-is.

In
`@app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditListActivity.kt`:
- Around line 244-250: The lambda passed to updateJumpButtonsVisibility
currently names its parameter "items", which shadows the EditListActivity.items
property; change the lambda parameter name (e.g., to "itemCount" or "count") so
it reflects that it is an Int and does not shadow the class property, updating
the usage inside the lambda accordingly (the call to updateJumpButtonsVisibility
should remain unchanged).

In
`@app/src/main/java/com/philkes/notallyx/presentation/view/note/listitem/ListManager.kt`:
- Around line 43-51: The callback parameter name onItemSizeChanged in class
ListManager is misleading—rename the lambda parameter from items to itemCount
(or count) in the ListManager constructor signature and update all call sites
and any anonymous/lambda implementations to use the new name (onItemSizeChanged:
((itemCount: Int) -> Unit)?). Locate references by the symbol onItemSizeChanged
and the ListManager constructor and update parameter documentation/comments if
present.

In
`@app/src/test/kotlin/com/philkes/notallyx/recyclerview/listmanager/ListManagerTestBase.kt`:
- Around line 61-63: The tests currently pass null for the ListManager
constructor's onItemSizeChanged parameter, so no test can verify the callback;
update ListManagerTestBase to provide a capturing lambda to the ListManager(...)
call (reference: ListManager constructor and listManager variable) that writes
the reported count into a test-scoped var (e.g. lastReportedItemSize)
initialized in the test base, and then update/add tests to assert expected
values after operations (e.g. after listManager.add/... or remove) to validate
the callback is invoked with the correct count.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a4b959 and 23acb10.

⛔ Files ignored due to path filters (1)
  • app/translations.xlsx is excluded by !**/*.xlsx
📒 Files selected for processing (10)
  • TRANSLATIONS.md
  • app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt
  • app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditListActivity.kt
  • app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditNoteActivity.kt
  • app/src/main/java/com/philkes/notallyx/presentation/view/note/listitem/ListManager.kt
  • app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt
  • app/src/main/res/drawable/vertical_align_bottom.xml
  • app/src/main/res/drawable/vertical_align_top.xml
  • app/src/main/res/values/strings.xml
  • app/src/test/kotlin/com/philkes/notallyx/recyclerview/listmanager/ListManagerTestBase.kt

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt (1)

249-251: Consider widening the receiver to Context for broader reusability.

resources.configuration is available on any Context, so restricting the receiver to Activity is unnecessarily narrow. The current callers in EditActivity would still compile without change.

♻️ Proposed refactor
-val Activity.isInLandscapeMode
+val Context.isInLandscapeMode
     get() = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt` around
lines 249 - 251, The extension val Activity.isInLandscapeMode is unnecessarily
narrow; change its receiver from Activity to Context so any Context can call it
(e.g., val Context.isInLandscapeMode). Update the declaration symbol
isInLandscapeMode accordingly and ensure usages in EditActivity still compile;
keep the implementation using resources.configuration.orientation ==
Configuration.ORIENTATION_LANDSCAPE and import Configuration if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt`:
- Around line 249-251: The extension val Activity.isInLandscapeMode is
unnecessarily narrow; change its receiver from Activity to Context so any
Context can call it (e.g., val Context.isInLandscapeMode). Update the
declaration symbol isInLandscapeMode accordingly and ensure usages in
EditActivity still compile; keep the implementation using
resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE and
import Configuration if needed.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23acb10 and e84935b.

⛔ Files ignored due to path filters (1)
  • app/translations.xlsx is excluded by !**/*.xlsx
📒 Files selected for processing (10)
  • TRANSLATIONS.md
  • app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt
  • app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditListActivity.kt
  • app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditNoteActivity.kt
  • app/src/main/java/com/philkes/notallyx/presentation/view/note/listitem/ListManager.kt
  • app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt
  • app/src/main/res/drawable/vertical_align_bottom.xml
  • app/src/main/res/drawable/vertical_align_top.xml
  • app/src/main/res/values/strings.xml
  • app/src/test/kotlin/com/philkes/notallyx/recyclerview/listmanager/ListManagerTestBase.kt
🚧 Files skipped from review as they are similar to previous changes (7)
  • app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt
  • app/src/main/res/drawable/vertical_align_top.xml
  • app/src/main/res/values/strings.xml
  • app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditListActivity.kt
  • app/src/test/kotlin/com/philkes/notallyx/recyclerview/listmanager/ListManagerTestBase.kt
  • app/src/main/res/drawable/vertical_align_bottom.xml
  • app/src/main/java/com/philkes/notallyx/presentation/view/note/listitem/ListManager.kt

@Crustack Crustack merged commit 8ba1078 into main Feb 23, 2026
1 check passed
@Crustack Crustack deleted the feat/189 branch February 23, 2026 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add scroll "to top" and "to bottom" shortcut

1 participant