Improved loading animation.
This commit is contained in:
parent
3028f99ced
commit
64461197b6
|
@ -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 {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue