Skip to content

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

Merged
Daniel-ADFA merged 4 commits into
stagefrom
ADFA-4386
Jun 26, 2026
Merged

ADFA-4386: Show active filter chips and indicator dot on Recent Projects#1427
Daniel-ADFA merged 4 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a5a0c2d3-4250-4ade-8091-eca202247d17

📥 Commits

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

📒 Files selected for processing (1)
  • resources/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (1)
  • resources/src/main/res/values/strings.xml

📝 Walkthrough

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

  • Added an “active filters” indicator dot on the Recent Projects sort button, which becomes visible whenever sorting and/or search is active.
  • Updated the Recent Projects filters bar layout to include:
    • a vertical container
    • an always-ready horizontal scroll chip row for active filters
    • a dot view (filters_active_dot) anchored to the sort button
  • Implemented removable “active filter” chips (rendered from ViewModel state) for:
    • active sort (including ↑/↓ direction)
    • active search query
  • Implemented chip interactions:
    • Sort chip opens the filters sheet; closing it calls viewModel.clearSort().
    • Search chip focuses the search input; closing it clears the search EditText.
  • Kept the chips and indicator dot synchronized with RecentProjectsViewModel.filterState (query, sort, ascending) and dismissed the filters sheet via viewModel.filterEvents (using a stored BottomSheetDialog reference).
  • Animated filter bar updates using TransitionManager.beginDelayedTransition(..., AutoTransition().setDuration(180)).
  • Added resources:
    • layout_project_filters_bar.xml (active dot + chip scroll container)
    • chip_active_filter.xml (Material3 removable chip w/ 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() starts collecting filterState and filterEvents with viewLifecycleScope.launch but without repeatOnLifecycle(...), which can keep collectors active beyond the STARTED lifecycle state.
  • Transition allocation: beginFilterBarTransition(...) creates a new AutoTransition() for each update.
  • Localization/typography: the search chip text is rendered with curly quotes (“${state.query}”), which may be undesirable for some locales/typographic conventions.
  • Accessibility: the active dot is explicitly set to android:importantForAccessibility="no"—confirm this matches the intended screen-reader experience.

Walkthrough

Adds active filter chip rendering to the recent projects screen, backed by a new FilterState flow in the view model. The filter bar layout, chip resources, and fragment logic were updated to show active sort and search chips, an active dot, and synchronized bottom-sheet dismissal.

Changes

Active Filters Chip Bar

Layer / File(s) Summary
Filter state model and synchronization
app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt
Introduces FilterState, exposes filterState: StateFlow<FilterState>, updates applyFilters() to publish current filter inputs, changes active-filter detection, and adds clearSort() to reset sort state.
Filter bar layout and chip 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 the filter bar layout, adds the active-filters dot and scrollable chip container, and introduces the chip, drawable, and string resources used by the active filter UI.
Fragment chip rendering and dismissal
app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt
Collects filterState, builds removable sort/search chips, animates chip updates, stores the filters dialog for dismiss synchronization, and refactors sort label mapping into labelRes().

Sequence Diagram(s)

sequenceDiagram
  participant RecentProjectsFragment
  participant RecentProjectsViewModel
  participant ChipGroup
  participant TransitionManager
  RecentProjectsFragment->>RecentProjectsViewModel: collect filterState
  RecentProjectsViewModel-->>RecentProjectsFragment: FilterState
  RecentProjectsFragment->>TransitionManager: beginDelayedTransition(AutoTransition)
  RecentProjectsFragment->>ChipGroup: clear and rebuild chips
  RecentProjectsFragment->>ChipGroup: add sort chip / search chip
  RecentProjectsFragment->>RecentProjectsViewModel: clearSort() or onSearchQuery("")
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • appdevforall/CodeOnTheGo#700: Shares the recent-projects filtering path and applyFilters()/sort-query state management that this PR builds on.

Suggested reviewers

  • itsaky-adfa
  • dara-abijo-adfa
  • jatezzz
  • hal-eisen-adfa

Poem

🐇 A chip for the sort, a chip for the search,
Tiny dots now gleam on their perch.
The filter bar scrolls, neat and bright,
With close-icon taps that feel just right.
Hop, hop—filters dance in view ✨

🚥 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, so there is nothing meaningful to evaluate beyond the code changes. Add a short description of the feature and its UI/state changes so reviewers can understand the intent quickly.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: active filter chips and the indicator dot on Recent Projects.
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.
✨ Finishing Touches
🧪 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

@Daniel-ADFA Daniel-ADFA merged commit e812858 into stage Jun 26, 2026
2 checks passed
@Daniel-ADFA Daniel-ADFA deleted the ADFA-4386 branch June 26, 2026 16:58
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