mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-20 21:41:31 +00:00
Make loading animation work when animations are off
This commit is contained in:
parent
eeabd32da4
commit
e139afed6a
@ -1,15 +1,10 @@
|
|||||||
package org.thoughtcrime.securesms.onboarding.loading
|
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.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@ -21,24 +16,11 @@ import org.thoughtcrime.securesms.ui.contentDescription
|
|||||||
import org.thoughtcrime.securesms.ui.h7
|
import org.thoughtcrime.securesms.ui.h7
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun LoadingScreen(state: State) {
|
internal fun LoadingScreen(progress: Float) {
|
||||||
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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
ProgressArc(
|
ProgressArc(
|
||||||
animatable.value,
|
progress,
|
||||||
modifier = Modifier.contentDescription(R.string.AccessibilityId_loading_animation)
|
modifier = Modifier.contentDescription(R.string.AccessibilityId_loading_animation)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
|
@ -47,8 +47,8 @@ class LoadingActivity: BaseActionBarActivity() {
|
|||||||
ApplicationContext.getInstance(this).newAccount = false
|
ApplicationContext.getInstance(this).newAccount = false
|
||||||
|
|
||||||
setComposeContent {
|
setComposeContent {
|
||||||
val state by viewModel.states.collectAsState()
|
val progress by viewModel.progress.collectAsState()
|
||||||
LoadingScreen(state)
|
LoadingScreen(progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
@ -4,15 +4,20 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.buffer
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.filter
|
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.onStart
|
||||||
import kotlinx.coroutines.flow.timeout
|
import kotlinx.coroutines.flow.timeout
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -23,25 +28,43 @@ import kotlin.time.Duration
|
|||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.Duration.Companion.seconds
|
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 ANIMATE_TO_DONE_TIME = 500.milliseconds
|
||||||
private val IDLE_DONE_TIME = 1.seconds
|
private val IDLE_DONE_TIME = 1.seconds
|
||||||
private val TIMEOUT_TIME = 15.seconds
|
private val TIMEOUT_TIME = 15.seconds
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
private val REFRESH_TIME = 50.milliseconds
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
internal class LoadingViewModel @Inject constructor(
|
internal class LoadingViewModel @Inject constructor(
|
||||||
val prefs: TextSecurePreferences
|
val prefs: TextSecurePreferences
|
||||||
): ViewModel() {
|
): ViewModel() {
|
||||||
|
|
||||||
private val _states = MutableStateFlow(State(TIMEOUT_TIME))
|
private val state = MutableStateFlow(State.LOADING)
|
||||||
val states = _states.asStateFlow()
|
|
||||||
|
private val _progress = MutableStateFlow(0f)
|
||||||
|
val progress = _progress.asStateFlow()
|
||||||
|
|
||||||
private val _events = MutableSharedFlow<Event>()
|
private val _events = MutableSharedFlow<Event>()
|
||||||
val events = _events.asSharedFlow()
|
val events = _events.asSharedFlow()
|
||||||
|
|
||||||
init {
|
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) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
TextSecurePreferences.events
|
TextSecurePreferences.events
|
||||||
@ -58,7 +81,7 @@ internal class LoadingViewModel @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun onSuccess() {
|
private suspend fun onSuccess() {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
_states.value = State(ANIMATE_TO_DONE_TIME)
|
state.value = State.SUCCESS
|
||||||
delay(IDLE_DONE_TIME)
|
delay(IDLE_DONE_TIME)
|
||||||
_events.emit(Event.SUCCESS)
|
_events.emit(Event.SUCCESS)
|
||||||
}
|
}
|
||||||
@ -66,6 +89,8 @@ internal class LoadingViewModel @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun onFail() {
|
private suspend fun onFail() {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
state.value = State.FAIL
|
||||||
|
delay(IDLE_DONE_TIME)
|
||||||
_events.emit(Event.TIMEOUT)
|
_events.emit(Event.TIMEOUT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,3 +100,22 @@ sealed interface Event {
|
|||||||
object SUCCESS: Event
|
object SUCCESS: Event
|
||||||
object TIMEOUT: 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)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user