Skip to content

feat: add album download button#115

Open
rshackleton wants to merge 1 commit intodddevid:masterfrom
rshackleton:feature/105-download-album-button
Open

feat: add album download button#115
rshackleton wants to merge 1 commit intodddevid:masterfrom
rshackleton:feature/105-download-album-button

Conversation

@rshackleton
Copy link
Copy Markdown

@rshackleton rshackleton commented Mar 31, 2026

Implements #105

The implementation was largely copied from the playlist screen.

Future<void> _downloadPlaylist() async {

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

This PR adds album download functionality to the album screen. It introduces background download capability using OfflineService, tracks download state with _isDownloading, and conditionally displays a download button in the app bar that's only available in online mode.

Changes

Cohort / File(s) Summary
Album Download Feature
lib/screens/album_screen.dart
Added _isDownloading state variable and _downloadAlbum() method to initialize OfflineService, start background downloads, and manage download feedback via snackbars. Updated SliverAppBar to conditionally show a download button (disabled during download) only when not in offline mode, with visibility driven by AuthProvider.state.

Sequence Diagram

sequenceDiagram
    actor User
    participant AlbumScreen
    participant AuthProvider
    participant OfflineService
    participant DownloadBackend as Background Download

    User->>AlbumScreen: Tap download button
    AlbumScreen->>AuthProvider: Check isOffline state
    AuthProvider-->>AlbumScreen: Not in offline mode
    AlbumScreen->>AlbumScreen: Set _isDownloading = true
    AlbumScreen->>OfflineService: Initialize service
    OfflineService-->>AlbumScreen: Ready
    AlbumScreen->>DownloadBackend: startBackgroundDownload(_songs, subsonicService)
    AlbumScreen->>AlbumScreen: Show "downloading in background" SnackBar
    DownloadBackend->>DownloadBackend: Download in progress...
    DownloadBackend-->>AlbumScreen: Download complete (Future resolves)
    AlbumScreen->>AlbumScreen: Set _isDownloading = false
    AlbumScreen->>User: Show success SnackBar
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Poem

🐰 A button for downloads so fleet,
Background tasks, oh how neat!
Albums cached when offline we go,
No more waiting for songs to load slow!
Progress spins round, then snackbars appear,
Digital music forever right here! 📀✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add album download button' directly summarizes the main change: adding a download button feature for albums in the UI.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/screens/album_screen.dart`:
- Around line 104-115: Replace the hardcoded English SnackBar and Text strings
with localized strings from AppLocalizations: use AppLocalizations.of(context)!
to build the messages that include dynamic values (_songs.length and
_album!.name) and update the SnackBar content in the ScaffoldMessenger/ SnackBar
code path (the block that constructs 'Downloaded ${_songs.length} songs from
${_album!.name}' and the 'Downloading ${_songs.length} songs in background…'
message). Ensure you add or use appropriate keys in AppLocalizations (e.g.,
downloadedSongs and downloadingInBackground) and format them with the dynamic
values so both SnackBars and any other hardcoded string at the referenced
location (line ~253) are localized.
- Around line 98-108: The success snackbar is unconditional because
offlineService.startBackgroundDownload(_songs, subsonicService) currently
completes even when some downloads fail; change startBackgroundDownload to
return a result (e.g., numberDownloaded, list of failed items, or a success
flag) or throw on fatal error, then await that result in album_screen and use it
to set _isDownloading and craft the SnackBar message: reference
offlineService.startBackgroundDownload, _songs and _album to compute the actual
downloaded count and/or report failures, and only show the "Downloaded X songs"
message when the returned result confirms X > 0 (otherwise show a
partial/failure message).
- Around line 201-203: The current build uses Provider.of<AuthProvider>(context,
listen: false) to set isOffline which prevents UI updates when auth changes and
only checks for AuthState.offlineMode; change both usages (the isOffline
declaration and the other check around the download button) to use
context.watch<AuthProvider>() so the widget rebuilds reactively and replace the
gating logic so the download UI/action is allowed only when
watch<AuthProvider>().state == AuthState.authenticated, denying it for
unauthenticated/error/serverUnreachable/offline states.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9850fc47-d2de-4d93-af3a-0e92139a821e

📥 Commits

Reviewing files that changed from the base of the PR and between 4e99337 and 3c66f13.

📒 Files selected for processing (1)
  • lib/screens/album_screen.dart

Comment on lines +98 to +108
offlineService.startBackgroundDownload(_songs, subsonicService).then((_) {
if (mounted) {
setState(() => _isDownloading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Downloaded ${_songs.length} songs from ${_album!.name}',
),
duration: const Duration(seconds: 3),
),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid unconditional success messaging after completion.

At Line 104, the snackbar claims all songs were downloaded, but OfflineService.startBackgroundDownload() absorbs failures and still completes normally. This can report success on partial/failed downloads.

Suggested adjustment in this file (safe interim)
-            content: Text(
-              'Downloaded ${_songs.length} songs from ${_album!.name}',
-            ),
+            content: Text(
+              'Album download finished. Some songs may have failed.',
+            ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
offlineService.startBackgroundDownload(_songs, subsonicService).then((_) {
if (mounted) {
setState(() => _isDownloading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Downloaded ${_songs.length} songs from ${_album!.name}',
),
duration: const Duration(seconds: 3),
),
);
offlineService.startBackgroundDownload(_songs, subsonicService).then((_) {
if (mounted) {
setState(() => _isDownloading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Album download finished. Some songs may have failed.',
),
duration: const Duration(seconds: 3),
),
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/screens/album_screen.dart` around lines 98 - 108, The success snackbar is
unconditional because offlineService.startBackgroundDownload(_songs,
subsonicService) currently completes even when some downloads fail; change
startBackgroundDownload to return a result (e.g., numberDownloaded, list of
failed items, or a success flag) or throw on fatal error, then await that result
in album_screen and use it to set _isDownloading and craft the SnackBar message:
reference offlineService.startBackgroundDownload, _songs and _album to compute
the actual downloaded count and/or report failures, and only show the
"Downloaded X songs" message when the returned result confirms X > 0 (otherwise
show a partial/failure message).

Comment on lines +104 to +115
'Downloaded ${_songs.length} songs from ${_album!.name}',
),
duration: const Duration(seconds: 3),
),
);
}
});

if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Downloading ${_songs.length} songs in background…'),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Localize newly added UI strings.

At Lines 104, 115, and 253, strings are hardcoded in English. This bypasses AppLocalizations used elsewhere in this screen.

Also applies to: 253-253

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/screens/album_screen.dart` around lines 104 - 115, Replace the hardcoded
English SnackBar and Text strings with localized strings from AppLocalizations:
use AppLocalizations.of(context)! to build the messages that include dynamic
values (_songs.length and _album!.name) and update the SnackBar content in the
ScaffoldMessenger/ SnackBar code path (the block that constructs 'Downloaded
${_songs.length} songs from ${_album!.name}' and the 'Downloading
${_songs.length} songs in background…' message). Ensure you add or use
appropriate keys in AppLocalizations (e.g., downloadedSongs and
downloadingInBackground) and format them with the dynamic values so both
SnackBars and any other hardcoded string at the referenced location (line ~253)
are localized.

Comment on lines +201 to +203
final isOffline =
Provider.of<AuthProvider>(context, listen: false).state ==
AuthState.offlineMode;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "album_screen.dart" | head -5

Repository: dddevid/Musly

Length of output: 88


🏁 Script executed:

find . -type f -name "auth_provider.dart" | head -5

Repository: dddevid/Musly

Length of output: 91


🏁 Script executed:

cat -n lib/screens/album_screen.dart | sed -n '195,260p'

Repository: dddevid/Musly

Length of output: 2968


🏁 Script executed:

cat -n lib/providers/auth_provider.dart

Repository: dddevid/Musly

Length of output: 11420


Make auth gating reactive and restrict download to authenticated state.

At line 202, using listen: false in build makes this value stale if auth state changes. Also, hiding only for offlineMode is too permissive—the download button currently shows in unsafe states like unauthenticated, error, and serverUnreachable. Only authenticated users should be able to download.

Replace with reactive pattern using context.watch<AuthProvider>() and gate the button to AuthState.authenticated only.

Also applies to: 251

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/screens/album_screen.dart` around lines 201 - 203, The current build uses
Provider.of<AuthProvider>(context, listen: false) to set isOffline which
prevents UI updates when auth changes and only checks for AuthState.offlineMode;
change both usages (the isOffline declaration and the other check around the
download button) to use context.watch<AuthProvider>() so the widget rebuilds
reactively and replace the gating logic so the download UI/action is allowed
only when watch<AuthProvider>().state == AuthState.authenticated, denying it for
unauthenticated/error/serverUnreachable/offline states.

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.

1 participant