Skip to content

ADFA-4386: Show active filter chips and indicator dot on Recent Projects#1427

Open
Daniel-ADFA wants to merge 3 commits into
stagefrom
ADFA-4386
Open

ADFA-4386: Show active filter chips and indicator dot on Recent Projects#1427
Daniel-ADFA wants to merge 3 commits into
stagefrom
ADFA-4386

Conversation

@Daniel-ADFA

@Daniel-ADFA Daniel-ADFA commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@Daniel-ADFA Daniel-ADFA requested a review from a team June 19, 2026 16:53
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Release Notes: Show Active Filter Chips and Indicator Dot on Recent Projects

  • Added “active filters” UI to the Recent Projects filters bar, displaying removable chips for:
    • Active sort criteria (with ↑/↓ direction indicator)
    • Active search query
  • Added a visual “active filters” indicator dot on the sort button; it shows whenever any filter (sort or search) is active.
  • Implemented chip interactions:
    • Chip close removes the corresponding filter (sort or search)
    • Tapping the search chip focuses the search field and shows the keyboard
    • Tapping the sort chip opens the filters sheet
  • Kept UI in sync with ViewModel state by rendering chips from a new FilterState model (query, sort, ascending).
  • Updated animations for filter bar changes using TransitionManager.beginDelayedTransition() with an AutoTransition duration of 180ms.
  • Improved filters-sheet dismiss behavior:
    • The fragment stores the active BottomSheetDialog and dismisses it when the ViewModel emits filterEvents.
  • New/updated resources:
    • layout_project_filters_bar.xml (vertical bar layout, active dot view, horizontal scroll chip container)
    • chip_active_filter.xml (Material3 removable chip with close icon)
    • Strings: filters_active, filter_chip_remove_sort, filter_chip_remove_search
    • Drawable: bg_filter_active_dot.xml

⚠️ Risks / Best-practice notes

  • Lifecycle collection: setupFilterChips() collects filterState and filterEvents with viewLifecycleScope.launch without repeatOnLifecycle(...), so collectors may continue beyond the STARTED lifecycle state.
  • Animation allocation: a new AutoTransition() is created for each filter update in beginFilterBarTransition(...).
  • Localization/typography: the search chip text uses curly quotes (“...”), which may not be ideal for all locales/typographic expectations.
  • Accessibility: the active dot is explicitly marked importantForAccessibility="no"—confirm this matches the desired screen-reader behavior.

Walkthrough

Adds an active-filters chip bar to the recent projects screen by introducing a FilterState data class and StateFlow in RecentProjectsViewModel. The filter bar layout is restructured vertically to include a ChipGroup in a HorizontalScrollView and an active-filters dot indicator. RecentProjectsFragment collects filterState, renders removable chips for active sort and search with close handlers, animates transitions via TransitionManager, and stores the bottom-sheet dialog reference for synchronized dismissal.

Changes

Active Filters Chip Bar

Layer / File(s) Summary
FilterState data class and StateFlow in ViewModel
app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt
Introduces FilterState data class with query, sort, ascending, and derived hasAny property; adds _filterState MutableStateFlow and public filterState: StateFlow<FilterState>; updates applyFilters() to emit current filter state before computing filtered list.
Clear sort action in ViewModel
app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt
Adds clearSort() suspend function that resets currentSort to null, restores ascending direction, and re-applies filters.
Filter bar layout restructure and visual resources
app/src/main/res/layout/layout_project_filters_bar.xml, app/src/main/res/layout/chip_active_filter.xml, resources/src/main/res/drawable/bg_filter_active_dot.xml, resources/src/main/res/values/strings.xml
Restructures filter bar root to vertical orientation with inner horizontal row for search/sort; wraps sort button in FrameLayout with initially hidden active-dot indicator; appends HorizontalScrollView + ChipGroup for active filter chips. Adds oval dot drawable, Material3 chip layout with close icon, and three string resources for filter labels.
Fragment chip rendering, close handlers, and animation
app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt
Adds transition/Chip/FilterState imports; stores BottomSheetDialog in filtersDialog property with dismiss listener; calls setupFilterChips() from onViewCreated; implements setupFilterChips() to collect filterState and rebuild active sort/search chips with close handlers; adds buildFilterChip() for chip creation and beginFilterBarTransition() using TransitionManager + AutoTransition; refactors setupSortUI() to use SortCriteria.labelRes() helper.

Sequence Diagram(s)

sequenceDiagram
  participant RecentProjectsFragment
  participant RecentProjectsViewModel
  participant filterState
  participant active_filters_group
  participant TransitionManager
  RecentProjectsFragment->>filterState: collect filterState
  filterState-->>RecentProjectsFragment: FilterState(query, sort, ascending, hasAny)
  RecentProjectsFragment->>TransitionManager: beginDelayedTransition(AutoTransition)
  RecentProjectsFragment->>active_filters_group: clear children
  RecentProjectsFragment->>active_filters_group: addView(sortChip)
  RecentProjectsFragment->>active_filters_group: addView(searchChip)
  Note over RecentProjectsFragment: Chip close clicked
  RecentProjectsFragment->>RecentProjectsViewModel: clearSort() or onSearchQuery("")
  RecentProjectsViewModel-->>RecentProjectsFragment: updated filterState emitted
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • appdevforall/CodeOnTheGo#700: Introduced the RecentProjectsViewModel filtering/sorting workflow (applyFilters(), onSortSelected, onSearchQuery) that the chip close handlers directly invoke.

Suggested reviewers

  • jatezzz
  • itsaky-adfa
  • dara-abijo-adfa
  • alome007

Poem

🐇 A chip for the sort, a chip for the search,
Animated dots appear on their perch.
The filter bar grows tall, then wide,
With scrolling chips displayed inside.
Each close-icon tap clears the state—
This rabbit's filter bar looks great! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess relevance to the changeset. Add a brief description explaining the feature changes, implementation approach, or any testing performed.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: displaying active filter chips and an indicator dot on the Recent Projects screen.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ADFA-4386

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/src/main/res/drawable/bg_filter_active_dot.xml (1)

1-11: ⚡ Quick win

Move this drawable to the shared resources module

This new drawable is in app/src/main/res/drawable/, but project convention keeps shared drawables in resources/src/main/res/drawable/ to avoid duplication and keep assets centralized.

Based on learnings, drawable resources in this project should live under resources/src/main/res/drawable/ rather than the app module drawable folder.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/res/drawable/bg_filter_active_dot.xml` around lines 1 - 11, The
drawable file bg_filter_active_dot.xml is currently located in the app module's
drawable directory (app/src/main/res/drawable/) but it should be moved to the
shared resources module's drawable directory (resources/src/main/res/drawable/)
to follow project convention and avoid duplication. Move the entire
bg_filter_active_dot.xml file from app/src/main/res/drawable/ to
resources/src/main/res/drawable/ to keep shared drawable assets centralized.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt`:
- Line 42: The hasAny property in FilterState does not account for the
ascending/descending sort order state. Update the boolean expression in the
hasAny getter to include a check for when ascending is false, in addition to the
existing checks for sort != null and query.isNotEmpty(). This will ensure that
descending-only filters are properly recognized as active and consistently
reflected in the filter indicator state.

---

Nitpick comments:
In `@app/src/main/res/drawable/bg_filter_active_dot.xml`:
- Around line 1-11: The drawable file bg_filter_active_dot.xml is currently
located in the app module's drawable directory (app/src/main/res/drawable/) but
it should be moved to the shared resources module's drawable directory
(resources/src/main/res/drawable/) to follow project convention and avoid
duplication. Move the entire bg_filter_active_dot.xml file from
app/src/main/res/drawable/ to resources/src/main/res/drawable/ to keep shared
drawable assets centralized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d6183531-b48f-49d4-8a80-ae189c449b4c

📥 Commits

Reviewing files that changed from the base of the PR and between d73ec0b and 7ba3d66.

📒 Files selected for processing (6)
  • app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt
  • app/src/main/res/drawable/bg_filter_active_dot.xml
  • app/src/main/res/layout/chip_active_filter.xml
  • app/src/main/res/layout/layout_project_filters_bar.xml
  • resources/src/main/res/values/strings.xml

@hal-eisen-adfa hal-eisen-adfa left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code review for the active-filter chips + indicator dot. Findings posted inline, most-impactful first. The headline is the dot-vs-chip truth-source split (descending-only lights the dot with no clearable chip — the exact ticket gap); the rest are leak/double-work and reuse cleanups.

Comment thread app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt Outdated
Comment thread app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt Outdated
Comment thread app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt Outdated
Comment thread app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt Outdated
Comment thread app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt Outdated
Comment thread app/src/main/res/layout/layout_project_filters_bar.xml
Comment thread app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt Outdated
…leak & double filter passes

- Make sort direction subordinate to a sort criteria: hasAny = sort != null ||
  query.isNotEmpty() and applyFilters() only reverses when a criteria is set, so
  a descending-only state no longer lights the indicator dot with no clearable
  chip. hasActiveFilters now delegates to filterState.value.hasAny so the dot and
  the sheet's Clear button share one definition of "active".
- Collect filterEvents once for the view lifetime and dismiss a single
  filtersDialog ref instead of launching a per-open collector on every sheet open.
- Sort chip removal: add clearSort() (one applyFilters pass) - no dot flicker.
- Search chip removal: clear the EditText only and let the debounced watcher
  drive the VM - one filter pass. Search chip body now focuses the search field
  instead of opening the unrelated sort sheet.
- Extract SortCriteria.labelRes() shared by setupSortUI and renderActiveFilters.
- Announce active state on the filter button's contentDescription and mark the
  decorative dot importantForAccessibility=no for TalkBack.
- Run beginDelayedTransition before chip mutations so chip enter/exit animates.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt (1)

97-117: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

applyFilters() mixes mutable shared state across coroutine boundaries

applyFilters() publishes _filterState first, then reads currentQuery/currentSort/isAscending during background computation. Concurrent calls can produce a filtered list that does not match the emitted filterState (or overwrite newer results with older computation).

Use a single immutable snapshot for both emission and filtering/sorting, and compute from that snapshot only.

Suggested fix
 private suspend fun applyFilters() {
-    _filterState.value = FilterState(currentQuery, currentSort, isAscending)
+    val snapshot = FilterState(currentQuery, currentSort, isAscending)
+    _filterState.value = snapshot
     withContext(Dispatchers.Default) {
         var result = allProjects

-        if (currentQuery.isNotEmpty()) {
-            result = result.filter { it.name.contains(currentQuery, ignoreCase = true) }
+        if (snapshot.query.isNotEmpty()) {
+            result = result.filter { it.name.contains(snapshot.query, ignoreCase = true) }
         }

-        val criteria = currentSort
+        val criteria = snapshot.sort
         if (criteria != null) {
             result = when (criteria) {
                 SortCriteria.NAME -> result.sortedBy { it.name.lowercase() }
                 SortCriteria.DATE_CREATED -> result.sortedBy { it.createdAt }
                 SortCriteria.DATE_MODIFIED -> result.sortedBy { it.lastModified }
             }
-            if (!isAscending) {
+            if (!snapshot.ascending) {
                 result = result.reversed()
             }
         }
         _projects.postValue(result)
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt`
around lines 97 - 117, In the `applyFilters()` method, the issue is that
`_filterState` is published before the background computation begins, but the
filtering logic reads the current values of `currentQuery`, `currentSort`, and
`isAscending` from shared state. If `applyFilters()` is called again before the
first coroutine completes, the emitted filter state will not match the actual
filtered results due to the race condition. Create a local immutable snapshot of
`currentQuery`, `currentSort`, and `isAscending` before the `FilterState`
assignment, then use this snapshot both for emitting to `_filterState` and for
the filtering and sorting logic within the `withContext` block to ensure
consistency between the emitted state and the computed results.
♻️ Duplicate comments (1)
app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt (1)

42-42: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

hasAny no longer reflects descending-only active sort direction

At Line 42, hasAny dropped !ascending, and Line 67 now fully depends on this value. That can hide active state when direction is changed to descending before selecting a criterion, creating inconsistent filter-state signaling.

Suggested fix
 data class FilterState(
     val query: String = "",
     val sort: SortCriteria? = null,
     val ascending: Boolean = true
 ) {
-    val hasAny: Boolean get() = sort != null || query.isNotEmpty()
+    val hasAny: Boolean get() = sort != null || !ascending || query.isNotEmpty()
 }

Also applies to: 67-67

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt`
at line 42, The hasAny property in RecentProjectsViewModel is missing a check
for the ascending flag that was previously included. Currently, it only checks
if sort is not null or query is not empty, but it should also evaluate whether
the sort direction is descending (when ascending is false). Add the !ascending
condition to the hasAny property logic using a logical OR operator so that it
properly reflects an active descending sort direction state, which is especially
important since line 67 depends on this value to signal filter state correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt`:
- Around line 97-117: In the `applyFilters()` method, the issue is that
`_filterState` is published before the background computation begins, but the
filtering logic reads the current values of `currentQuery`, `currentSort`, and
`isAscending` from shared state. If `applyFilters()` is called again before the
first coroutine completes, the emitted filter state will not match the actual
filtered results due to the race condition. Create a local immutable snapshot of
`currentQuery`, `currentSort`, and `isAscending` before the `FilterState`
assignment, then use this snapshot both for emitting to `_filterState` and for
the filtering and sorting logic within the `withContext` block to ensure
consistency between the emitted state and the computed results.

---

Duplicate comments:
In
`@app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt`:
- Line 42: The hasAny property in RecentProjectsViewModel is missing a check for
the ascending flag that was previously included. Currently, it only checks if
sort is not null or query is not empty, but it should also evaluate whether the
sort direction is descending (when ascending is false). Add the !ascending
condition to the hasAny property logic using a logical OR operator so that it
properly reflects an active descending sort direction state, which is especially
important since line 67 depends on this value to signal filter state correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 19a52ca4-36f1-4e35-9c61-24b95fab907f

📥 Commits

Reviewing files that changed from the base of the PR and between 9b5943c and 266ac0d.

📒 Files selected for processing (3)
  • app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt
  • app/src/main/res/layout/layout_project_filters_bar.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/res/layout/layout_project_filters_bar.xml
  • app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt

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.

4 participants