diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index ee1b51cfa0..1f075e99dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -1024,7 +1024,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun showVoiceMessageUI() { - binding?.inputBarRecordingView?.show() + binding?.inputBarRecordingView?.show(lifecycleScope) binding?.inputBar?.alpha = 0.0f val animation = ValueAnimator.ofObject(FloatEvaluator(), 1.0f, 0.0f) animation.duration = 250L diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarRecordingView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarRecordingView.kt index 39d2c43645..6d7281dc47 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarRecordingView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarRecordingView.kt @@ -12,6 +12,11 @@ import android.widget.RelativeLayout import android.widget.TextView import androidx.core.content.res.ResourcesCompat import androidx.core.view.isVisible +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ViewInputBarRecordingBinding import org.thoughtcrime.securesms.util.DateUtils @@ -26,9 +31,7 @@ class InputBarRecordingView : RelativeLayout { private var dotViewAnimation: ValueAnimator? = null private var pulseAnimation: ValueAnimator? = null var delegate: InputBarRecordingViewDelegate? = null - private val updateTimerRunnable = Runnable { - updateTimer() - } + private var timerJob: Job? = null val lockView: LinearLayout get() = binding.lockView @@ -50,9 +53,10 @@ class InputBarRecordingView : RelativeLayout { binding = ViewInputBarRecordingBinding.inflate(LayoutInflater.from(context), this, true) binding.inputBarMiddleContentContainer.disableClipping() binding.inputBarCancelButton.setOnClickListener { hide() } + } - fun show() { + fun show(scope: CoroutineScope) { startTimestamp = Date().time binding.recordButtonOverlayImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_microphone, context.theme)) binding.inputBarCancelButton.alpha = 0.0f @@ -69,7 +73,7 @@ class InputBarRecordingView : RelativeLayout { animateDotView() pulse() animateLockViewUp() - updateTimer() + startTimer(scope) } fun hide() { @@ -86,6 +90,24 @@ class InputBarRecordingView : RelativeLayout { } animation.start() delegate?.handleVoiceMessageUIHidden() + stopTimer() + } + + private fun startTimer(scope: CoroutineScope) { + timerJob?.cancel() + timerJob = scope.launch { + while (isActive) { + val duration = (Date().time - startTimestamp) / 1000L + binding.recordingViewDurationTextView.text = DateUtils.formatElapsedTime(duration) + + delay(500) + } + } + } + + private fun stopTimer() { + timerJob?.cancel() + timerJob = null } private fun animateDotView() { @@ -129,29 +151,6 @@ class InputBarRecordingView : RelativeLayout { animation.start() } - private fun updateTimer() { - val duration = (Date().time - startTimestamp) / 1000L - binding.recordingViewDurationTextView.text = DateUtils.formatElapsedTime(duration) - - if (isAttachedToWindow) { - // Make sure there's only one runnable in the handler at a time. - removeCallbacks(updateTimerRunnable) - - // Should only update the timer if the view is still attached to the window. - // Otherwise, the timer will keep running even after the view is detached. - postDelayed(updateTimerRunnable, 500) - } - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - - if (isVisible) { - // If the view was visible (i.e. recording) when it was detached, start the timer again. - updateTimer() - } - } - fun lock() { val fadeOutAnimation = ValueAnimator.ofObject(FloatEvaluator(), 1.0f, 0.0f) fadeOutAnimation.duration = 250L diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt index f6df7e99e2..db0c4d11cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt @@ -16,6 +16,13 @@ import android.widget.TextView import android.widget.Toast import androidx.annotation.ColorRes import androidx.localbroadcastmanager.content.LocalBroadcastManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.databinding.ActivityPathBinding import org.session.libsession.snode.OnionRequestAPI @@ -182,6 +189,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() { private lateinit var location: Location private var dotAnimationStartDelay: Long = 0 private var dotAnimationRepeatInterval: Long = 0 + private var job: Job? = null private val dotView by lazy { val result = PathDotView(context) @@ -240,25 +248,36 @@ class PathActivity : PassphraseRequiredActionBarActivity() { addView(dotView) } - private fun performAnimation() { - if (isAttachedToWindow) { - expand() + override fun onAttachedToWindow() { + super.onAttachedToWindow() - postDelayed({ - collapse() - postDelayed({ - performAnimation() - }, dotAnimationRepeatInterval) - }, 1000) - } + startAnimation() } - override fun onAttachedToWindow() { - super.onAttachedToWindow() + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + + stopAnimation() + } + + private fun startAnimation() { + job?.cancel() + job = GlobalScope.launch { + withContext(Dispatchers.Main) { + while (isActive) { + delay(dotAnimationStartDelay) + expand() + delay(EXPAND_ANIM_DELAY_MILLS) + collapse() + delay(dotAnimationRepeatInterval) + } + } + } + } - postDelayed({ - performAnimation() - }, dotAnimationStartDelay) + private fun stopAnimation() { + job?.cancel() + job = null } private fun expand() { @@ -276,6 +295,10 @@ class PathActivity : PassphraseRequiredActionBarActivity() { val endColor = context.resources.getColorWithID(endColorID, context.theme) GlowViewUtilities.animateShadowColorChange(dotView, startColor, endColor) } + + companion object { + private const val EXPAND_ANIM_DELAY_MILLS = 1000L + } } // endregion } \ No newline at end of file