mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-25 21:22:16 +03:00
Merge branch 'track_states' into 'develop'
Updated track state display to use spinner See merge request ultrasonic/ultrasonic!803
This commit is contained in:
commit
fa688b0a3e
@ -147,7 +147,10 @@ class BaseAdapter<T : Identifiable> : MultiTypeAdapter(), FastScrollRecyclerView
|
|||||||
* @see .getCurrentList
|
* @see .getCurrentList
|
||||||
*/
|
*/
|
||||||
fun onCurrentListChanged(previousList: List<T>, currentList: List<T>) {
|
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) {
|
fun notifySelected(id: Long) {
|
||||||
|
@ -10,6 +10,7 @@ import android.widget.TextView
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
@ -25,6 +26,9 @@ import org.moire.ultrasonic.util.Settings
|
|||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
import timber.log.Timber
|
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`.
|
* 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()
|
private val downloader: Downloader by inject()
|
||||||
|
|
||||||
|
var entry: Track? = null
|
||||||
|
private set
|
||||||
var check: CheckedTextView = view.findViewById(R.id.song_check)
|
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 rating: LinearLayout = view.findViewById(R.id.song_five_star)
|
||||||
private var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1)
|
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 fiveStar2: ImageView = view.findViewById(R.id.song_five_star_2)
|
||||||
private var fiveStar3: ImageView = view.findViewById(R.id.song_five_star_3)
|
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 fiveStar4: ImageView = view.findViewById(R.id.song_five_star_4)
|
||||||
private var fiveStar5: ImageView = view.findViewById(R.id.song_five_star_5)
|
private var fiveStar5: ImageView = view.findViewById(R.id.song_five_star_5)
|
||||||
var star: ImageView = view.findViewById(R.id.song_star)
|
private var star: ImageView = view.findViewById(R.id.song_star)
|
||||||
var drag: ImageView = view.findViewById(R.id.song_drag)
|
private var track: TextView = view.findViewById(R.id.song_track)
|
||||||
var track: TextView = view.findViewById(R.id.song_track)
|
private var title: TextView = view.findViewById(R.id.song_title)
|
||||||
var title: TextView = view.findViewById(R.id.song_title)
|
private var artist: TextView = view.findViewById(R.id.song_artist)
|
||||||
var artist: TextView = view.findViewById(R.id.song_artist)
|
private var duration: TextView = view.findViewById(R.id.song_duration)
|
||||||
var duration: TextView = view.findViewById(R.id.song_duration)
|
private var statusImage: ImageView = view.findViewById(R.id.song_status_image)
|
||||||
var progress: TextView = view.findViewById(R.id.song_status)
|
private var progressIndicator: CircularProgressIndicator =
|
||||||
|
view.findViewById<CircularProgressIndicator?>(R.id.song_status_progress).apply {
|
||||||
var entry: Track? = null
|
this.max = 100
|
||||||
private set
|
}
|
||||||
|
|
||||||
private var isMaximized = false
|
private var isMaximized = false
|
||||||
private var cachedStatus = DownloadStatus.UNKNOWN
|
private var cachedStatus = DownloadStatus.UNKNOWN
|
||||||
private var statusImage: Drawable? = null
|
|
||||||
private var isPlayingCached = false
|
private var isPlayingCached = false
|
||||||
|
|
||||||
private var rxBusSubscription: CompositeDisposable? = null
|
private var rxBusSubscription: CompositeDisposable? = null
|
||||||
|
|
||||||
var observableChecked = MutableLiveData(false)
|
|
||||||
|
|
||||||
lateinit var imageHelper: Utils.ImageHelper
|
|
||||||
|
|
||||||
fun setSong(
|
fun setSong(
|
||||||
song: Track,
|
song: Track,
|
||||||
checkable: Boolean,
|
checkable: Boolean,
|
||||||
@ -103,7 +108,7 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
|
|||||||
|
|
||||||
if (song.isVideo) {
|
if (song.isVideo) {
|
||||||
artist.isVisible = false
|
artist.isVisible = false
|
||||||
progress.isVisible = false
|
progressIndicator.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new Disposable for the new Subscriptions
|
// 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?) {
|
private fun updateStatus(status: DownloadStatus, progress: Int?) {
|
||||||
if (status != cachedStatus) {
|
progressIndicator.progress = progress ?: 0
|
||||||
cachedStatus = status
|
|
||||||
|
|
||||||
when (status) {
|
if (status == cachedStatus) return
|
||||||
DownloadStatus.DONE -> {
|
cachedStatus = status
|
||||||
statusImage = imageHelper.downloadedImage
|
|
||||||
progress.text = null
|
when (status) {
|
||||||
}
|
DownloadStatus.DONE -> {
|
||||||
DownloadStatus.PINNED -> {
|
showStatusImage(imageHelper.downloadedImage)
|
||||||
statusImage = imageHelper.pinImage
|
}
|
||||||
progress.text = null
|
DownloadStatus.PINNED -> {
|
||||||
}
|
showStatusImage(imageHelper.pinImage)
|
||||||
DownloadStatus.FAILED,
|
}
|
||||||
DownloadStatus.CANCELLED -> {
|
DownloadStatus.FAILED,
|
||||||
statusImage = imageHelper.errorImage
|
DownloadStatus.CANCELLED -> {
|
||||||
progress.text = null
|
showStatusImage(imageHelper.errorImage)
|
||||||
}
|
}
|
||||||
DownloadStatus.DOWNLOADING -> {
|
DownloadStatus.DOWNLOADING -> {
|
||||||
statusImage = imageHelper.downloadingImage[0]
|
showProgress()
|
||||||
}
|
}
|
||||||
else -> {
|
DownloadStatus.RETRYING,
|
||||||
statusImage = null
|
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() {
|
private fun showStatusImage(image: Drawable?) {
|
||||||
progress.post {
|
progressIndicator.isVisible = false
|
||||||
progress.setCompoundDrawablesWithIntrinsicBounds(
|
statusImage.isVisible = true
|
||||||
null, null, statusImage, null
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -375,31 +375,42 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
|
|||||||
var unpinEnabled = false
|
var unpinEnabled = false
|
||||||
var deleteEnabled = false
|
var deleteEnabled = false
|
||||||
var downloadEnabled = false
|
var downloadEnabled = false
|
||||||
|
var isNotInProgress = true
|
||||||
val multipleSelection = viewAdapter.hasMultipleSelection()
|
val multipleSelection = viewAdapter.hasMultipleSelection()
|
||||||
|
|
||||||
var pinnedCount = 0
|
var pinnedCount = 0
|
||||||
|
|
||||||
for (song in selection) {
|
for (song in selection) {
|
||||||
val state = downloader.getDownloadState(song)
|
val state = downloader.getDownloadState(song)
|
||||||
if (state == DownloadStatus.DONE || state == DownloadStatus.PINNED) {
|
when (state) {
|
||||||
deleteEnabled = true
|
DownloadStatus.DONE -> {
|
||||||
}
|
deleteEnabled = true
|
||||||
if (state == DownloadStatus.PINNED) {
|
}
|
||||||
pinnedCount++
|
DownloadStatus.PINNED -> {
|
||||||
unpinEnabled = true
|
deleteEnabled = true
|
||||||
}
|
pinnedCount++
|
||||||
if (state == DownloadStatus.IDLE || state == DownloadStatus.FAILED) {
|
unpinEnabled = true
|
||||||
downloadEnabled = true
|
}
|
||||||
|
DownloadStatus.IDLE, DownloadStatus.FAILED -> {
|
||||||
|
downloadEnabled = true
|
||||||
|
}
|
||||||
|
DownloadStatus.DOWNLOADING,
|
||||||
|
DownloadStatus.QUEUED,
|
||||||
|
DownloadStatus.RETRYING -> {
|
||||||
|
isNotInProgress = false
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playNowButton?.isVisible = enabled
|
playNowButton?.isVisible = enabled
|
||||||
playNextButton?.isVisible = enabled && multipleSelection
|
playNextButton?.isVisible = enabled && multipleSelection
|
||||||
playLastButton?.isVisible = enabled && multipleSelection
|
playLastButton?.isVisible = enabled && multipleSelection
|
||||||
pinButton?.isVisible = (enabled && !isOffline() && selection.size > pinnedCount)
|
pinButton?.isVisible =
|
||||||
unpinButton?.isVisible = (enabled && unpinEnabled)
|
isNotInProgress && enabled && !isOffline() && selection.size > pinnedCount
|
||||||
downloadButton?.isVisible = (enabled && downloadEnabled && !isOffline())
|
unpinButton?.isVisible = isNotInProgress && enabled && unpinEnabled
|
||||||
deleteButton?.isVisible = (enabled && deleteEnabled)
|
downloadButton?.isVisible = isNotInProgress && enabled && downloadEnabled && !isOffline()
|
||||||
|
deleteButton?.isVisible = isNotInProgress && enabled && deleteEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadBackground(save: Boolean) {
|
private fun downloadBackground(save: Boolean) {
|
||||||
|
@ -190,6 +190,7 @@ class Downloader(
|
|||||||
|
|
||||||
// Set correct priority (the lower the number, the higher the priority)
|
// Set correct priority (the lower the number, the higher the priority)
|
||||||
downloadQueue.add(DownloadableTrack(track, item.shouldBePinned(), 0, priority++))
|
downloadQueue.add(DownloadableTrack(track, item.shouldBePinned(), 0, priority++))
|
||||||
|
postState(track, DownloadStatus.QUEUED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +241,9 @@ class Downloader(
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun clearBackground() {
|
fun clearBackground() {
|
||||||
// Clear the pending queue
|
// Clear the pending queue
|
||||||
downloadQueue.clear()
|
while (!downloadQueue.isEmpty()) {
|
||||||
|
postState(downloadQueue.remove().track, DownloadStatus.IDLE)
|
||||||
|
}
|
||||||
|
|
||||||
// Cancel all active downloads with a low priority
|
// Cancel all active downloads with a low priority
|
||||||
for (key in activelyDownloading.keys) {
|
for (key in activelyDownloading.keys) {
|
||||||
@ -272,6 +275,7 @@ class Downloader(
|
|||||||
) continue
|
) continue
|
||||||
val file = DownloadableTrack(track, save, 0, backgroundPriorityCounter++)
|
val file = DownloadableTrack(track, save, 0, backgroundPriorityCounter++)
|
||||||
downloadQueue.add(file)
|
downloadQueue.add(file)
|
||||||
|
postState(track, DownloadStatus.QUEUED)
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.v("downloadBackground Checking Downloads")
|
Timber.v("downloadBackground Checking Downloads")
|
||||||
@ -288,7 +292,7 @@ class Downloader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelDownload(track: Track) {
|
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()
|
activelyDownloading[key]?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,20 +308,21 @@ class Downloader(
|
|||||||
fun getDownloadState(track: Track): DownloadStatus {
|
fun getDownloadState(track: Track): DownloadStatus {
|
||||||
if (Storage.isPathExists(track.getCompleteFile())) return DownloadStatus.DONE
|
if (Storage.isPathExists(track.getCompleteFile())) return DownloadStatus.DONE
|
||||||
if (Storage.isPathExists(track.getPinnedFile())) return DownloadStatus.PINNED
|
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 != null) {
|
||||||
if (key.tryCount > 0) return DownloadStatus.RETRYING
|
if (key.tryCount > 0) return DownloadStatus.RETRYING
|
||||||
return DownloadStatus.DOWNLOADING
|
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
|
return DownloadStatus.IDLE
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CHECK_INTERVAL = 5000L
|
const val CHECK_INTERVAL = 5000L
|
||||||
const val MAX_RETRIES = 5
|
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) {
|
private fun postState(track: Track, state: DownloadStatus, progress: Int? = null) {
|
||||||
@ -579,5 +584,5 @@ class Downloader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class DownloadStatus {
|
enum class DownloadStatus {
|
||||||
IDLE, DOWNLOADING, RETRYING, FAILED, CANCELLED, DONE, PINNED, UNKNOWN
|
IDLE, QUEUED, DOWNLOADING, RETRYING, FAILED, CANCELLED, DONE, PINNED, UNKNOWN
|
||||||
}
|
}
|
||||||
|
@ -140,21 +140,6 @@ object Util {
|
|||||||
toast!!.show()
|
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.
|
* Converts a byte-count to a formatted string suitable for display to the user.
|
||||||
* For instance:
|
* For instance:
|
||||||
|
@ -97,19 +97,28 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
tools:text="Artist" />
|
tools:text="Artist" />
|
||||||
|
|
||||||
<TextView
|
<ImageView
|
||||||
a:id="@+id/song_status"
|
a:id="@+id/song_status_image"
|
||||||
a:layout_width="wrap_content"
|
a:layout_width="25dp"
|
||||||
a:layout_height="wrap_content"
|
a:layout_height="0dp"
|
||||||
a:drawablePadding="6dip"
|
a:layout_marginEnd="10dip"
|
||||||
a:paddingEnd="12dip"
|
app:layout_constraintTop_toTopOf="@id/song_details"
|
||||||
a:textAlignment="textEnd"
|
app:layout_constraintBottom_toTopOf="@id/song_duration"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/song_duration"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="1.0"
|
a:importantForAccessibility="no" />
|
||||||
app:layout_constraintStart_toStartOf="@+id/song_duration"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
tools:text="100%" />
|
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
|
<TextView
|
||||||
a:id="@+id/song_duration"
|
a:id="@+id/song_duration"
|
||||||
@ -128,7 +137,7 @@
|
|||||||
a:layout_width="wrap_content"
|
a:layout_width="wrap_content"
|
||||||
a:layout_height="wrap_content"
|
a:layout_height="wrap_content"
|
||||||
app:barrierDirection="left"
|
app:barrierDirection="left"
|
||||||
app:constraint_referenced_ids="song_status,song_duration" />
|
app:constraint_referenced_ids="song_duration" />
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user