Simplify and fix download status display

This commit is contained in:
tzugen 2021-11-15 00:19:47 +01:00
parent 7a2dbf65d9
commit 6277ee73c0
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
10 changed files with 118 additions and 68 deletions

View File

@ -11,6 +11,7 @@ import org.moire.ultrasonic.util.Util
*/ */
class ImageHelper(context: Context) { class ImageHelper(context: Context) {
lateinit var errorImage: Drawable
lateinit var starHollowDrawable: Drawable lateinit var starHollowDrawable: Drawable
lateinit var starDrawable: Drawable lateinit var starDrawable: Drawable
lateinit var pinImage: Drawable lateinit var pinImage: Drawable
@ -39,6 +40,7 @@ class ImageHelper(context: Context) {
starDrawable = Util.getDrawableFromAttribute(context, R.attr.star_full) starDrawable = Util.getDrawableFromAttribute(context, R.attr.star_full)
pinImage = Util.getDrawableFromAttribute(context, R.attr.pin) pinImage = Util.getDrawableFromAttribute(context, R.attr.pin)
downloadedImage = Util.getDrawableFromAttribute(context, R.attr.downloaded) downloadedImage = Util.getDrawableFromAttribute(context, R.attr.downloaded)
errorImage = Util.getDrawableFromAttribute(context, R.attr.error)
downloadingImage = Util.getDrawableFromAttribute(context, R.attr.downloading) downloadingImage = Util.getDrawableFromAttribute(context, R.attr.downloading)
playingImage = Util.getDrawableFromAttribute(context, R.attr.media_play_small) playingImage = Util.getDrawableFromAttribute(context, R.attr.media_play_small)
} }

View File

@ -1,7 +1,6 @@
package org.moire.ultrasonic.adapters package org.moire.ultrasonic.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.AdapterListUpdateCallback import androidx.recyclerview.widget.AdapterListUpdateCallback
import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.AsyncDifferConfig
@ -145,6 +144,14 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
selectionRevision.postValue(selectionRevision.value!! + 1) selectionRevision.postValue(selectionRevision.value!! + 1)
} }
fun notifyChanged() {
// When the download state of an entry was changed by an external process,
// increase the revision counter in order to update the UI
selectionRevision.postValue(selectionRevision.value!! + 1)
}
fun setSelectionStatusOfAll(select: Boolean): Int { fun setSelectionStatusOfAll(select: Boolean): Int {
// Clear current selection // Clear current selection
selectedSet.clear() selectedSet.clear()

View File

@ -78,13 +78,14 @@ class TrackViewBinder(
// Observe download status // Observe download status
downloadFile.status.observe(lifecycleOwner, { downloadFile.status.observe(lifecycleOwner, {
Timber.w("CAUGHT STATUS CHANGE") Timber.w("CAUGHT STATUS CHANGE")
holder.updateDownloadStatus(downloadFile) holder.updateStatus(it)
holder.adapter.notifyChanged()
} }
) )
downloadFile.progress.observe(lifecycleOwner, { downloadFile.progress.observe(lifecycleOwner, {
Timber.w("CAUGHT PROGRESS CHANGE") Timber.w("CAUGHT PROGRESS CHANGE")
holder.updateDownloadStatus(downloadFile) holder.updateProgress(it)
} }
) )
} }

View File

@ -9,7 +9,6 @@ import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
@ -21,6 +20,7 @@ import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.featureflags.Feature import org.moire.ultrasonic.featureflags.Feature
import org.moire.ultrasonic.featureflags.FeatureStorage import org.moire.ultrasonic.featureflags.FeatureStorage
import org.moire.ultrasonic.service.DownloadFile import org.moire.ultrasonic.service.DownloadFile
import org.moire.ultrasonic.service.DownloadStatus
import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.service.MusicServiceFactory import org.moire.ultrasonic.service.MusicServiceFactory
import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Settings
@ -47,7 +47,7 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
var title: TextView = view.findViewById(R.id.song_title) var title: TextView = view.findViewById(R.id.song_title)
var artist: TextView = view.findViewById(R.id.song_artist) var artist: TextView = view.findViewById(R.id.song_artist)
var duration: TextView = view.findViewById(R.id.song_duration) var duration: TextView = view.findViewById(R.id.song_duration)
var status: TextView = view.findViewById(R.id.song_status) var progress: TextView = view.findViewById(R.id.song_status)
var entry: MusicDirectory.Entry? = null var entry: MusicDirectory.Entry? = null
private set private set
@ -55,10 +55,8 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
private set private set
private var isMaximized = false private var isMaximized = false
private var leftImage: Drawable? = null private var cachedStatus = DownloadStatus.UNKNOWN
private var previousLeftImageType: ImageType? = null private var statusImage: Drawable? = null
private var previousRightImageType: ImageType? = null
private var leftImageType: ImageType? = null
private var playing = false private var playing = false
private val useFiveStarRating: Boolean by lazy { private val useFiveStarRating: Boolean by lazy {
@ -160,7 +158,8 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
// TODO: Should be removed // TODO: Should be removed
fun update() { fun update() {
updateDownloadStatus(downloadFile!!) updateProgress(downloadFile!!.progress.value!!)
updateStatus(downloadFile!!.status.value!!)
if (useFiveStarRating) { if (useFiveStarRating) {
val rating = entry?.userRating ?: 0 val rating = entry?.userRating ?: 0
@ -220,52 +219,55 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
} }
fun updateDownloadStatus(downloadFile: DownloadFile) { fun updateStatus(status: DownloadStatus) {
if (downloadFile.isWorkDone) { if (status == cachedStatus) return
val saved = downloadFile.isSaved cachedStatus = status
val newLeftImageType =
if (saved) ImageType.Pin else ImageType.Downloaded
if (leftImageType != newLeftImageType) {
leftImage = if (saved) imageHelper.pinImage else imageHelper.downloadedImage Timber.w("STATUS: %s", status)
leftImageType = newLeftImageType
when (status) {
DownloadStatus.DONE -> {
statusImage = imageHelper.downloadedImage
progress.text = null
}
DownloadStatus.PINNED -> {
statusImage = imageHelper.pinImage
progress.text = null
}
DownloadStatus.FAILED,
DownloadStatus.ABORTED -> {
statusImage = imageHelper.errorImage
progress.text = null
}
DownloadStatus.DOWNLOADING -> {
statusImage = imageHelper.downloadingImage
}
else -> {
statusImage = null
} }
} else {
leftImageType = ImageType.None
leftImage = null
} }
val rightImageType: ImageType updateImages()
val rightImage: Drawable? }
if (downloadFile.isDownloading && !downloadFile.isDownloadCancelled) { fun updateProgress(p: Int) {
status.text = Util.formatPercentage(downloadFile.progress.value!!) if (cachedStatus == DownloadStatus.DOWNLOADING) {
progress.text = Util.formatPercentage(p)
rightImageType = ImageType.Downloading } else {
rightImage = imageHelper.downloadingImage progress.text = null
} else {
rightImageType = ImageType.None
rightImage = null
val statusText = status.text
if (!statusText.isNullOrEmpty()) status.text = null
} }
}
if (previousLeftImageType != leftImageType || previousRightImageType != rightImageType) { private fun updateImages() {
previousLeftImageType = leftImageType progress.setCompoundDrawablesWithIntrinsicBounds(
previousRightImageType = rightImageType null, null, statusImage, null
)
status.setCompoundDrawablesWithIntrinsicBounds( if (statusImage === imageHelper.downloadingImage) {
leftImage, null, rightImage, null val frameAnimation = statusImage as AnimationDrawable?
) frameAnimation?.setVisible(true, true)
frameAnimation?.start()
if (rightImage === imageHelper.downloadingImage) {
// FIXME
val frameAnimation = rightImage as AnimationDrawable?
frameAnimation?.setVisible(true, true)
frameAnimation?.start()
}
} }
} }
@ -293,11 +295,4 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
title.isSingleLine = !isMaximized title.isSingleLine = !isMaximized
artist.isSingleLine = !isMaximized artist.isSingleLine = !isMaximized
} }
}
enum class ImageType {
None, Pin, Downloaded, Downloading
}
}

View File

@ -32,6 +32,11 @@ import timber.log.Timber
/** /**
* This class represents a single Song or Video that can be downloaded. * This class represents a single Song or Video that can be downloaded.
*
* Terminology:
* PinnedFile: A "pinned" song. Will stay in cache permanently
* CompleteFile: A "downloaded" song. Will be quicker to be deleted if the cache is full
*
*/ */
class DownloadFile( class DownloadFile(
val song: MusicDirectory.Entry, val song: MusicDirectory.Entry,
@ -63,11 +68,28 @@ class DownloadFile(
private val activeServerProvider: ActiveServerProvider by inject() private val activeServerProvider: ActiveServerProvider by inject()
val progress: MutableLiveData<Int> = MutableLiveData(0) val progress: MutableLiveData<Int> = MutableLiveData(0)
val status: MutableLiveData<DownloadStatus> = MutableLiveData(DownloadStatus.IDLE) val status: MutableLiveData<DownloadStatus>
init { init {
val state: DownloadStatus
partialFile = File(saveFile.parent, FileUtil.getPartialFile(saveFile.name)) partialFile = File(saveFile.parent, FileUtil.getPartialFile(saveFile.name))
completeFile = File(saveFile.parent, FileUtil.getCompleteFile(saveFile.name)) completeFile = File(saveFile.parent, FileUtil.getCompleteFile(saveFile.name))
when {
saveFile.exists() -> {
state = DownloadStatus.PINNED
}
completeFile.exists() -> {
state = DownloadStatus.DONE
}
else -> {
state = DownloadStatus.IDLE
}
}
status = MutableLiveData(state)
} }
/** /**
@ -143,13 +165,14 @@ class DownloadFile(
fun unpin() { fun unpin() {
if (saveFile.exists()) { if (saveFile.exists()) {
if (!saveFile.renameTo(completeFile)) { if (saveFile.renameTo(completeFile)) {
status.postValue(DownloadStatus.DONE)
} else {
Timber.w( Timber.w(
"Renaming file failed. Original file: %s; Rename to: %s", "Renaming file failed. Original file: %s; Rename to: %s",
saveFile.name, completeFile.name saveFile.name, completeFile.name
) )
} }
status.postValue(DownloadStatus.DONE)
} }
} }
@ -212,23 +235,23 @@ class DownloadFile(
try { try {
if (saveFile.exists()) { if (saveFile.exists()) {
Timber.i("%s already exists. Skipping.", saveFile) Timber.i("%s already exists. Skipping.", saveFile)
status.postValue(DownloadStatus.DONE) status.postValue(DownloadStatus.PINNED)
Timber.i("UPDATING STATUS")
return return
} }
if (completeFile.exists()) { if (completeFile.exists()) {
var newStatus: DownloadStatus = DownloadStatus.DONE
if (shouldSave) { if (shouldSave) {
if (isPlaying) { if (isPlaying) {
saveWhenDone = true saveWhenDone = true
} else { } else {
Util.renameFile(completeFile, saveFile) Util.renameFile(completeFile, saveFile)
newStatus = DownloadStatus.PINNED
} }
} else { } else {
Timber.i("%s already exists. Skipping.", completeFile) Timber.i("%s already exists. Skipping.", completeFile)
} }
status.postValue(DownloadStatus.DONE) status.postValue(newStatus)
Timber.i("UPDATING STATUS")
return return
} }
@ -285,8 +308,6 @@ class DownloadFile(
} }
downloadAndSaveCoverArt() downloadAndSaveCoverArt()
status.postValue(DownloadStatus.DONE)
} }
if (isPlaying) { if (isPlaying) {
@ -294,9 +315,11 @@ class DownloadFile(
} else { } else {
if (shouldSave) { if (shouldSave) {
Util.renameFile(partialFile, saveFile) Util.renameFile(partialFile, saveFile)
status.postValue(DownloadStatus.PINNED)
Util.scanMedia(saveFile) Util.scanMedia(saveFile)
} else { } else {
Util.renameFile(partialFile, completeFile) Util.renameFile(partialFile, completeFile)
status.postValue(DownloadStatus.DONE)
} }
} }
} catch (all: Exception) { } catch (all: Exception) {
@ -378,7 +401,6 @@ class DownloadFile(
private fun setProgress(totalBytesCopied: Long) { private fun setProgress(totalBytesCopied: Long) {
if (song.size != null) { if (song.size != null) {
progress.postValue((totalBytesCopied * 100 / song.size!!).toInt()) progress.postValue((totalBytesCopied * 100 / song.size!!).toInt())
Timber.i("UPDATING PROGESS")
} }
} }
@ -414,6 +436,7 @@ class DownloadFile(
override val id: String override val id: String
get() = song.id get() = song.id
override val longId: Long by lazy { override val longId: Long by lazy {
id.hashCode().toLong() id.hashCode().toLong()
} }
@ -424,5 +447,5 @@ class DownloadFile(
} }
enum class DownloadStatus { enum class DownloadStatus {
IDLE, DOWNLOADING, RETRYING, FAILED, ABORTED, DONE IDLE, DOWNLOADING, RETRYING, FAILED, ABORTED, DONE, PINNED, UNKNOWN
} }

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>

View File

@ -39,7 +39,7 @@
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:layout_gravity="right|center_vertical" a:layout_gravity="right|center_vertical"
a:drawablePadding="6dip" a:drawablePadding="6dip"
a:paddingEnd="6dip"/> a:paddingEnd="12dip"/>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout

View File

@ -61,6 +61,7 @@
<attr name="share" format="reference"/> <attr name="share" format="reference"/>
<attr name="download" format="reference"/> <attr name="download" format="reference"/>
<attr name="downloaded" format="reference"/> <attr name="downloaded" format="reference"/>
<attr name="error" format="reference"/>
<attr name="downloading" format="reference"/> <attr name="downloading" format="reference"/>
<attr name="media_previous" format="reference"/> <attr name="media_previous" format="reference"/>
<attr name="media_next" format="reference"/> <attr name="media_next" format="reference"/>

View File

@ -34,6 +34,7 @@
<item name="share">@drawable/ic_menu_share_dark</item> <item name="share">@drawable/ic_menu_share_dark</item>
<item name="download">@drawable/ic_menu_download_dark</item> <item name="download">@drawable/ic_menu_download_dark</item>
<item name="downloaded">@drawable/stat_sys_download_anim_0_dark</item> <item name="downloaded">@drawable/stat_sys_download_anim_0_dark</item>
<item name="error">@drawable/ic_baseline_error_dark</item>
<item name="downloading">@drawable/stat_sys_download_dark</item> <item name="downloading">@drawable/stat_sys_download_dark</item>
<item name="media_previous">@drawable/media_backward_normal_dark</item> <item name="media_previous">@drawable/media_backward_normal_dark</item>
<item name="media_next">@drawable/media_forward_normal_dark</item> <item name="media_next">@drawable/media_forward_normal_dark</item>
@ -99,6 +100,7 @@
<item name="share">@drawable/ic_menu_share_dark</item> <item name="share">@drawable/ic_menu_share_dark</item>
<item name="download">@drawable/ic_menu_download_dark</item> <item name="download">@drawable/ic_menu_download_dark</item>
<item name="downloaded">@drawable/stat_sys_download_anim_0_dark</item> <item name="downloaded">@drawable/stat_sys_download_anim_0_dark</item>
<item name="error">@drawable/ic_baseline_error_dark</item>
<item name="downloading">@drawable/stat_sys_download_dark</item> <item name="downloading">@drawable/stat_sys_download_dark</item>
<item name="media_previous">@drawable/media_backward_normal_dark</item> <item name="media_previous">@drawable/media_backward_normal_dark</item>
<item name="media_next">@drawable/media_forward_normal_dark</item> <item name="media_next">@drawable/media_forward_normal_dark</item>
@ -163,6 +165,7 @@
<item name="share">@drawable/ic_menu_share_light</item> <item name="share">@drawable/ic_menu_share_light</item>
<item name="download">@drawable/ic_menu_download_light</item> <item name="download">@drawable/ic_menu_download_light</item>
<item name="downloaded">@drawable/stat_sys_download_anim_0_light</item> <item name="downloaded">@drawable/stat_sys_download_anim_0_light</item>
<item name="error">@drawable/ic_baseline_error_light</item>
<item name="downloading">@drawable/stat_sys_download_light</item> <item name="downloading">@drawable/stat_sys_download_light</item>
<item name="media_previous">@drawable/media_backward_normal_light</item> <item name="media_previous">@drawable/media_backward_normal_light</item>
<item name="media_next">@drawable/media_forward_normal_light</item> <item name="media_next">@drawable/media_forward_normal_light</item>