Skip to content

Conversation

@baiyuanneko
Copy link
Contributor

@baiyuanneko baiyuanneko commented Oct 7, 2025

This pull request added a scroll bar to the track list page, which is convenient in many situation. For example, if one just want to manually randomly select a track to listen.

  1. All three type of screens / layouts are supported and tested.
  2. The scroll bar has correct total count (or progress) initially. This is done by getting the correct total count when enter the track list page, and set the correct scroll bar status.
  3. Lazy loading still remains. For example, track list page only loads first page at first time, if user scroll to the end, the last page starts to get loaded.

Summary by Sourcery

Add scrollbars to all track list screens with an accurate total count by querying a new media files count endpoint and refactor away from infinite_scroll_pagination in favor of manual lazy-loading builders

New Features:

  • Add visible Scrollbar to track lists on large, small, and band screen layouts
  • Introduce new GetMediaFilesCount API endpoint and Dart wrapper to fetch the total number of media files

Enhancements:

  • Replace infinite_scroll_pagination with custom lazy-loading using ListView/GridView.builder and manual state management
  • Ensure scrollbars reflect the correct total item count and maintain lazy loading as the user scrolls

@sourcery-ai
Copy link

sourcery-ai bot commented Oct 7, 2025

Reviewer's Guide

This PR refactors all track list screens to remove the previous infinite_scroll_pagination dependency and replace it with a manual lazy-loading mechanism driven by a totalCount/getItem interface wrapped in Scrollbar widgets, and adds a new backend signal and Dart utility to fetch the total number of media files for correct scroll positioning.

Sequence diagram for manual lazy loading and scroll bar initialization

sequenceDiagram
  actor User
  participant UI
  participant Backend
  participant Database
  User->>UI: Open track list page
  UI->>Backend: Request total track count
  Backend->>Database: Query media files count
  Database-->>Backend: Return count
  Backend-->>UI: Return total count
  UI->>UI: Display scroll bar with correct total count
  UI->>Backend: Request first page of tracks
  Backend->>Database: Query first page of tracks
  Database-->>Backend: Return tracks
  Backend-->>UI: Return tracks
  UI->>UI: Display first page of tracks
  User->>UI: Scrolls to end of list
  UI->>Backend: Request next page of tracks
  Backend->>Database: Query next page of tracks
  Database-->>Backend: Return tracks
  Backend-->>UI: Return tracks
  UI->>UI: Display next page of tracks
Loading

File-Level Changes

Change Details Files
Refactor track list widgets to use Scrollbar and manual lazy loading instead of PagingController
  • Removed infinite_scroll_pagination imports and PagingController usage
  • Added totalCount and getItem parameters to all track list widgets
  • Replaced PagedListView/PagedGridView with Scrollbar + ListView/GridView.builder
  • Updated reloadData callbacks to no-ops and ProgressRing placeholders for loading items
lib/widgets/track_list/large_screen_track_list.dart
lib/widgets/track_list/small_screen_track_list.dart
lib/widgets/track_list/band_screen_track_list.dart
lib/screens/query_tracks/query_tracks_list.dart
lib/screens/tracks/track_list.dart
Implement custom state management and lazy loading logic in QueryTrackListView and TrackListView
  • Removed PagingController and its page listeners
  • Introduced _loadedItems, _loadingIndices, _checkAndLoadItem, _getItem and _loadPage methods
  • Fetch initial totalCount on init and show ProgressRing until ready
  • Schedule setState via SchedulerBinding post-frame callbacks
lib/screens/query_tracks/query_tracks_list.dart
lib/screens/tracks/track_list.dart
Add backend API for total media file count and Dart binding
  • Defined GetMediaFilesCountRequest/Response in native messages and handlers
  • Implemented get_media_files_count DB action and signal handler
  • Registered new request type in native requests macro
  • Added Dart utility getMediaFilesCount() to call the new Rust API
native/hub/src/handlers/media_file.rs
native/hub/src/messages/media_file.rs
native/requests/src/lib.rs
database/src/actions/file.rs
lib/utils/api/get_media_files_count.dart

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • Extract the custom lazy‐loading and scroll logic into a shared controller or widget to avoid duplicating pagination code across different screen layouts.
  • Avoid using an arbitrary fallback totalCount (e.g. 10000) when the count fetch fails—consider surfacing an error state or using a clearer default strategy.
  • Review the use of SchedulerBinding.addPostFrameCallback for state updates—batch your setState calls where possible to simplify the update flow and reduce scheduling overhead.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Extract the custom lazy‐loading and scroll logic into a shared controller or widget to avoid duplicating pagination code across different screen layouts.
- Avoid using an arbitrary fallback totalCount (e.g. 10000) when the count fetch fails—consider surfacing an error state or using a clearer default strategy.
- Review the use of SchedulerBinding.addPostFrameCallback for state updates—batch your setState calls where possible to simplify the update flow and reduce scheduling overhead.

## Individual Comments

### Comment 1
<location> `lib/widgets/track_list/small_screen_track_list.dart:101-103` </location>
<code_context>
               ),
-            ),
+              // Add extra bottom spacing for the last item
+              if (isLastItem)
+                SizedBox(height: MediaQuery.of(context).size.width / 3 + 20),
+            ],
           );
</code_context>

<issue_to_address>
**suggestion:** Adding extra bottom spacing based on screen width may not scale well on all devices.

The current calculation may lead to inconsistent padding across devices. Consider using a fixed value, basing it on screen height, or adjusting it according to content layout for better responsiveness.

```suggestion
              // Add extra bottom spacing for the last item
              if (isLastItem)
                SizedBox(height: MediaQuery.of(context).size.height * 0.05),
```
</issue_to_address>

### Comment 2
<location> `lib/widgets/track_list/large_screen_track_list.dart:85-86` </location>
<code_context>
+      itemBuilder: (context, index) {
+        final item = getItem(index);
+
+        if (item == null) {
+          return const SizedBox(
+            width: 64,
</code_context>

<issue_to_address>
**suggestion:** Displaying a ProgressRing for null items may result in multiple spinners in the grid.

Consider using a single loading indicator for the grid or a skeleton loader for individual items to avoid multiple spinners appearing simultaneously.
</issue_to_address>

### Comment 3
<location> `lib/widgets/track_list/band_screen_track_list.dart:92-93` </location>
<code_context>
+      itemBuilder: (context, index) {
+        final item = getItem(index);
+
+        if (item == null) {
+          return const SizedBox(
+            width: 64,
+            height: 64,
</code_context>

<issue_to_address>
**suggestion:** Returning a fixed-size loading indicator may cause layout jumps if items load at different rates.

Consider using a placeholder that matches the final item's dimensions to prevent layout shifts when content loads.

Suggested implementation:

```
        if (item == null) {
          // Use the same dimensions as BandViewTrackItem to prevent layout shifts.
          return const SizedBox(
            width: BandViewTrackItem.width,
            height: BandViewTrackItem.height,
            child: Center(child: ProgressRing()),
          );
        }

```

- If `BandViewTrackItem.width` and `BandViewTrackItem.height` are not defined as static constants, you should define them in the `BandViewTrackItem` class and use those values here.
- If the dimensions are dynamic or depend on the context, you may need to refactor both the placeholder and the item widget to use a shared method or constant for sizing.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +101 to +103
// Add extra bottom spacing for the last item
if (isLastItem)
SizedBox(height: MediaQuery.of(context).size.width / 3 + 20),
Copy link

Choose a reason for hiding this comment

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

suggestion: Adding extra bottom spacing based on screen width may not scale well on all devices.

The current calculation may lead to inconsistent padding across devices. Consider using a fixed value, basing it on screen height, or adjusting it according to content layout for better responsiveness.

Suggested change
// Add extra bottom spacing for the last item
if (isLastItem)
SizedBox(height: MediaQuery.of(context).size.width / 3 + 20),
// Add extra bottom spacing for the last item
if (isLastItem)
SizedBox(height: MediaQuery.of(context).size.height * 0.05),

Comment on lines +85 to +86
if (item == null) {
return const Center(child: ProgressRing());
Copy link

Choose a reason for hiding this comment

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

suggestion: Displaying a ProgressRing for null items may result in multiple spinners in the grid.

Consider using a single loading indicator for the grid or a skeleton loader for individual items to avoid multiple spinners appearing simultaneously.

Comment on lines +92 to +93
if (item == null) {
return const SizedBox(
Copy link

Choose a reason for hiding this comment

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

suggestion: Returning a fixed-size loading indicator may cause layout jumps if items load at different rates.

Consider using a placeholder that matches the final item's dimensions to prevent layout shifts when content loads.

Suggested implementation:

        if (item == null) {
          // Use the same dimensions as BandViewTrackItem to prevent layout shifts.
          return const SizedBox(
            width: BandViewTrackItem.width,
            height: BandViewTrackItem.height,
            child: Center(child: ProgressRing()),
          );
        }

  • If BandViewTrackItem.width and BandViewTrackItem.height are not defined as static constants, you should define them in the BandViewTrackItem class and use those values here.
  • If the dimensions are dynamic or depend on the context, you may need to refactor both the placeholder and the item widget to use a shared method or constant for sizing.

@dosubot dosubot bot added the C - UI UI related issue label Oct 7, 2025
@Losses
Copy link
Owner

Losses commented Oct 7, 2025

Hi, I noticed that you are still editing the pull request, let me know when you are ready for the review, thanks!

@baiyuanneko
Copy link
Contributor Author

Hi, I noticed that you are still editing the pull request, let me know when you are ready for the review, thanks!

Let me attempt to fix the analyze problems first, thanks! When I complete I will reply you again. @Losses

@baiyuanneko
Copy link
Contributor Author

Now I've completed my fix. Seems that rust analyze fails mainly due to other files, so maybe it is required to ignore its result currently. You can review the code now. Thanks! @Losses

@Losses
Copy link
Owner

Losses commented Oct 9, 2025

Now I've completed my fix. Seems that rust analyze fails mainly due to other files, so maybe it is required to ignore its result currently. You can review the code now. Thanks! @Losses

I'll merge it first and tweak it later, thank you for the contribution!

@Losses Losses merged commit 12eee83 into Losses:master Oct 9, 2025
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C - UI UI related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants