@ -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 )
}