Skip to content

Feat/download notification#849

Closed
eddyizm wants to merge 11 commits into
developmentfrom
feat/download-notification
Closed

Feat/download notification#849
eddyizm wants to merge 11 commits into
developmentfrom
feat/download-notification

Conversation

@eddyizm

@eddyizm eddyizm commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added a visible download queue with support for reordering and tracking pending items.
    • Added a shortcut to open the download queue directly from the app.
    • Improved album, playlist, song, and starred-item downloads to better preserve target locations.
  • Bug Fixes

    • Improved download progress, paused, and completion notifications.
    • Added recovery for missing or out-of-sync downloaded files.
    • Made download/export handling more reliable when using a custom download folder.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bd263197-1c28-43ae-ae44-460e50a579da

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/download-notification

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: 4

Caution

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

⚠️ Outside diff range comments (3)
app/src/main/java/com/cappielloantonio/tempo/model/Download.kt (1)

10-26: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Move queuePosition into the primary constructor. @Parcelize only writes constructor properties, so this body field is dropped when Download is sent through a Bundle, and the queue order comes back as 0. app/src/main/java/com/cappielloantonio/tempo/model/Download.kt:24-25

🤖 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/cappielloantonio/tempo/model/Download.kt` around lines
10 - 26, Move the queuePosition property into the Download primary constructor
so `@Parcelize` includes it when the object is passed through a Bundle. Update the
Download class definition to make queuePosition a constructor property alongside
id, playlistId, playlistName, downloadState, and downloadUri, and remove the
separate body field so the saved queue order is preserved.
app/src/main/java/com/cappielloantonio/tempo/util/ExternalAudioWriter.java (1)

109-113: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Three notifyFailure calls bypass the emitNotifications gate.

performDownload gates most notifications on emitNotifications, but the "Invalid media URI" (Line 110), "Unsupported media URI" (Line 160), and "Failed to create file" (Line 222) failures still fire unconditionally. When exportDownloadById invokes performDownload(..., false) to run a silent background export, any of these paths will still surface a "Download failed" notification to the user even though DownloadManager already reported the item as completed — defeating the suppression this PR introduces.

Gate all three on emitNotifications for consistency.

🔧 Proposed gating
-        if (mediaUri == null) {
-            notifyFailure(context, "Invalid media URI.");
+        if (mediaUri == null) {
+            if (emitNotifications) notifyFailure(context, "Invalid media URI.");
             ExternalDownloadMetadataStore.remove(metadataKey);
             return;
         }
             } else {
-                notifyFailure(context, "Unsupported media URI.");
+                if (emitNotifications) notifyFailure(context, "Unsupported media URI.");
                 ExternalDownloadMetadataStore.remove(metadataKey);
                 return;
             }
             targetFile = directory.createFile(mimeType, fileName);
             if (targetFile == null) {
-                notifyFailure(context, "Failed to create file.");
+                if (emitNotifications) notifyFailure(context, "Failed to create file.");
                 return;
             }
🤖 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/cappielloantonio/tempo/util/ExternalAudioWriter.java`
around lines 109 - 113, The `performDownload` flow in `ExternalAudioWriter`
still sends failure notifications even when `emitNotifications` is false. Update
the three unconditional `notifyFailure` paths in `performDownload`—the invalid
media URI check, the unsupported media URI branch, and the file creation failure
branch—to respect the same `emitNotifications` gate used elsewhere, so
`exportDownloadById` can run silently. Keep the cleanup logic
(`ExternalDownloadMetadataStore.remove`, file deletion, etc.) unchanged and only
suppress the user-facing failure notification when notifications are disabled.
app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java (1)

351-356: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear or bind albumLinkChip here
albumLinkChip is still inflated, but nothing resets its default VISIBLE state or click/text handlers. That lets syncAssetLinkGroupVisibility() treat it as visible, so the row can surface with an empty, inert chip when the song/artist chips are absent. Either clear it explicitly or drop the lookup if album links are no longer shown.

🤖 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/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java`
around lines 351 - 356, The album link chip is being inflated in
SongBottomSheetDialog but never bound or hidden, so it can remain visible with
no behavior. In the song details setup where albumLinkChip, artistLinkChip, and
bindAssetLinkChip are used, either explicitly reset albumLinkChip to a
hidden/non-interactive state before calling syncAssetLinkGroupVisibility() or
remove the albumLinkChip lookup entirely if album links are no longer supported.
Make sure the visibility logic only considers chips that are actually bound and
actionable.
🧹 Nitpick comments (2)
app/src/main/java/com/cappielloantonio/tempo/service/DownloaderService.java (2)

156-172: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Hardcoded user-facing notification strings.

Titles/body/action labels ("Downloads Paused", "Waiting for WiFi connection...", "Downloading (%d of %d)", "Pause"/"Resume"/"Done", "%d tracks downloaded successfully", etc.) are inline English literals. The rest of the app uses R.string/plurals, so these won't localize. Recommend extracting to string resources (with plurals where counts are involved).

Also applies to: 205-231, 242-249

🤖 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/cappielloantonio/tempo/service/DownloaderService.java`
around lines 156 - 172, The notification UI in DownloaderService still uses
hardcoded English literals for titles, body text, and action labels, so those
strings will not be localized. Move the user-facing text in the
notification-building logic (including the paused/downloading/preparing messages
and the action labels) into R.string resources, and use plurals for count-based
messages such as completed/downloaded totals. Update the related notification
setup methods in DownloaderService so they reference the resource IDs instead of
inline strings, matching the app’s existing localization pattern.

300-308: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid silently swallowing export failures.

The catch block discards all exceptions with no logging, so a broken export (e.g., store read failure, exportDownloadById error) leaves no trace. Log at warn level to preserve diagnostics while still not disrupting notification flow.

🤖 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/cappielloantonio/tempo/service/DownloaderService.java`
around lines 300 - 308, The catch block in DownloaderService is silently
discarding all exceptions without any logging, making it impossible to diagnose
export failures from ExternalDownloadMetadataStore or ExternalAudioWriter
methods. Add a warn-level log statement in the catch block that includes the
exception details (using the Exception e parameter) to preserve diagnostic
information while maintaining the current behavior of not disrupting
notification handling.
🤖 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/cappielloantonio/tempo/repository/DownloadRepository.java`:
- Around line 212-222: Make queue-position assignment atomic in
DownloadRepository by removing the separate read-then-add flow around
getMaxQueuePositionSync(); the current MAX(queue_position) lookup in
GetMaxQueuePositionThreadSafe can race when multiple callers later add 1 and
insert. Update the download creation path so the max-read and insert happen
together in one transaction or use a single SQL statement/DAO method that
returns and persists the next queue position in one step, and route callers to
that atomic path instead of relying on getMaxQueuePositionSync().

In `@app/src/main/java/com/cappielloantonio/tempo/service/DownloaderManager.java`:
- Around line 76-104: The enqueue path in DownloaderManager.download(...) is
doing synchronous repository lookups for the max queue position on the caller
thread, which can block UI interactions. Move queue-position assignment into the
existing background insert flow used by insertDatabase(...) or the download-list
processing path, and keep only lightweight state setup in download(...). Use the
DownloaderManager.download(MediaItem, Download, Integer) and
download(List<MediaItem>, List<Download>) methods as the places to reroute this
work so the queue position is resolved off the main thread.

In `@app/src/main/java/com/cappielloantonio/tempo/service/DownloaderService.java`:
- Around line 233-250: The “Done” action in DownloaderService’s completion
notification is a no-op because the PendingIntent uses an empty Intent with no
receiver, so tapping the action will not dismiss the notification. Update the
dismiss flow in the notification builder to either remove the action entirely or
route it through a real broadcast receiver that explicitly cancels the
completion notification via
NotificationManager.cancel(COMPLETION_NOTIFICATION_ID). Locate the notification
setup in DownloaderService around the builder/addAction usage and make the
action actually close the completion notification.

In
`@app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java`:
- Around line 150-154: The count == 0 path in DownloadFragment’s refresh logic
is recursively re-entering the same refresh flow through
reconcileMissingFiles(), which can keep scheduling background work on the common
no-changes case. Remove that recursive call from reconcileMissingFiles() or
change the helper to perform its file check without calling
refreshExternalDownloads() again, and keep the no-changes toast path in the
existing refresh branch.

---

Outside diff comments:
In `@app/src/main/java/com/cappielloantonio/tempo/model/Download.kt`:
- Around line 10-26: Move the queuePosition property into the Download primary
constructor so `@Parcelize` includes it when the object is passed through a
Bundle. Update the Download class definition to make queuePosition a constructor
property alongside id, playlistId, playlistName, downloadState, and downloadUri,
and remove the separate body field so the saved queue order is preserved.

In
`@app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java`:
- Around line 351-356: The album link chip is being inflated in
SongBottomSheetDialog but never bound or hidden, so it can remain visible with
no behavior. In the song details setup where albumLinkChip, artistLinkChip, and
bindAssetLinkChip are used, either explicitly reset albumLinkChip to a
hidden/non-interactive state before calling syncAssetLinkGroupVisibility() or
remove the albumLinkChip lookup entirely if album links are no longer supported.
Make sure the visibility logic only considers chips that are actually bound and
actionable.

In `@app/src/main/java/com/cappielloantonio/tempo/util/ExternalAudioWriter.java`:
- Around line 109-113: The `performDownload` flow in `ExternalAudioWriter` still
sends failure notifications even when `emitNotifications` is false. Update the
three unconditional `notifyFailure` paths in `performDownload`—the invalid media
URI check, the unsupported media URI branch, and the file creation failure
branch—to respect the same `emitNotifications` gate used elsewhere, so
`exportDownloadById` can run silently. Keep the cleanup logic
(`ExternalDownloadMetadataStore.remove`, file deletion, etc.) unchanged and only
suppress the user-facing failure notification when notifications are disabled.

---

Nitpick comments:
In `@app/src/main/java/com/cappielloantonio/tempo/service/DownloaderService.java`:
- Around line 156-172: The notification UI in DownloaderService still uses
hardcoded English literals for titles, body text, and action labels, so those
strings will not be localized. Move the user-facing text in the
notification-building logic (including the paused/downloading/preparing messages
and the action labels) into R.string resources, and use plurals for count-based
messages such as completed/downloaded totals. Update the related notification
setup methods in DownloaderService so they reference the resource IDs instead of
inline strings, matching the app’s existing localization pattern.
- Around line 300-308: The catch block in DownloaderService is silently
discarding all exceptions without any logging, making it impossible to diagnose
export failures from ExternalDownloadMetadataStore or ExternalAudioWriter
methods. Add a warn-level log statement in the catch block that includes the
exception details (using the Exception e parameter) to preserve diagnostic
information while maintaining the current behavior of not disrupting
notification handling.
🪄 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 Plus

Run ID: 6f206388-e22a-4100-ac88-88326f79c9fc

📥 Commits

Reviewing files that changed from the base of the PR and between d56d042 and 97311d9.

📒 Files selected for processing (21)
  • .gitignore
  • app/schemas/com.cappielloantonio.tempo.database.AppDatabase/20.json
  • app/schemas/com.cappielloantonio.tempo.database.AppDatabase/21.json
  • app/src/main/java/com/cappielloantonio/tempo/database/dao/DownloadDao.java
  • app/src/main/java/com/cappielloantonio/tempo/model/Download.kt
  • app/src/main/java/com/cappielloantonio/tempo/repository/DownloadRepository.java
  • app/src/main/java/com/cappielloantonio/tempo/service/DownloaderManager.java
  • app/src/main/java/com/cappielloantonio/tempo/service/DownloaderService.java
  • app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java
  • app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java
  • app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DirectoryFragment.java
  • app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java
  • app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java
  • app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java
  • app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java
  • app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java
  • app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java
  • app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java
  • app/src/main/java/com/cappielloantonio/tempo/util/Constants.kt
  • app/src/main/java/com/cappielloantonio/tempo/util/ExternalAudioWriter.java
  • app/src/main/java/com/cappielloantonio/tempo/util/ExternalDownloadMetadataStore.java

Comment thread app/src/main/java/com/cappielloantonio/tempo/repository/DownloadRepository.java Outdated
Comment thread app/src/main/java/com/cappielloantonio/tempo/service/DownloaderManager.java Outdated
Comment thread app/src/main/java/com/cappielloantonio/tempo/service/DownloaderService.java Outdated
@eddyizm eddyizm closed this Jul 18, 2026
@eddyizm
eddyizm deleted the feat/download-notification branch July 18, 2026 00:45
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