Merge branch 'fix_download_status' into 'develop'

Fixed updating button states in TrackCollectionFragment according to track states

Closes #787 and #785

See merge request ultrasonic/ultrasonic!800
This commit is contained in:
birdbird 2022-08-08 14:01:43 +00:00
commit fbda2980a5
13 changed files with 96 additions and 99 deletions

View File

@ -1,6 +1,5 @@
package org.moire.ultrasonic.adapters
import android.graphics.drawable.AnimationDrawable
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.Checkable
@ -94,8 +93,7 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
setupStarButtons(song, useFiveStarRating)
}
updateStatus(downloader.getDownloadState(song))
updateProgress(0)
updateStatus(downloader.getDownloadState(song), null)
if (useFiveStarRating) {
setFiveStars(entry?.userRating ?: 0)
@ -116,8 +114,7 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
rxBusSubscription!! += RxBus.trackDownloadStateObservable.subscribe {
if (it.id != song.id) return@subscribe
updateStatus(it.state)
updateProgress(it.progress)
updateStatus(it.state, it.progress)
}
}
@ -207,52 +204,49 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
}
}
private fun updateStatus(status: DownloadStatus) {
if (status == cachedStatus) return
cachedStatus = status
private fun updateStatus(status: DownloadStatus, p: Int?) {
if (status != cachedStatus) {
cachedStatus = status
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
}
else -> {
statusImage = null
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 (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 updateProgress(p: Int) {
if (cachedStatus == DownloadStatus.DOWNLOADING) {
progress.text = Util.formatPercentage(p)
} else {
progress.text = null
}
}
private fun updateImages() {
progress.setCompoundDrawablesWithIntrinsicBounds(
null, null, statusImage, null
)
if (statusImage === imageHelper.downloadingImage) {
val frameAnimation = statusImage as AnimationDrawable?
frameAnimation?.setVisible(true, true)
frameAnimation?.start()
progress.post {
progress.setCompoundDrawablesWithIntrinsicBounds(
null, null, statusImage, null
)
}
}

View File

@ -39,7 +39,7 @@ object Utils {
lateinit var errorImage: Drawable
lateinit var pinImage: Drawable
lateinit var downloadedImage: Drawable
lateinit var downloadingImage: Drawable
lateinit var downloadingImage: List<Drawable>
lateinit var playingImage: Drawable
var theme: String
@ -63,7 +63,15 @@ object Utils {
downloadedImage =
ContextCompat.getDrawable(context, R.drawable.stat_sys_download_anim_0)!!
errorImage = ContextCompat.getDrawable(context, R.drawable.ic_baseline_error)!!
downloadingImage = ContextCompat.getDrawable(context, R.drawable.stat_sys_download)!!
downloadingImage = listOf(
ContextCompat.getDrawable(context, R.drawable.stat_sys_download_anim_1)!!,
ContextCompat.getDrawable(context, R.drawable.stat_sys_download_anim_2)!!,
ContextCompat.getDrawable(context, R.drawable.stat_sys_download_anim_3)!!,
ContextCompat.getDrawable(context, R.drawable.stat_sys_download_anim_4)!!,
ContextCompat.getDrawable(context, R.drawable.stat_sys_download_anim_5)!!,
ContextCompat.getDrawable(context, R.drawable.stat_sys_download_anim_6)!!,
ContextCompat.getDrawable(context, R.drawable.stat_sys_download_anim_7)!!,
)
playingImage = ContextCompat.getDrawable(context, R.drawable.ic_stat_play)!!
}
}

View File

@ -21,6 +21,7 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.rxjava3.disposables.CompositeDisposable
import java.util.Collections
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
@ -39,6 +40,8 @@ import org.moire.ultrasonic.model.TrackCollectionModel
import org.moire.ultrasonic.service.DownloadStatus
import org.moire.ultrasonic.service.Downloader
import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.service.RxBus
import org.moire.ultrasonic.service.plusAssign
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
import org.moire.ultrasonic.subsonic.ShareHandler
import org.moire.ultrasonic.subsonic.VideoPlayer
@ -83,6 +86,7 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
internal var cancellationToken: CancellationToken? = null
override val listModel: TrackCollectionModel by viewModels()
private val rxBusSubscription: CompositeDisposable = CompositeDisposable()
/**
* The id of the main layout
@ -143,6 +147,14 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
)
)
// Change the buttons if the status of any selected track changes
rxBusSubscription += RxBus.trackDownloadStateObservable.subscribe {
if (it.progress != null) return@subscribe
val selectedSongs = getSelectedSongs()
if (!selectedSongs.any { song -> song.id == it.id }) return@subscribe
enableButtons(selectedSongs)
}
enableButtons()
// Update the buttons when the selection has changed
@ -261,6 +273,7 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
override fun onDestroyView() {
cancellationToken!!.cancel()
rxBusSubscription.dispose()
super.onDestroyView()
}
@ -356,10 +369,12 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
}
}
@Suppress("ComplexMethod")
internal open fun enableButtons(selection: List<Track> = getSelectedSongs()) {
val enabled = selection.isNotEmpty()
var unpinEnabled = false
var deleteEnabled = false
var downloadEnabled = false
val multipleSelection = viewAdapter.hasMultipleSelection()
var pinnedCount = 0
@ -373,6 +388,9 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
pinnedCount++
unpinEnabled = true
}
if (state == DownloadStatus.IDLE || state == DownloadStatus.FAILED) {
downloadEnabled = true
}
}
playNowButton?.isVisible = enabled
@ -380,7 +398,7 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
playLastButton?.isVisible = enabled && multipleSelection
pinButton?.isVisible = (enabled && !isOffline() && selection.size > pinnedCount)
unpinButton?.isVisible = (enabled && unpinEnabled)
downloadButton?.isVisible = (enabled && !deleteEnabled && !isOffline())
downloadButton?.isVisible = (enabled && downloadEnabled && !isOffline())
deleteButton?.isVisible = (enabled && deleteEnabled)
}

View File

@ -185,7 +185,7 @@ class Downloader(
val existingItem = downloadQueue.firstOrNull { it.track.id == track.id }
if (existingItem != null) {
existingItem.priority = priority + 1
return
continue
}
// Set correct priority (the lower the number, the higher the priority)
@ -267,7 +267,9 @@ class Downloader(
fun downloadBackground(tracks: List<Track>, save: Boolean) {
// By using the counter we ensure that the songs are added in the correct order
for (track in tracks) {
if (downloadQueue.any { t -> t.track.id == track.id }) continue
if (downloadQueue.any { t -> t.track.id == track.id } ||
activelyDownloading.any { t -> t.key.track.id == track.id }
) continue
val file = DownloadableTrack(track, save, 0, backgroundPriorityCounter++)
downloadQueue.add(file)
}
@ -281,7 +283,7 @@ class Downloader(
Storage.delete(track.getPartialFile())
Storage.delete(track.getCompleteFile())
Storage.delete(track.getPinnedFile())
postState(track, DownloadStatus.IDLE, 0)
postState(track, DownloadStatus.IDLE)
Util.scanMedia(track.getPinnedFile())
}
@ -295,7 +297,7 @@ class Downloader(
if (!Storage.isPathExists(pinnedFile)) return
val file = Storage.getFromPath(track.getPinnedFile()) ?: return
Storage.rename(file, track.getCompleteFile())
postState(track, DownloadStatus.DONE, 100)
postState(track, DownloadStatus.DONE)
}
@Suppress("ReturnCount")
@ -318,7 +320,7 @@ class Downloader(
const val REFRESH_INTERVAL = 100
}
private fun postState(track: Track, state: DownloadStatus, progress: Int) {
private fun postState(track: Track, state: DownloadStatus, progress: Int? = null) {
RxBus.trackDownloadStatePublisher.onNext(
RxBus.TrackDownloadState(
track.id,
@ -340,7 +342,7 @@ class Downloader(
try {
if (Storage.isPathExists(item.pinnedFile)) {
Timber.i("%s already exists. Skipping.", item.pinnedFile)
postState(item.track, DownloadStatus.PINNED, 100)
postState(item.track, DownloadStatus.PINNED)
return
}
@ -365,11 +367,11 @@ class Downloader(
} catch (ignore: Exception) {
Timber.w(ignore)
}
postState(item.track, newStatus, 100)
postState(item.track, newStatus)
return
}
postState(item.track, DownloadStatus.DOWNLOADING, 0)
postState(item.track, DownloadStatus.DOWNLOADING)
// Some devices seem to throw error on partial file which doesn't exist
val needsDownloading: Boolean
@ -415,7 +417,7 @@ class Downloader(
outputStream.close()
if (isCancelled) {
postState(item.track, DownloadStatus.CANCELLED, 0)
postState(item.track, DownloadStatus.CANCELLED)
throw RuntimeException(
String.format(
Locale.ROOT, "Download of '%s' was cancelled",
@ -436,14 +438,14 @@ class Downloader(
item.partialFile,
item.pinnedFile
)
postState(item.track, DownloadStatus.PINNED, 100)
postState(item.track, DownloadStatus.PINNED)
Util.scanMedia(item.pinnedFile)
} else {
Storage.rename(
item.partialFile,
item.completeFile
)
postState(item.track, DownloadStatus.DONE, 100)
postState(item.track, DownloadStatus.DONE)
}
} catch (all: Exception) {
outputStream.safeClose()
@ -451,12 +453,12 @@ class Downloader(
Storage.delete(item.pinnedFile)
if (!isCancelled) {
if (item.tryCount < MAX_RETRIES) {
postState(item.track, DownloadStatus.RETRYING, 0)
postState(item.track, DownloadStatus.RETRYING)
item.tryCount++
activelyDownloading.remove(item)
downloadQueue.add(item)
} else {
postState(item.track, DownloadStatus.FAILED, 0)
postState(item.track, DownloadStatus.FAILED)
activelyDownloading.remove(item)
downloadQueue.remove(item)
failedList.add(item)

View File

@ -91,7 +91,7 @@ class RxBus {
data class TrackDownloadState(
val id: String,
val state: DownloadStatus,
val progress: Int
val progress: Int?
)
}

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/drawable/status_icon_background.xml
**
** Copyright 2008, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<animation-list
xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/stat_sys_download_anim_0" android:duration="200" />
<item android:drawable="@drawable/stat_sys_download_anim_1" android:duration="200" />
<item android:drawable="@drawable/stat_sys_download_anim_2" android:duration="200" />
<item android:drawable="@drawable/stat_sys_download_anim_3" android:duration="200" />
<item android:drawable="@drawable/stat_sys_download_anim_4" android:duration="200" />
<item android:drawable="@drawable/stat_sys_download_anim_5" android:duration="200" />
<item android:drawable="@drawable/stat_sys_download_anim_6" android:duration="200" />
<item android:drawable="@drawable/stat_sys_download_anim_7" android:duration="200" />
</animation-list>

View File

@ -8,6 +8,7 @@
android:fillColor="#FFF"
android:pathData="M15,5V3H9v2"/>
<path
android:fillColor="#888"
android:fillColor="#FFF"
android:fillAlpha="150"
android:pathData="M9,5V9H5l7,7 7,-7H15V5M5,18v2h14v-2z"/>
</vector>

View File

@ -8,6 +8,7 @@
android:fillColor="#FFF"
android:pathData="M15,7V3H9v4"/>
<path
android:fillColor="#888"
android:fillColor="#FFF"
android:fillAlpha="150"
android:pathData="M9,7V9H5l7,7 7,-7H15V7M5,18v2h14v-2z"/>
</vector>

View File

@ -8,6 +8,7 @@
android:fillColor="#FFF"
android:pathData="M15,9V3H9v6"/>
<path
android:fillColor="#888"
android:fillColor="#FFF"
android:fillAlpha="150"
android:pathData="M9,9H5l7,7 7,-7H15M5,18v2h14v-2z"/>
</vector>

View File

@ -8,6 +8,7 @@
android:fillColor="#FFF"
android:pathData="M17,11 L19,9H15V3H9V9H5l2,2"/>
<path
android:fillColor="#888"
android:fillColor="#FFF"
android:fillAlpha="150"
android:pathData="m7,11 l5,5 5,-5M5,18v2h14v-2z"/>
</vector>

View File

@ -8,6 +8,7 @@
android:fillColor="#FFF"
android:pathData="M15,13 L19,9H15V3H9V9H5l4,4"/>
<path
android:fillColor="#888"
android:fillColor="#FFF"
android:fillAlpha="150"
android:pathData="m9,13 l3,3 3,-3M5,18v2h14v-2z"/>
</vector>

View File

@ -8,6 +8,7 @@
android:fillColor="#FFF"
android:pathData="M13,15 L19,9H15V3H9V9H5l6,6"/>
<path
android:fillColor="#888"
android:fillColor="#FFF"
android:fillAlpha="150"
android:pathData="m11,15 l1,1 1,-1m-8,3v2h14v-2z"/>
</vector>

View File

@ -8,6 +8,7 @@
android:fillColor="#FFF"
android:pathData="M19,9H15V3H9V9H5l7,7z"/>
<path
android:fillColor="#888"
android:fillColor="#FFF"
android:fillAlpha="150"
android:pathData="m5,18v2h14v-2z"/>
</vector>