Skip to content

feat: Add download list in the App#20

Open
syed-tp wants to merge 2 commits intomainfrom
app/download_list
Open

feat: Add download list in the App#20
syed-tp wants to merge 2 commits intomainfrom
app/download_list

Conversation

@syed-tp
Copy link
Contributor

@syed-tp syed-tp commented Jun 17, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a Downloads screen for viewing and managing media downloads.
    • Added a button on the main screen to access the Downloads section.
    • Enabled users to pause, resume, or delete downloads directly from the Downloads screen.
    • Displayed download progress, status, and asset details for each item in a user-friendly list.
  • User Interface

    • Added new layouts for the Downloads screen and individual download items.
    • Included visual indicators for loading state and empty list messages.
  • Localization

    • Added new string resources for download-related actions and messages.

@codetortoiseai
Copy link

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jun 17, 2025

Walkthrough

This change introduces a comprehensive downloads management feature to the Android app. It adds a new DownloadsActivity, associated layouts, a ViewModel for download state management, a RecyclerView adapter, click handling, and relevant string resources. The main activity is updated to provide access to the downloads screen, and player initialization parameters are updated.

Changes

File(s) Change Summary
AndroidManifest.xml Declared new DownloadsActivity with appropriate attributes.
.../DownloadAdapter.kt, .../RecyclerItemClickListener.kt, .../DownloadViewModel.kt Added DownloadAdapter (RecyclerView adapter), RecyclerItemClickListener, and DownloadViewModel for downloads logic.
.../DownloadsActivity.kt Added DownloadsActivity to display and manage downloads with toolbar, RecyclerView, and popup menu actions.
.../MainActivity.kt Updated SDK key, imported Intent, and added navigation to DownloadsActivity via downloads button.
.../PlayerUIViewModel.kt Updated asset ID and access token for TPStreamsPlayer initialization.
res/layout/activity_downloads.xml, res/layout/item_download.xml Added new layouts for downloads screen and individual download items.
res/layout/activity_main.xml Added "View Downloads" button to main activity layout.
res/values/strings.xml Added string resources for downloads UI, actions, and statuses.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MainActivity
    participant DownloadsActivity
    participant DownloadViewModel
    participant DownloadAdapter
    participant DownloadTracker

    User->>MainActivity: Clicks "View Downloads"
    MainActivity->>DownloadsActivity: Starts activity
    DownloadsActivity->>DownloadViewModel: Observes downloads LiveData
    DownloadViewModel->>DownloadTracker: Registers listener, loads downloads
    DownloadTracker-->>DownloadViewModel: Notifies on downloads update
    DownloadViewModel-->>DownloadsActivity: Updates downloads LiveData
    DownloadsActivity->>DownloadAdapter: Submits new list
    User->>DownloadsActivity: Clicks on a download item
    DownloadsActivity->>DownloadAdapter: Gets item state
    DownloadsActivity->>User: Shows popup menu (Pause/Resume/Delete/Cancel)
    User->>DownloadsActivity: Selects action
    DownloadsActivity->>DownloadViewModel: Calls pause/resume/remove
    DownloadViewModel->>DownloadTracker: Performs action
    DownloadTracker-->>DownloadViewModel: Notifies on state change
Loading

Possibly related PRs

Suggested reviewers

  • bharathwaaj

Poem

In the meadow of code, a new path unfurls,
Downloads now gathered in tidy little swirls.
With buttons and lists, and a toolbar so bright,
Rabbits can pause, resume, or delete with delight.
So hop to your downloads, see progress anew—
This patch brings a garden of features for you!
🐇📥✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai
Copy link

coderabbitai bot commented Jun 17, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

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

🧹 Nitpick comments (11)
app/src/main/java/com/tpstreams/player/MainActivity.kt (1)

26-26: Move SDK initialisation to an Application class

TPStreamsPlayer.init("9q94nm") is invoked every time MainActivity is recreated (rotation, multi-window, process restart).
Initialise once in a custom Application subclass instead:

class TPStreamsApp : Application() {
    override fun onCreate() {
        super.onCreate()
        TPStreamsPlayer.init("9q94nm")
    }
}

Don’t forget to register it in the manifest.

app/src/main/res/layout/activity_main.xml (1)

19-27: Add accessibility and style attributes to the new button

For consistency and accessibility:

 android:id="@+id/downloads_button"
+style="?attr/buttonStyle"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="@string/view_downloads"
+android:contentDescription="@string/view_downloads"

This reuses the app’s default button style and provides an explicit content description for screen-reader users.

app/src/main/AndroidManifest.xml (1)

28-31: Use string resources & add legacy parent meta-data

Hard-coding "Downloads" breaks localisation and lints. Also, for API < 17 add the legacy parent meta-data tag.

-<activity
-    android:name=".DownloadsActivity"
-    android:exported="false"
-    android:label="Downloads"
-    android:parentActivityName=".MainActivity" />
+<activity
+    android:name=".DownloadsActivity"
+    android:exported="false"
+    android:label="@string/title_downloads"
+    android:parentActivityName=".MainActivity">
+    <meta-data
+        android:name="android.support.PARENT_ACTIVITY"
+        android:value=".MainActivity" />
+</activity>
app/src/main/res/values/strings.xml (1)

19-19: Explicitly declare the placeholder format for download_size

Add format="string" to make lint happy and avoid runtime formatting issues with non-string values.

-    <string name="download_size">Size: %1$s</string>
+    <string name="download_size" format="string">Size: %1$s</string>
app/src/main/res/layout/item_download.xml (1)

38-47: Set an explicit max value on the progress bar

Relying on the default max=100 is brittle; being explicit improves readability and prevents surprises if the default ever changes.

-        <ProgressBar
+        <ProgressBar
             android:id="@+id/download_progress"
             style="?android:attr/progressBarStyleHorizontal"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginTop="8dp"
+            android:max="100"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/asset_id_text"
             tools:progress="65" />
app/src/main/res/layout/activity_downloads.xml (1)

31-51: Potential overlap between empty view and progress bar

Both empty_view and progress_bar are centered below the toolbar and could occupy the same space leading to visual flicker when toggling visibility.
Consider putting them in a small wrapper FrameLayout (or mutually exclusive ViewSwitcher) so only one child is ever visible at a time.

app/src/main/java/com/tpstreams/player/DownloadsActivity.kt (1)

45-53: Duplicate empty-state checks

checkEmptyState() is called both here and from the adapter observer. One is sufficient; keeping both is redundant and makes the code harder to reason about.

app/src/main/java/com/tpstreams/player/RecyclerItemClickListener.kt (1)

37-44: Return false after delegating to keep RecyclerView scroll behaviour intact

Returning true from onInterceptTouchEvent consumes the touch event, which can block RecyclerView scrolling if the tap is slightly dragged.
Change the logic to:

if (childView != null && listener != null && gestureDetector.onTouchEvent(e)) {
    listener.onItemClick(childView, view.getChildAdapterPosition(childView))
}
return false

This lets the click propagate while still notifying the listener.

app/src/main/java/com/tpstreams/player/DownloadAdapter.kt (3)

34-35: Hard-coded UI strings break localisation & accessibility

Strings such as "Asset ID: " and the state literals are embedded directly in code.
Move them into strings.xml and fetch via itemView.context.getString(...) to enable i18n/RTL, testing, and easy copy editing.

-assetIdText.text = "Asset ID: ${downloadItem.assetId}"
+assetIdText.text = itemView.context.getString(
+    R.string.download_asset_id_format,
+    downloadItem.assetId
+)

Do the same for each state in getStateString, e.g.:

-Download.STATE_COMPLETED -> "COMPLETED"
+Download.STATE_COMPLETED -> context.getString(R.string.state_completed)

Also applies to: 50-59


14-23: Enable stable IDs for smoother list animations

ListAdapter diffing is optimal, but without stable IDs the layout system must re-bind on every change, causing flickers during rapid progress updates.

class DownloadAdapter : ListAdapter<DownloadItem, DownloadAdapter.DownloadViewHolder>(
    DownloadDiffCallback()
) {
+    init { setHasStableIds(true) }
+
+    override fun getItemId(position: Int): Long =
+        getItem(position).assetId.hashCode().toLong()

49-60: Avoid allocating strings repeatedly in getStateString

Mapping every bind call creates new String instances.
Return resource IDs and resolve once, or cache an immutable map.

Consider:

private val stateLabels = mapOf(
    Download.STATE_COMPLETED  to R.string.state_completed,
    ...
)

@StringRes
private fun stateRes(state: Int) = stateLabels[state] ?: R.string.state_unknown
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0608c76 and 29e23bb.

📒 Files selected for processing (11)
  • app/src/main/AndroidManifest.xml (1 hunks)
  • app/src/main/java/com/tpstreams/player/DownloadAdapter.kt (1 hunks)
  • app/src/main/java/com/tpstreams/player/DownloadViewModel.kt (1 hunks)
  • app/src/main/java/com/tpstreams/player/DownloadsActivity.kt (1 hunks)
  • app/src/main/java/com/tpstreams/player/MainActivity.kt (2 hunks)
  • app/src/main/java/com/tpstreams/player/PlayerUIViewModel.kt (1 hunks)
  • app/src/main/java/com/tpstreams/player/RecyclerItemClickListener.kt (1 hunks)
  • app/src/main/res/layout/activity_downloads.xml (1 hunks)
  • app/src/main/res/layout/activity_main.xml (1 hunks)
  • app/src/main/res/layout/item_download.xml (1 hunks)
  • app/src/main/res/values/strings.xml (1 hunks)

Comment on lines +17 to +18
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Initialize _isLoading with a default value to avoid null emissions

MutableLiveData<Boolean>() starts out as null, so observers must defensively handle the first null emission.
Given _isLoading is effectively a boolean flag, initialise it to false so the UI never has to deal with an unexpected null.

-private val _isLoading = MutableLiveData<Boolean>()
+private val _isLoading = MutableLiveData(false)
📝 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
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
private val _isLoading = MutableLiveData(false)
val isLoading: LiveData<Boolean> = _isLoading
🤖 Prompt for AI Agents
In app/src/main/java/com/tpstreams/player/DownloadViewModel.kt around lines 17
to 18, the MutableLiveData _isLoading is not initialized with a default value,
causing it to emit null initially. Fix this by initializing _isLoading with
false to ensure observers always receive a non-null Boolean value and the UI
does not have to handle null cases.

Comment on lines +32 to +36
fun loadDownloads() {
_isLoading.value = true
val downloadItems = downloadTracker.getAllDownloadItems()
_downloads.value = downloadItems
_isLoading.value = false
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Off-load loadDownloads() to a background thread

downloadTracker.getAllDownloadItems() can touch disk / network and is executed on the main thread, risking UI jank.
Run the fetch on Dispatchers.IO and post the result back:

-fun loadDownloads() {
-    _isLoading.value = true
-    val downloadItems = downloadTracker.getAllDownloadItems()
-    _downloads.value = downloadItems
-    _isLoading.value = false
-}
+fun loadDownloads() = viewModelScope.launch {
+    _isLoading.postValue(true)
+    val downloadItems = withContext(Dispatchers.IO) {
+        downloadTracker.getAllDownloadItems()
+    }
+    _downloads.postValue(downloadItems)
+    _isLoading.postValue(false)
+}

(remember to add import androidx.lifecycle.viewModelScope and coroutine deps if not already present).

📝 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
fun loadDownloads() {
_isLoading.value = true
val downloadItems = downloadTracker.getAllDownloadItems()
_downloads.value = downloadItems
_isLoading.value = false
// remember to import:
// import androidx.lifecycle.viewModelScope
// import kotlinx.coroutines.Dispatchers
// import kotlinx.coroutines.withContext
fun loadDownloads() = viewModelScope.launch {
_isLoading.postValue(true)
val downloadItems = withContext(Dispatchers.IO) {
downloadTracker.getAllDownloadItems()
}
_downloads.postValue(downloadItems)
_isLoading.postValue(false)
}
🤖 Prompt for AI Agents
In app/src/main/java/com/tpstreams/player/DownloadViewModel.kt around lines 32
to 36, the loadDownloads() function performs disk or network operations on the
main thread, which can cause UI jank. To fix this, wrap the call to
downloadTracker.getAllDownloadItems() inside a coroutine launched on
viewModelScope with Dispatchers.IO, then update _downloads and _isLoading on the
main thread after the fetch completes. Also, ensure you import
androidx.lifecycle.viewModelScope and have coroutine dependencies configured.

Comment on lines +15 to +16
assetId = "BEArYFdaFbt",
accessToken = "e6a1b485-daad-42eb-8cf2-6b6e51631092",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid committing credentials / tokens to VCS

assetId and especially accessToken are hard-coded. Access tokens are usually short-lived secrets and should reside in secure storage (remote config, keystore, or injected via CI build configs).

At minimum, move them to BuildConfig fields that are excluded from version control.

-assetId = "BEArYFdaFbt",
-accessToken = "e6a1b485-daad-42eb-8cf2-6b6e51631092",
+assetId = BuildConfig.TP_ASSET_ID,
+accessToken = BuildConfig.TP_ACCESS_TOKEN,

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/src/main/java/com/tpstreams/player/PlayerUIViewModel.kt around lines 15
to 16, the assetId and accessToken are hard-coded, exposing sensitive
credentials in version control. To fix this, remove these hard-coded values and
instead retrieve them from BuildConfig fields or a secure storage mechanism.
Configure these fields in your build system or CI pipeline so that the secrets
are injected at build time and not stored in the source code repository.

Comment on lines +38 to +43
adapter.registerAdapterDataObserver(object : androidx.recyclerview.widget.RecyclerView.AdapterDataObserver() {
override fun onChanged() {
checkEmptyState()
}
})

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Unregister the AdapterDataObserver to avoid leaking the Activity

RecyclerView.AdapterDataObserver holds a strong reference to the outer Activity via the anonymous class.
Add onDestroy { adapter.unregisterAdapterDataObserver(observer) } (where observer is stored in a field) or drop the observer since checkEmptyState() is already invoked from the downloads LiveData observer.

🤖 Prompt for AI Agents
In app/src/main/java/com/tpstreams/player/DownloadsActivity.kt around lines 38
to 43, the AdapterDataObserver is registered anonymously without being
unregistered, causing a potential memory leak by holding a strong reference to
the Activity. To fix this, store the observer instance in a field, then override
onDestroy() to unregister this observer from the adapter using
adapter.unregisterAdapterDataObserver(observer). Alternatively, if
checkEmptyState() is already called from the downloads LiveData observer, you
can remove the AdapterDataObserver registration entirely.

Comment on lines +75 to +111
private fun showPopupMenu(view: View, downloadItem: DownloadItem) {
val popupMenu = PopupMenu(this, view)

when (downloadItem.state) {
Download.STATE_COMPLETED -> {
popupMenu.menu.add(getString(R.string.delete_download_title))
}
Download.STATE_DOWNLOADING -> {
popupMenu.menu.add(getString(R.string.pause_download))
popupMenu.menu.add(getString(R.string.cancel_download))
}
Download.STATE_STOPPED -> {
popupMenu.menu.add(getString(R.string.resume_download))
popupMenu.menu.add(getString(R.string.cancel_download))
}
else -> {
popupMenu.menu.add(getString(R.string.cancel_download))
}
}

popupMenu.setOnMenuItemClickListener { menuItem ->
when (menuItem.title) {
getString(R.string.delete_download_title), getString(R.string.cancel_download) -> {
viewModel.removeDownload(downloadItem.assetId)
}
getString(R.string.pause_download) -> {
viewModel.pauseDownload(downloadItem.assetId)
}
getString(R.string.resume_download) -> {
viewModel.resumeDownload(downloadItem.assetId)
}
}
true
}

popupMenu.show()
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use stable menu item IDs instead of comparing titles

Comparing menuItem.title against translated strings can break in localized builds. Create items with fixed IDs and switch on item.itemId:

-        when (downloadItem.state) {
+        when (downloadItem.state) {
             Download.STATE_COMPLETED -> {
-                popupMenu.menu.add(getString(R.string.delete_download_title))
+                popupMenu.menu.add(0, R.id.menu_delete, 0, R.string.delete_download_title)
             }
             ...
         }
 
-        popupMenu.setOnMenuItemClickListener { menuItem ->
-            when (menuItem.title) {
-                getString(R.string.delete_download_title), getString(R.string.cancel_download) -> {
+        popupMenu.setOnMenuItemClickListener { menuItem ->
+            when (menuItem.itemId) {
+                R.id.menu_delete, R.id.menu_cancel -> {
                     viewModel.removeDownload(downloadItem.assetId)
                 }
-                getString(R.string.pause_download) -> {
+                R.id.menu_pause -> {
                     viewModel.pauseDownload(downloadItem.assetId)
                 }
-                getString(R.string.resume_download) -> {
+                R.id.menu_resume -> {
                     viewModel.resumeDownload(downloadItem.assetId)
                 }
             }
             true
         }

Remember to declare these IDs in res/values/ids.xml or use existing ones.

📝 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
private fun showPopupMenu(view: View, downloadItem: DownloadItem) {
val popupMenu = PopupMenu(this, view)
when (downloadItem.state) {
Download.STATE_COMPLETED -> {
popupMenu.menu.add(getString(R.string.delete_download_title))
}
Download.STATE_DOWNLOADING -> {
popupMenu.menu.add(getString(R.string.pause_download))
popupMenu.menu.add(getString(R.string.cancel_download))
}
Download.STATE_STOPPED -> {
popupMenu.menu.add(getString(R.string.resume_download))
popupMenu.menu.add(getString(R.string.cancel_download))
}
else -> {
popupMenu.menu.add(getString(R.string.cancel_download))
}
}
popupMenu.setOnMenuItemClickListener { menuItem ->
when (menuItem.title) {
getString(R.string.delete_download_title), getString(R.string.cancel_download) -> {
viewModel.removeDownload(downloadItem.assetId)
}
getString(R.string.pause_download) -> {
viewModel.pauseDownload(downloadItem.assetId)
}
getString(R.string.resume_download) -> {
viewModel.resumeDownload(downloadItem.assetId)
}
}
true
}
popupMenu.show()
}
private fun showPopupMenu(view: View, downloadItem: DownloadItem) {
val popupMenu = PopupMenu(this, view)
when (downloadItem.state) {
Download.STATE_COMPLETED -> {
popupMenu.menu.add(0, R.id.menu_delete, 0, R.string.delete_download_title)
}
Download.STATE_DOWNLOADING -> {
popupMenu.menu.add(0, R.id.menu_pause, 0, R.string.pause_download)
popupMenu.menu.add(0, R.id.menu_cancel, 0, R.string.cancel_download)
}
Download.STATE_STOPPED -> {
popupMenu.menu.add(0, R.id.menu_resume, 0, R.string.resume_download)
popupMenu.menu.add(0, R.id.menu_cancel, 0, R.string.cancel_download)
}
else -> {
popupMenu.menu.add(0, R.id.menu_cancel, 0, R.string.cancel_download)
}
}
popupMenu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.menu_delete, R.id.menu_cancel -> {
viewModel.removeDownload(downloadItem.assetId)
}
R.id.menu_pause -> {
viewModel.pauseDownload(downloadItem.assetId)
}
R.id.menu_resume -> {
viewModel.resumeDownload(downloadItem.assetId)
}
}
true
}
popupMenu.show()
}
🤖 Prompt for AI Agents
In app/src/main/java/com/tpstreams/player/DownloadsActivity.kt between lines 75
and 111, the popup menu item click handling compares menuItem.title against
localized strings, which can break in localized builds. To fix this, assign
fixed, stable IDs to each menu item when adding them to the popup menu, then in
the setOnMenuItemClickListener switch on menuItem.itemId instead of title.
Define these IDs in res/values/ids.xml or use existing resource IDs to ensure
consistency across locales.

Comment on lines +25 to +31
class DownloadViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val titleText: TextView = itemView.findViewById(R.id.title_text)
private val assetIdText: TextView = itemView.findViewById(R.id.asset_id_text)
private val progressBar: ProgressBar = itemView.findViewById(R.id.download_progress)
private val progressText: TextView = itemView.findViewById(R.id.progress_text)
private val stateText: TextView = itemView.findViewById(R.id.state_text)

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace brittle findViewById with View Binding

Manual view look-ups are verbose, error-prone, and prevent the compiler from catching missing IDs.
Switching to View Binding (or Kotlin synthetic properties if you’re still on KTX) will:

• Remove the repeated casts
• Guarantee non-null references at compile time
• Reduce boilerplate in onCreateViewHolder / ViewHolder

Example refactor (View Binding):

-class DownloadViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
-    private val titleText: TextView = itemView.findViewById(R.id.title_text)
-    ...
+class DownloadViewHolder(
+    private val binding: ItemDownloadBinding
+) : RecyclerView.ViewHolder(binding.root) {
+    fun bind(downloadItem: DownloadItem) {
+        binding.titleText.text   = downloadItem.title
+        ...

And in onCreateViewHolder:

- val view = LayoutInflater.from(parent.context).inflate(R.layout.item_download, parent, false)
- return DownloadViewHolder(view)
+ val binding = ItemDownloadBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return DownloadViewHolder(binding)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/src/main/java/com/tpstreams/player/DownloadAdapter.kt around lines 25 to
31, replace the manual findViewById calls in DownloadViewHolder with View
Binding to avoid verbose and error-prone code. Enable View Binding in the
module, create a binding property for the item layout, and use it to directly
access views without casting. Update onCreateViewHolder to inflate the binding
instead of the layout and pass the binding.root to the ViewHolder constructor,
then access views through the binding instance inside the ViewHolder.

Comment on lines +37 to +41
val progress = downloadItem.progressPercentage.toInt()
progressBar.progress = progress

// Show only percentage
progressText.text = "$progress%"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Clamp / guard progress to avoid illegal values

progressPercentage can legally be C.PERCENTAGE_UNSET (‐1) or occasionally exceed 100 on bad manifests.
Directly casting to Int and assigning will:

• Display a “-1%” label
• Throw IllegalArgumentException on some OEM progress bars that validate range

-val progress = downloadItem.progressPercentage.toInt()
-progressBar.progress = progress
-progressText.text = "$progress%"
+val raw = downloadItem.progressPercentage
+val clamped = raw.coerceIn(0f, 100f).toInt()
+progressBar.isIndeterminate = raw < 0
+progressBar.progress = clamped
+progressText.text = if (raw < 0) "—" else "$clamped%"
📝 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
val progress = downloadItem.progressPercentage.toInt()
progressBar.progress = progress
// Show only percentage
progressText.text = "$progress%"
val raw = downloadItem.progressPercentage
val clamped = raw.coerceIn(0f, 100f).toInt()
progressBar.isIndeterminate = raw < 0
progressBar.progress = clamped
// Show only percentage
progressText.text = if (raw < 0) "" else "$clamped%"
🤖 Prompt for AI Agents
In app/src/main/java/com/tpstreams/player/DownloadAdapter.kt around lines 37 to
41, the progress value derived from downloadItem.progressPercentage can be -1 or
exceed 100, which causes invalid progress bar states and exceptions. To fix
this, clamp the progress value to a valid range between 0 and 100 before
assigning it to progressBar.progress and displaying it in progressText. This
prevents illegal values and ensures safe UI updates.

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