Make loading animation work when animations are off

This commit is contained in:
bemusementpark 2024-07-11 14:58:18 +09:30
parent eeabd32da4
commit e139afed6a
3 changed files with 54 additions and 28 deletions

View File

@ -1,15 +1,10 @@
package org.thoughtcrime.securesms.onboarding.loading
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.TweenSpec
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@ -21,24 +16,11 @@ import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.h7
@Composable
internal fun LoadingScreen(state: State) {
val animatable = remember { Animatable(initialValue = 0f, visibilityThreshold = 0.005f) }
LaunchedEffect(state) {
animatable.stop()
animatable.animateTo(
targetValue = 1f,
animationSpec = TweenSpec(
durationMillis = state.duration.inWholeMilliseconds.toInt(),
easing = LinearEasing
)
)
}
internal fun LoadingScreen(progress: Float) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Spacer(modifier = Modifier.weight(1f))
ProgressArc(
animatable.value,
progress,
modifier = Modifier.contentDescription(R.string.AccessibilityId_loading_animation)
)
Text(

View File

@ -47,8 +47,8 @@ class LoadingActivity: BaseActionBarActivity() {
ApplicationContext.getInstance(this).newAccount = false
setComposeContent {
val state by viewModel.states.collectAsState()
LoadingScreen(state)
val progress by viewModel.progress.collectAsState()
LoadingScreen(progress)
}
lifecycleScope.launch {

View File

@ -4,15 +4,20 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.timeout
import kotlinx.coroutines.launch
@ -23,25 +28,43 @@ import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
data class State(val duration: Duration)
enum class State {
LOADING,
SUCCESS,
FAIL
}
private val ANIMATE_TO_DONE_TIME = 500.milliseconds
private val IDLE_DONE_TIME = 1.seconds
private val TIMEOUT_TIME = 15.seconds
@OptIn(FlowPreview::class)
private val REFRESH_TIME = 50.milliseconds
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
@HiltViewModel
internal class LoadingViewModel @Inject constructor(
val prefs: TextSecurePreferences
): ViewModel() {
private val _states = MutableStateFlow(State(TIMEOUT_TIME))
val states = _states.asStateFlow()
private val state = MutableStateFlow(State.LOADING)
private val _progress = MutableStateFlow(0f)
val progress = _progress.asStateFlow()
private val _events = MutableSharedFlow<Event>()
val events = _events.asSharedFlow()
init {
viewModelScope.launch(Dispatchers.IO) {
state.flatMapLatest {
when (it) {
State.LOADING -> progress(0f, 1f, TIMEOUT_TIME)
else -> progress(progress.value, 1f, ANIMATE_TO_DONE_TIME)
}
}.buffer(0, BufferOverflow.DROP_OLDEST)
.collectLatest { _progress.value = it }
}
viewModelScope.launch(Dispatchers.IO) {
try {
TextSecurePreferences.events
@ -58,7 +81,7 @@ internal class LoadingViewModel @Inject constructor(
private suspend fun onSuccess() {
withContext(Dispatchers.Main) {
_states.value = State(ANIMATE_TO_DONE_TIME)
state.value = State.SUCCESS
delay(IDLE_DONE_TIME)
_events.emit(Event.SUCCESS)
}
@ -66,6 +89,8 @@ internal class LoadingViewModel @Inject constructor(
private suspend fun onFail() {
withContext(Dispatchers.Main) {
state.value = State.FAIL
delay(IDLE_DONE_TIME)
_events.emit(Event.TIMEOUT)
}
}
@ -75,3 +100,22 @@ sealed interface Event {
object SUCCESS: Event
object TIMEOUT: Event
}
private fun progress(
init: Float,
target: Float,
time: Duration,
refreshRate: Duration = REFRESH_TIME
): Flow<Float> = flow {
val startMs = System.currentTimeMillis()
val timeMs = time.inWholeMilliseconds
val finishMs = startMs + timeMs
val range = target - init
generateSequence { System.currentTimeMillis() }.takeWhile { it < finishMs }.forEach {
emit((it - startMs) * range / timeMs + init)
delay(refreshRate)
}
emit(target)
}