Animating Download Button (I can't stop)

from downloading to download goes to:
Fill in circle
Fly in a check mark to replace the down arrow (looks like upstream at the moment....am I changing to be like upstream?)
SIKE, it changes right back to the down arrow after a second

...why am I like this

Also I removed the double download completed call to listeners or whatever
oh and cleanup of dlqueue i guess
This commit is contained in:
Jays2Kings 2021-04-16 00:02:37 -04:00
parent 2c143155d6
commit 7f47c5fd4d
9 changed files with 202 additions and 46 deletions

View File

@ -494,7 +494,7 @@ class Downloader(
// Delete successful downloads from queue // Delete successful downloads from queue
if (download.status == Download.DOWNLOADED) { if (download.status == Download.DOWNLOADED) {
// remove downloaded chapter from queue // remove downloaded chapter from queue
queue.remove(download) queue.remove(download, false)
} }
if (areAllDownloadsFinished()) { if (areAllDownloadsFinished()) {
DownloadService.stop(context) DownloadService.stop(context)

View File

@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadStore import eu.kanade.tachiyomi.data.download.DownloadStore
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import rx.Observable
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
@ -32,7 +31,7 @@ class DownloadQueue(
updatedRelay.call(Unit) updatedRelay.call(Unit)
} }
fun remove(download: Download) { fun remove(download: Download, callListeners: Boolean = true) {
val removed = queue.remove(download) val removed = queue.remove(download)
store.remove(download) store.remove(download)
download.setStatusSubject(null) download.setStatusSubject(null)
@ -40,7 +39,9 @@ class DownloadQueue(
if (download.status == Download.DOWNLOADING || download.status == Download.QUEUE) { if (download.status == Download.DOWNLOADING || download.status == Download.QUEUE) {
download.status = Download.NOT_DOWNLOADED download.status = Download.NOT_DOWNLOADED
} }
if (callListeners) {
downloadListeners.forEach { it.updateDownload(download) } downloadListeners.forEach { it.updateDownload(download) }
}
if (removed) { if (removed) {
updatedRelay.call(Unit) updatedRelay.call(Unit)
} }
@ -76,15 +77,6 @@ class DownloadQueue(
updatedRelay.call(Unit) updatedRelay.call(Unit)
} }
fun getActiveDownloads(): Observable<Download> =
Observable.from(this).filter { download -> download.status == Download.DOWNLOADING }
fun getStatusObservable(): Observable<Download> = statusSubject.onBackpressureBuffer()
fun getUpdatedObservable(): Observable<List<Download>> = updatedRelay.onBackpressureBuffer()
.startWith(Unit)
.map { this }
private fun setPagesFor(download: Download) { private fun setPagesFor(download: Download) {
if (download.status == Download.DOWNLOADING) { if (download.status == Download.DOWNLOADING) {
if (download.pages != null) { if (download.pages != null) {
@ -93,12 +85,12 @@ class DownloadQueue(
callListeners(download) callListeners(download)
} }
} }
downloadListeners.forEach { it.updateDownload(download) } callListeners(download)
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) { } else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
setPagesSubject(download.pages, null) setPagesSubject(download.pages, null)
downloadListeners.forEach { it.updateDownload(download) } callListeners(download)
} else { } else {
downloadListeners.forEach { it.updateDownload(download) } callListeners(download)
} }
} }
@ -106,27 +98,6 @@ class DownloadQueue(
downloadListeners.forEach { it.updateDownload(download) } downloadListeners.forEach { it.updateDownload(download) }
} }
fun getProgressObservable(): Observable<Download> {
return statusSubject.onBackpressureBuffer()
.startWith(getActiveDownloads())
.flatMap { download ->
if (download.status == Download.DOWNLOADING) {
val pageStatusSubject = PublishSubject.create<Int>()
setPagesSubject(download.pages, pageStatusSubject)
downloadListeners.forEach { it.updateDownload(download) }
return@flatMap pageStatusSubject
.onBackpressureBuffer()
.filter { it == Page.READY }
.map { download }
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
setPagesSubject(download.pages, null)
downloadListeners.forEach { it.updateDownload(download) }
}
Observable.just(download)
}
.filter { it.status == Download.DOWNLOADING }
}
private fun setPagesSubject(pages: List<Page>?, subject: PublishSubject<Int>?) { private fun setPagesSubject(pages: List<Page>?, subject: PublishSubject<Int>?) {
if (pages != null) { if (pages != null) {
for (page in pages) { for (page in pages) {

View File

@ -1,18 +1,21 @@
package eu.kanade.tachiyomi.ui.download package eu.kanade.tachiyomi.ui.download
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.databinding.DownloadButtonBinding import eu.kanade.tachiyomi.databinding.DownloadButtonBinding
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.widget.EndAnimatorListener
class DownloadButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : class DownloadButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs) { FrameLayout(context, attrs) {
@ -55,6 +58,14 @@ class DownloadButton @JvmOverloads constructor(context: Context, attrs: Attribut
context, context,
R.drawable.ic_check_24dp R.drawable.ic_check_24dp
)?.mutate() )?.mutate()
private val filledAnim = AnimatedVectorDrawableCompat.create(
context,
R.drawable.anim_outline_to_filled
)
private val checkAnim = AnimatedVectorDrawableCompat.create(
context,
R.drawable.anim_dl_to_check_to_dl
)
private var isAnimating = false private var isAnimating = false
private var iconAnimation: ObjectAnimator? = null private var iconAnimation: ObjectAnimator? = null
@ -65,7 +76,7 @@ class DownloadButton @JvmOverloads constructor(context: Context, attrs: Attribut
binding = DownloadButtonBinding.bind(this) binding = DownloadButtonBinding.bind(this)
} }
fun setDownloadStatus(state: Int, progress: Int = 0) { fun setDownloadStatus(state: Int, progress: Int = 0, animated: Boolean = false) {
if (state != Download.DOWNLOADING) { if (state != Download.DOWNLOADING) {
iconAnimation?.cancel() iconAnimation?.cancel()
binding.downloadIcon.alpha = 1f binding.downloadIcon.alpha = 1f
@ -124,9 +135,28 @@ class DownloadButton @JvmOverloads constructor(context: Context, attrs: Attribut
binding.downloadProgress.gone() binding.downloadProgress.gone()
binding.downloadBorder.visible() binding.downloadBorder.visible()
binding.downloadProgressIndeterminate.gone() binding.downloadProgressIndeterminate.gone()
binding.downloadBorder.setImageDrawable(filledCircle)
binding.downloadBorder.drawable.setTint(downloadedColor) binding.downloadBorder.drawable.setTint(downloadedColor)
if (animated) {
binding.downloadBorder.setImageDrawable(filledAnim)
binding.downloadIcon.setImageDrawable(checkAnim)
filledAnim?.start()
val alphaAnimation = ValueAnimator.ofArgb(disabledColor, downloadedTextColor)
alphaAnimation.addUpdateListener { valueAnimator ->
binding.downloadIcon.drawable.setTint(valueAnimator.animatedValue as Int)
}
alphaAnimation.addListener(
EndAnimatorListener {
binding.downloadIcon.drawable.setTint(downloadedTextColor) binding.downloadIcon.drawable.setTint(downloadedTextColor)
checkAnim?.start()
}
)
alphaAnimation.duration = 150
alphaAnimation.start()
binding.downloadBorder.drawable.setTint(downloadedColor)
} else {
binding.downloadBorder.setImageDrawable(filledCircle)
binding.downloadIcon.drawable.setTint(downloadedTextColor)
}
} }
Download.ERROR -> { Download.ERROR -> {
binding.downloadProgress.gone() binding.downloadProgress.gone()

View File

@ -523,7 +523,8 @@ class MangaDetailsController :
getHolder(download.chapter)?.notifyStatus( getHolder(download.chapter)?.notifyStatus(
download.status, download.status,
presenter.isLockedFromSearch, presenter.isLockedFromSearch,
download.progress download.progress,
true
) )
} }

View File

@ -152,12 +152,12 @@ class ChapterHolder(
if (binding.frontView.translationX != 0f) itemView.post { adapter.notifyItemChanged(flexibleAdapterPosition) } if (binding.frontView.translationX != 0f) itemView.post { adapter.notifyItemChanged(flexibleAdapterPosition) }
} }
fun notifyStatus(status: Int, locked: Boolean, progress: Int) = with(binding.downloadButton.downloadButton) { fun notifyStatus(status: Int, locked: Boolean, progress: Int, animated: Boolean = false) = with(binding.downloadButton.downloadButton) {
if (locked) { if (locked) {
gone() gone()
return return
} }
visibleIf(!localSource) visibleIf(!localSource)
setDownloadStatus(status, progress) setDownloadStatus(status, progress, animated)
} }
} }

View File

@ -138,8 +138,8 @@ class RecentMangaHolder(
return item.mch.history.id != null return item.mch.history.id != null
} }
fun notifyStatus(status: Int, progress: Int, isRead: Boolean) { fun notifyStatus(status: Int, progress: Int, isRead: Boolean, animated: Boolean = false) {
binding.downloadButton.downloadButton.setDownloadStatus(status, progress) binding.downloadButton.downloadButton.setDownloadStatus(status, progress, animated)
val isChapterRead = val isChapterRead =
if (adapter.showDownloads == RecentMangaAdapter.ShowRecentsDLs.UnreadOrDownloaded) isRead else false if (adapter.showDownloads == RecentMangaAdapter.ShowRecentsDLs.UnreadOrDownloaded) isRead else false
binding.downloadButton.downloadButton.isVisible = binding.downloadButton.downloadButton.isVisible =

View File

@ -415,7 +415,7 @@ class RecentsController(bundle: Bundle? = null) :
binding.downloadBottomSheet.dlBottomSheet.onUpdateDownloadedPages(download) binding.downloadBottomSheet.dlBottomSheet.onUpdateDownloadedPages(download)
val id = download.chapter.id ?: return val id = download.chapter.id ?: return
val holder = binding.recycler.findViewHolderForItemId(id) as? RecentMangaHolder ?: return val holder = binding.recycler.findViewHolderForItemId(id) as? RecentMangaHolder ?: return
holder.notifyStatus(download.status, download.progress, download.chapter.read) holder.notifyStatus(download.status, download.progress, download.chapter.read, true)
} }
private fun refreshItem(chapterId: Long) { private fun refreshItem(chapterId: Long) {

View File

@ -0,0 +1,125 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:name="main_group"
android:pivotX="12"
android:pivotY="12">
<path
android:name="check_dl"
android:pathData="M 21 7 L 9 19 L 3.5 13.5 L 4.91 12.09 L 9 16.17 L 19.59 5.59 L 21 7 Z"
android:fillColor="#000"
android:fillAlpha="0"
android:strokeWidth="1"/>
</group>
<group android:name="start_group">
<path
android:name="start_dl"
android:pathData="M 11 4 L 13 4 L 13 4 L 13 16 L 18.5 10.5 L 19.92 11.92 L 12 19.84 L 4.08 11.92 L 5.5 10.5 L 11 16 L 11 4 L 11 4"
android:fillColor="#000000"/>
<group
android:name="check_group"
android:translateY="12">
<path
android:name="start_check"
android:pathData="M 19.59 5.59 L 21 7 L 21 7 L 10.479 17.521 L 10.479 17.521 L 9 19 L 9 19 L 3.5 13.5 L 4.91 12.09 L 9 16.17 L 19.325 5.855 L 19.59 5.59"
android:fillColor="#000"
android:fillAlpha="0"
android:strokeWidth="1"/>
</group>
</group>
</vector>
</aapt:attr>
<target android:name="check_dl">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="pathData"
android:startOffset="1250"
android:duration="450"
android:valueFrom="M 19.59 5.59 L 21 7 L 21 7 L 10.479 17.521 L 10.479 17.521 L 9 19 L 9 19 L 3.5 13.5 L 4.91 12.09 L 9 16.17 L 19.325 5.855 L 19.59 5.59"
android:valueTo="M 11 4 L 13 4 L 13 4 L 13 16 L 18.5 10.5 L 19.92 11.92 L 12 19.84 L 4.08 11.92 L 5.5 10.5 L 11 16 L 11 4 L 11 4"
android:valueType="pathType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="fillAlpha"
android:duration="400"
android:valueFrom="0"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="400"
android:duration="100"
android:valueFrom="1"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</set>
</aapt:attr>
</target>
<target android:name="main_group">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="rotation"
android:startOffset="1250"
android:duration="450"
android:valueFrom="0"
android:valueTo="360"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="start_group">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="translateY"
android:duration="400"
android:valueFrom="0"
android:valueTo="-12"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="start_dl">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillAlpha"
android:duration="250"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="start_check">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="150"
android:duration="250"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="400"
android:duration="100"
android:valueFrom="0"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</set>
</aapt:attr>
</target>
</animated-vector>

View File

@ -0,0 +1,29 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:name="path"
android:pathData="M 10 0 C 7.349 0 4.804 1.054 2.929 2.929 C 1.054 4.804 0 7.349 0 10 C 0 12.651 1.054 15.196 2.929 17.071 C 4.804 18.946 7.349 20 10 20 C 12.651 20 15.196 18.946 17.071 17.071 C 18.946 15.196 20 12.651 20 10 C 20 7.349 18.946 4.804 17.071 2.929 C 15.196 1.054 12.651 0 10 0 Z M 10 18 C 7.879 18 5.843 17.157 4.343 15.657 C 2.843 14.157 2 12.121 2 10 C 2 7.879 2.843 5.843 4.343 4.343 C 5.843 2.843 7.879 2 10 2 C 12.121 2 14.157 2.843 15.657 4.343 C 17.157 5.843 18 7.879 18 10 C 18 12.121 17.157 14.157 15.657 15.657 C 14.157 17.157 12.121 18 10 18"
android:fillColor="#000"
android:strokeWidth="1"/>
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="pathData"
android:duration="200"
android:valueFrom="M 10 0 C 7.349 0 4.804 1.054 2.929 2.929 C 1.054 4.804 0 7.349 0 10 C 0 12.651 1.054 15.196 2.929 17.071 C 4.804 18.946 7.349 20 10 20 C 12.651 20 15.196 18.946 17.071 17.071 C 18.946 15.196 20 12.651 20 10 C 20 7.349 18.946 4.804 17.071 2.929 C 15.196 1.054 12.651 0 10 0 Z M 10 18 C 7.879 18 5.843 17.157 4.343 15.657 C 2.843 14.157 2 12.121 2 10 C 2 7.879 2.843 5.843 4.343 4.343 C 5.843 2.843 7.879 2 10 2 C 12.121 2 14.157 2.843 15.657 4.343 C 17.157 5.843 18 7.879 18 10 C 18 12.121 17.157 14.157 15.657 15.657 C 14.157 17.157 12.121 18 10 18"
android:valueTo="M 10 0 C 7.349 0 4.804 1.054 2.929 2.929 C 1.054 4.804 0 7.349 0 10 C 0 12.651 1.054 15.196 2.929 17.071 C 4.804 18.946 7.349 20 10 20 C 12.651 20 15.196 18.946 17.071 17.071 C 18.946 15.196 20 12.651 20 10 C 20 7.349 18.946 4.804 17.071 2.929 C 15.196 1.054 12.651 0 10 0 Z M 10 10 C 10 10 10 10 10 10 C 10 10 10 10 10 10 C 10 10 10 10 10 10 C 10 10 10 10 10 10 C 10 10 10 10 10 10 C 10 10 10 10 10 10 C 10 10 10 10 10 10 C 10 10 10 10 10 10"
android:valueType="pathType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
</animated-vector>