Merge branch 'track_states' into 'develop'

Updated track state display to use spinner

See merge request ultrasonic/ultrasonic!803
This commit is contained in:
Nite 2022-08-18 14:44:49 +00:00
commit fa688b0a3e
6 changed files with 129 additions and 103 deletions

View File

@ -147,7 +147,10 @@ class BaseAdapter<T : Identifiable> : MultiTypeAdapter(), FastScrollRecyclerView
* @see .getCurrentList
*/
fun onCurrentListChanged(previousList: List<T>, currentList: List<T>) {
// Void
previousList.minus(currentList.toSet()).map {
selectedSet.remove(it.longId)
}
selectionRevision.postValue(selectionRevision.value!! + 1)
}
fun notifySelected(id: Long) {

View File

@ -10,6 +10,7 @@ import android.widget.TextView
import androidx.core.view.isVisible
import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.progressindicator.CircularProgressIndicator
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@ -25,6 +26,9 @@ import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util
import timber.log.Timber
const val INDICATOR_THICKNESS_INDEFINITE = 5
const val INDICATOR_THICKNESS_DEFINITE = 10
/**
* Used to display songs and videos in a `ListView`.
*/
@ -32,35 +36,36 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
private val downloader: Downloader by inject()
var entry: Track? = null
private set
var check: CheckedTextView = view.findViewById(R.id.song_check)
var drag: ImageView = view.findViewById(R.id.song_drag)
var observableChecked = MutableLiveData(false)
lateinit var imageHelper: Utils.ImageHelper
private var rating: LinearLayout = view.findViewById(R.id.song_five_star)
private var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1)
private var fiveStar2: ImageView = view.findViewById(R.id.song_five_star_2)
private var fiveStar3: ImageView = view.findViewById(R.id.song_five_star_3)
private var fiveStar4: ImageView = view.findViewById(R.id.song_five_star_4)
private var fiveStar5: ImageView = view.findViewById(R.id.song_five_star_5)
var star: ImageView = view.findViewById(R.id.song_star)
var drag: ImageView = view.findViewById(R.id.song_drag)
var track: TextView = view.findViewById(R.id.song_track)
var title: TextView = view.findViewById(R.id.song_title)
var artist: TextView = view.findViewById(R.id.song_artist)
var duration: TextView = view.findViewById(R.id.song_duration)
var progress: TextView = view.findViewById(R.id.song_status)
var entry: Track? = null
private set
private var star: ImageView = view.findViewById(R.id.song_star)
private var track: TextView = view.findViewById(R.id.song_track)
private var title: TextView = view.findViewById(R.id.song_title)
private var artist: TextView = view.findViewById(R.id.song_artist)
private var duration: TextView = view.findViewById(R.id.song_duration)
private var statusImage: ImageView = view.findViewById(R.id.song_status_image)
private var progressIndicator: CircularProgressIndicator =
view.findViewById<CircularProgressIndicator?>(R.id.song_status_progress).apply {
this.max = 100
}
private var isMaximized = false
private var cachedStatus = DownloadStatus.UNKNOWN
private var statusImage: Drawable? = null
private var isPlayingCached = false
private var rxBusSubscription: CompositeDisposable? = null
var observableChecked = MutableLiveData(false)
lateinit var imageHelper: Utils.ImageHelper
fun setSong(
song: Track,
checkable: Boolean,
@ -103,7 +108,7 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
if (song.isVideo) {
artist.isVisible = false
progress.isVisible = false
progressIndicator.isVisible = false
}
// Create new Disposable for the new Subscriptions
@ -204,50 +209,58 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
}
}
private fun updateStatus(status: DownloadStatus, p: Int?) {
if (status != cachedStatus) {
cachedStatus = status
private fun updateStatus(status: DownloadStatus, progress: Int?) {
progressIndicator.progress = progress ?: 0
when (status) {
DownloadStatus.DONE -> {
statusImage = imageHelper.downloadedImage
progress.text = null
}
DownloadStatus.PINNED -> {
statusImage = imageHelper.pinImage
progress.text = null
}
DownloadStatus.FAILED,
DownloadStatus.CANCELLED -> {
statusImage = imageHelper.errorImage
progress.text = null
}
DownloadStatus.DOWNLOADING -> {
statusImage = imageHelper.downloadingImage[0]
}
else -> {
statusImage = null
}
if (status == cachedStatus) return
cachedStatus = status
when (status) {
DownloadStatus.DONE -> {
showStatusImage(imageHelper.downloadedImage)
}
DownloadStatus.PINNED -> {
showStatusImage(imageHelper.pinImage)
}
DownloadStatus.FAILED,
DownloadStatus.CANCELLED -> {
showStatusImage(imageHelper.errorImage)
}
DownloadStatus.DOWNLOADING -> {
showProgress()
}
DownloadStatus.RETRYING,
DownloadStatus.QUEUED -> {
showIndefiniteProgress()
}
else -> {
showStatusImage(null)
}
}
if (cachedStatus == DownloadStatus.DOWNLOADING && p != null) {
progress.text = Util.formatPercentage(p)
statusImage =
imageHelper.downloadingImage[(imageHelper.downloadingImage.size / 100F * p).toInt()]
} else {
progress.text = null
}
updateImages()
}
private fun updateImages() {
progress.post {
progress.setCompoundDrawablesWithIntrinsicBounds(
null, null, statusImage, null
)
}
private fun showStatusImage(image: Drawable?) {
progressIndicator.isVisible = false
statusImage.isVisible = true
statusImage.setImageDrawable(image)
}
private fun showIndefiniteProgress() {
statusImage.isVisible = false
progressIndicator.isVisible = true
progressIndicator.isIndeterminate = true
progressIndicator.indicatorDirection =
CircularProgressIndicator.INDICATOR_DIRECTION_COUNTERCLOCKWISE
progressIndicator.trackThickness = INDICATOR_THICKNESS_INDEFINITE
}
private fun showProgress() {
statusImage.isVisible = false
progressIndicator.isVisible = true
progressIndicator.isIndeterminate = false
progressIndicator.indicatorDirection =
CircularProgressIndicator.INDICATOR_DIRECTION_CLOCKWISE
progressIndicator.trackThickness = INDICATOR_THICKNESS_DEFINITE
}
/*

View File

@ -375,31 +375,42 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
var unpinEnabled = false
var deleteEnabled = false
var downloadEnabled = false
var isNotInProgress = true
val multipleSelection = viewAdapter.hasMultipleSelection()
var pinnedCount = 0
for (song in selection) {
val state = downloader.getDownloadState(song)
if (state == DownloadStatus.DONE || state == DownloadStatus.PINNED) {
deleteEnabled = true
}
if (state == DownloadStatus.PINNED) {
pinnedCount++
unpinEnabled = true
}
if (state == DownloadStatus.IDLE || state == DownloadStatus.FAILED) {
downloadEnabled = true
when (state) {
DownloadStatus.DONE -> {
deleteEnabled = true
}
DownloadStatus.PINNED -> {
deleteEnabled = true
pinnedCount++
unpinEnabled = true
}
DownloadStatus.IDLE, DownloadStatus.FAILED -> {
downloadEnabled = true
}
DownloadStatus.DOWNLOADING,
DownloadStatus.QUEUED,
DownloadStatus.RETRYING -> {
isNotInProgress = false
}
else -> {}
}
}
playNowButton?.isVisible = enabled
playNextButton?.isVisible = enabled && multipleSelection
playLastButton?.isVisible = enabled && multipleSelection
pinButton?.isVisible = (enabled && !isOffline() && selection.size > pinnedCount)
unpinButton?.isVisible = (enabled && unpinEnabled)
downloadButton?.isVisible = (enabled && downloadEnabled && !isOffline())
deleteButton?.isVisible = (enabled && deleteEnabled)
pinButton?.isVisible =
isNotInProgress && enabled && !isOffline() && selection.size > pinnedCount
unpinButton?.isVisible = isNotInProgress && enabled && unpinEnabled
downloadButton?.isVisible = isNotInProgress && enabled && downloadEnabled && !isOffline()
deleteButton?.isVisible = isNotInProgress && enabled && deleteEnabled
}
private fun downloadBackground(save: Boolean) {

View File

@ -190,6 +190,7 @@ class Downloader(
// Set correct priority (the lower the number, the higher the priority)
downloadQueue.add(DownloadableTrack(track, item.shouldBePinned(), 0, priority++))
postState(track, DownloadStatus.QUEUED)
}
}
@ -240,7 +241,9 @@ class Downloader(
@Synchronized
fun clearBackground() {
// Clear the pending queue
downloadQueue.clear()
while (!downloadQueue.isEmpty()) {
postState(downloadQueue.remove().track, DownloadStatus.IDLE)
}
// Cancel all active downloads with a low priority
for (key in activelyDownloading.keys) {
@ -272,6 +275,7 @@ class Downloader(
) continue
val file = DownloadableTrack(track, save, 0, backgroundPriorityCounter++)
downloadQueue.add(file)
postState(track, DownloadStatus.QUEUED)
}
Timber.v("downloadBackground Checking Downloads")
@ -288,7 +292,7 @@ class Downloader(
}
private fun cancelDownload(track: Track) {
val key = activelyDownloading.keys.singleOrNull { t -> t.track.id == track.id } ?: return
val key = activelyDownloading.keys.singleOrNull { it.track.id == track.id } ?: return
activelyDownloading[key]?.cancel()
}
@ -304,20 +308,21 @@ class Downloader(
fun getDownloadState(track: Track): DownloadStatus {
if (Storage.isPathExists(track.getCompleteFile())) return DownloadStatus.DONE
if (Storage.isPathExists(track.getPinnedFile())) return DownloadStatus.PINNED
if (downloads.any { it.id == track.id }) return DownloadStatus.QUEUED
val key = activelyDownloading.keys.firstOrNull { k -> k.track.id == track.id }
val key = activelyDownloading.keys.firstOrNull { it.track.id == track.id }
if (key != null) {
if (key.tryCount > 0) return DownloadStatus.RETRYING
return DownloadStatus.DOWNLOADING
}
if (failedList.any { t -> t.track.id == track.id }) return DownloadStatus.FAILED
if (failedList.any { it.track.id == track.id }) return DownloadStatus.FAILED
return DownloadStatus.IDLE
}
companion object {
const val CHECK_INTERVAL = 5000L
const val MAX_RETRIES = 5
const val REFRESH_INTERVAL = 100
const val REFRESH_INTERVAL = 50
}
private fun postState(track: Track, state: DownloadStatus, progress: Int? = null) {
@ -579,5 +584,5 @@ class Downloader(
}
enum class DownloadStatus {
IDLE, DOWNLOADING, RETRYING, FAILED, CANCELLED, DONE, PINNED, UNKNOWN
IDLE, QUEUED, DOWNLOADING, RETRYING, FAILED, CANCELLED, DONE, PINNED, UNKNOWN
}

View File

@ -140,21 +140,6 @@ object Util {
toast!!.show()
}
/**
* Formats an Int to a percentage string
* For instance:
*
* * `format(99)` returns *"99 %"*.
*
*
* @param percent The percent as a range from 0 - 100
* @return The formatted string.
*/
@Synchronized
fun formatPercentage(percent: Int): String {
return min(max(percent, 0), 100).toString() + " %"
}
/**
* Converts a byte-count to a formatted string suitable for display to the user.
* For instance:

View File

@ -97,19 +97,28 @@
app:layout_constraintStart_toStartOf="parent"
tools:text="Artist" />
<TextView
a:id="@+id/song_status"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:drawablePadding="6dip"
a:paddingEnd="12dip"
a:textAlignment="textEnd"
app:layout_constraintBottom_toTopOf="@+id/song_duration"
<ImageView
a:id="@+id/song_status_image"
a:layout_width="25dp"
a:layout_height="0dp"
a:layout_marginEnd="10dip"
app:layout_constraintTop_toTopOf="@id/song_details"
app:layout_constraintBottom_toTopOf="@id/song_duration"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="@+id/song_duration"
app:layout_constraintTop_toTopOf="parent"
tools:text="100%" />
a:importantForAccessibility="no" />
<com.google.android.material.progressindicator.CircularProgressIndicator
a:id="@+id/song_status_progress"
a:layout_width="wrap_content"
a:layout_height="0dp"
a:layout_marginEnd="10dip"
app:indicatorSize="20dp"
app:indicatorColor="?attr/color_menu_selected"
app:trackColor="?attr/color_selected"
app:layout_constraintTop_toTopOf="@id/song_details"
app:layout_constraintBottom_toTopOf="@id/song_duration"
app:layout_constraintEnd_toEndOf="parent"
a:indeterminate="true" />
<TextView
a:id="@+id/song_duration"
@ -128,7 +137,7 @@
a:layout_width="wrap_content"
a:layout_height="wrap_content"
app:barrierDirection="left"
app:constraint_referenced_ids="song_status,song_duration" />
app:constraint_referenced_ids="song_duration" />
</androidx.constraintlayout.widget.ConstraintLayout>