Improved loading animation.

This commit is contained in:
Kevin Gorham 2020-01-15 10:32:10 -05:00
parent 3028f99ced
commit 64461197b6
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
5 changed files with 244 additions and 32 deletions

View File

@ -1,6 +1,8 @@
package cash.z.ecc.android.ui.home
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.PorterDuff
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -10,17 +12,14 @@ import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentHomeBinding
import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.ext.disabledIf
import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ext.onClickNavTo
import cash.z.ecc.android.ext.toColoredSpan
import cash.z.ecc.android.ext.*
import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.home.HomeFragment.BannerAction.*
import cash.z.ecc.android.ui.send.SendViewModel
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.NO_SEED
import cash.z.wallet.sdk.Synchronizer
import cash.z.wallet.sdk.Synchronizer.Status.SYNCING
import cash.z.wallet.sdk.Synchronizer.Status.*
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.ext.convertZecToZatoshi
import cash.z.wallet.sdk.ext.safelyConvertToBigDecimal
@ -40,6 +39,8 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
private val sendViewModel: SendViewModel by activityViewModel()
private val viewModel: HomeViewModel by viewModel()
lateinit var snake: MagicSnakeLoader
override fun inflate(inflater: LayoutInflater): FragmentHomeBinding =
FragmentHomeBinding.inflate(inflater)
@ -50,6 +51,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
override fun onAttach(context: Context) {
twig("HomeFragment.onAttach")
twig("ZZZ")
twig("ZZZ")
twig("ZZZ")
twig("ZZZ ===================== HOME FRAGMENT CREATED ==================================")
super.onAttach(context)
// this will call startSync either now or later (after initializing with newly created seed)
@ -99,6 +104,8 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
onSend()
}
setSendAmount("0", false)
snake = MagicSnakeLoader(binding.lottieButtonLoading)
}
binding.buttonNumberPadBack.setOnLongClickListener {
@ -106,10 +113,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
true
}
// if (::uiModel.isInitialized) {
// twig("uiModel exists!")
// onModelUpdated(HomeViewModel.UiModel(), uiModel)
// }
if (::uiModel.isInitialized) {
twig("uiModel exists!")
onModelUpdated(null, uiModel)
}
}
private fun onClearAmount() {
@ -179,7 +186,13 @@ twig("onResume (D)")
fun setSendEnabled(enabled: Boolean) {
binding.buttonSendAmount.apply {
isEnabled = enabled
// backgroundTintList = ColorStateList.valueOf( resources.getColor( if(enabled) R.color.colorPrimary else R.color.zcashWhite_24) )
if (enabled) {
// setTextColor(resources.getColorStateList(R.color.selector_button_text_dark))
binding.lottieButtonLoading.alpha = 1.0f
} else {
// setTextColor(R.color.zcashGray.toAppColor())
binding.lottieButtonLoading.alpha = 0.32f
}
}
}
@ -189,22 +202,35 @@ twig("onResume (D)")
return
}
snake.isSynced = uiModel.isSynced
if (!uiModel.isSynced) {
snake.downloadProgress = uiModel.downloadProgress
snake.scanProgress = uiModel.scanProgress
}
val sendText = when {
uiModel.isSynced -> "SEND AMOUNT"
uiModel.isSynced -> if (uiModel.hasFunds) "SEND AMOUNT" else "NO FUNDS AVAILABLE"
uiModel.status == Synchronizer.Status.DISCONNECTED -> "DISCONNECTED"
uiModel.status == Synchronizer.Status.STOPPED -> "IDLE"
uiModel.isDownloading -> "Downloading . . . ${uiModel.downloadProgress}%"
uiModel.isDownloading -> "Downloading . . . ${snake.downloadProgress}%"
uiModel.isValidating -> "Validating . . ."
uiModel.isScanning -> "Scanning . . . ${uiModel.scanProgress}%"
uiModel.isScanning -> "Scanning . . . ${snake.scanProgress}%"
else -> "Updating"
}
binding.lottieButtonLoading.progress = if (uiModel.isSynced) 1.0f else uiModel.totalProgress * 0.82f // line fully closes at 82% mark
// binding.lottieButtonLoading.progress = if (uiModel.isSynced) 1.0f else uiModel.totalProgress * 0.82f // line fully closes at 82% mark
binding.buttonSendAmount.text = sendText
twig("Lottie progress set to ${binding.lottieButtonLoading.progress} (isSynced? ${uiModel.isSynced})")
// twig("Lottie progress set to ${binding.lottieButtonLoading.progress} (isSynced? ${uiModel.isSynced})")
twig("Send button set to: $sendText")
val resId = if (uiModel.isSynced) R.color.selector_button_text_dark else R.color.selector_button_text_light
binding.buttonSendAmount.setTextColor(resources.getColorStateList(resId))
// if (uiModel.status == DISCONNECTED || uiModel.status == STOPPED) {
// binding.buttonSendAmount.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.zcashGray))
// } else {
// binding.buttonSendAmount.backgroundTintList = null
// }
}
/**
@ -249,11 +275,11 @@ twig("onResume (D)")
// Private UI Events
//
private fun onModelUpdated(old: HomeViewModel.UiModel, new: HomeViewModel.UiModel) {
private fun onModelUpdated(old: HomeViewModel.UiModel?, new: HomeViewModel.UiModel) {
twig("onModelUpdated: $new")
uiModel = new
twig("onModelUpdated (A)")
if (old.pendingSend != new.pendingSend) {
if (old?.pendingSend != new.pendingSend) {
twig("onModelUpdated (B)")
setSendAmount(new.pendingSend)
twig("onModelUpdated (C)")
@ -262,7 +288,8 @@ twig("onModelUpdated (D)")
// TODO: handle stopped and disconnected flows
setProgress(uiModel) // TODO: we may not need to separate anymore
twig("onModelUpdated (E)")
if (new.status == SYNCING) onSyncing(new) else onSynced(new)
// if (new.status = SYNCING) onSyncing(new) else onSynced(new)
if (new.status == SYNCED) onSynced(new) else onSyncing(new)
twig("onModelUpdated (F)")
setSendEnabled(new.isSendEnabled)
twig("onModelUpdated (G) sendEnabled? ${new.isSendEnabled}")
@ -274,7 +301,7 @@ twig("onModelUpdated (G) sendEnabled? ${new.isSendEnabled}")
}
private fun onSynced(uiModel: HomeViewModel.UiModel) {
binding.lottieButtonLoading.progress = 1.0f
snake.isSynced = true
if (!uiModel.hasBalance) {
onNoFunds()
} else {

View File

@ -3,8 +3,7 @@ package cash.z.ecc.android.ui.home
import androidx.lifecycle.ViewModel
import cash.z.wallet.sdk.SdkSynchronizer
import cash.z.wallet.sdk.Synchronizer
import cash.z.wallet.sdk.Synchronizer.Status.DISCONNECTED
import cash.z.wallet.sdk.Synchronizer.Status.SYNCED
import cash.z.wallet.sdk.Synchronizer.Status.*
import cash.z.wallet.sdk.block.CompactBlockProcessor
import cash.z.wallet.sdk.ext.ZcashSdk.MINERS_FEE_ZATOSHI
import cash.z.wallet.sdk.ext.ZcashSdk.ZATOSHI_PER_ZEC
@ -54,6 +53,7 @@ class HomeViewModel @Inject constructor() : ViewModel() {
}
}
}
twig("initializing view models stream")
uiModels = synchronizer.run {
combine(status, processorInfo, balances, zec) { s, p, b, z->
UiModel(s, p, b.availableZatoshi, b.totalZatoshi, z)
@ -88,16 +88,9 @@ class HomeViewModel @Inject constructor() : ViewModel() {
val isSendEnabled: Boolean get() = isSynced && hasFunds
// Processor Info
val isDownloading: Boolean
get() = status != SYNCED
&& processorInfo.lastDownloadedHeight < processorInfo.lastDownloadRange.last
val isScanning: Boolean
get() = status != SYNCED
&& processorInfo.lastScannedHeight < processorInfo.lastScanRange.last
&& processorInfo.lastScannedHeight > processorInfo.lastScanRange.first
val isValidating: Boolean
get() = (status != SYNCED)
&& (!isScanning && !isDownloading)
val isDownloading = status == DOWNLOADING
val isScanning = status == SCANNING
val isValidating = status == VALIDATING
val downloadProgress: Int get() {
return processorInfo.run {
if (lastDownloadRange.isEmpty()) {

View File

@ -0,0 +1,191 @@
package cash.z.ecc.android.ui.home
import android.animation.ValueAnimator
import cash.z.wallet.sdk.ext.twig
import com.airbnb.lottie.LottieAnimationView
class MagicSnakeLoader(
val lottie: LottieAnimationView,
private val scanningStartFrame: Int = 100,
private val scanningEndFrame: Int = 175,
val totalFrames: Int = 200
) : ValueAnimator.AnimatorUpdateListener {
private var isPaused: Boolean = true
private var isStarted: Boolean = false
var isSynced: Boolean = false
set(value) {
twig("ZZZ isSynced=$value isStarted=$isStarted")
if (value && !isStarted) {
twig("ZZZ isSynced=$value TURBO sync")
lottie.progress = 1.0f
field = value
return
}
// it is started but it hadn't reached the synced state yet
if (value && !field) {
twig("ZZZ synced was $field but now is $value so playing to completion since we are now synced")
field = value
playToCompletion()
} else {
field = value
twig("ZZZ isSynced=$value and lottie.progress=${lottie.progress}")
}
}
var scanProgress: Int = 0
set(value) {
field = value
twig("ZZZ scanProgress=$value")
if (value > 0) {
startMaybe()
onScanUpdated()
}
}
var downloadProgress: Int = 0
set(value) {
field = value
twig("ZZZ downloadProgress=$value")
if (value > 0) startMaybe()
}
private fun startMaybe() {
if (!isSynced && !isStarted) lottie.postDelayed({
// after some delay, if we're still not synced then we better start animating (unless we already are)!
if (!isSynced && isPaused) {
twig("ZZZ yes start!")
lottie.resumeAnimation()
isPaused = false
isStarted = true
} else {
twig("ZZZ I would have started but we're already synced!")
}
}, 200L).also { twig("ZZZ startMaybe???") }
}
// set(value) {
// field = value
// if (value in 1..99 && isStopped) {
// lottie.playAnimation()
// isStopped = false
// } else if (value >= 100) {
// isStopped = true
// }
// }
private val isDownloading get() = downloadProgress in 1..99
private val isScanning get() = scanProgress in 1..99
init {
lottie.addAnimatorUpdateListener(this)
}
// downloading = true
// lottieAnimationView.playAnimation()
// lottieAnimationView.addAnimatorUpdateListener { valueAnimator ->
// // Set animation progress
// val progress = (valueAnimator.animatedValue as Float * 100).toInt()
// progressTv.text = "Progress: $progress%"
//
// if (downloading && progress >= 40) {
// lottieAnimationView.progress = 0f
// }
// }
override fun onAnimationUpdate(animation: ValueAnimator) {
if (isSynced || isPaused) {
// playToCompletion()
return
}
twig("ZZZ")
twig("ZZZ\t\tonAnimationUpdate(${animation.animatedValue})")
// if we are scanning, then set the animation progress, based on the scan progress
// if we're not scanning, then we're looping
animation.currentFrame().let { frame ->
if (isDownloading) allowLoop(frame) else applyScanProgress(frame)
}
}
private val acceptablePauseFrames = arrayOf(33,34,67,68,99)
private fun applyScanProgress(frame: Int) {
twig("ZZZ applyScanProgress($frame) : isPaused=$isPaused isStarted=$isStarted min=${lottie.minFrame} max=${lottie.maxFrame}")
// don't hardcode the progress until the loop animation has completed, cleanly
if (isPaused) {
onScanUpdated()
} else {
// once we're ready to show scan progress, do it! Don't do extra loops.
if (frame >= scanningStartFrame || frame in acceptablePauseFrames) {
twig("ZZZ pausing so we can scan! ${if(frame<scanningStartFrame) "WE STOPPED EARLY!" else ""}")
pause()
}
}
}
private fun onScanUpdated() {
twig("ZZZ onScanUpdated : isPaused=$isPaused")
if (isSynced) {
// playToCompletion()
return
}
if (isPaused && isStarted) {
// move forward within the scan range, proportionate to how much scanning is complete
val scanRange = scanningEndFrame - scanningStartFrame
val scanRangeProgress = scanProgress.toFloat() / 100.0f * scanRange.toFloat()
lottie.progress = (scanningStartFrame.toFloat() + scanRangeProgress) / totalFrames
twig("ZZZ onScanUpdated : scanRange=$scanRange scanRangeProgress=$scanRangeProgress lottie.progress=${(scanningStartFrame.toFloat() + scanRangeProgress)}/$totalFrames=${lottie.progress}")
}
}
private fun playToCompletion() {
removeLoops()
twig("ZZZ playing to completion")
unpause()
}
private fun removeLoops() {
lottie.frame.let {frame ->
if (frame in 33..67) {
twig("ZZZ removing 1 loop!")
lottie.frame = frame + 34
} else if (frame in 0..33) {
twig("ZZZ removing 2 loops!")
lottie.frame = frame + 67
}
}
}
private fun allowLoop(frame: Int) {
twig("ZZZ allowLoop($frame) : isPaused=$isPaused")
unpause()
if (frame >= scanningStartFrame) {
twig("ZZZ resetting to 0f (LOOPING)")
lottie.progress = 0f
}
}
fun unpause() {
if (isPaused) {
twig("ZZZ unpausing")
lottie.resumeAnimation()
isPaused = false
}
}
fun pause() {
if (!isPaused) {
twig("ZZZ pausing")
lottie.pauseAnimation()
isPaused = true
}
}
private fun ValueAnimator.currentFrame(): Int {
return ((animatedValue as Float) * totalFrames).toInt()
}
}

View File

@ -281,7 +281,7 @@
app:layout_constraintVertical_bias="0.38"
app:lottie_autoPlay="false"
app:lottie_loop="false"
app:lottie_rawRes="@raw/lottie_button_loading" />
app:lottie_rawRes="@raw/lottie_button_loading_new" />
<TextView
android:id="@+id/button_send_amount"

File diff suppressed because one or more lines are too long