Get faster results
This commit is contained in:
@@ -17,16 +17,13 @@
|
||||
package io.element.android.features.login.impl.changeaccountprovider.form
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -38,42 +35,28 @@ class ChangeAccountProviderFormPresenter @Inject constructor(
|
||||
override fun present(): ChangeAccountProviderFormState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
||||
var currentJob: Job? = remember { null }
|
||||
|
||||
val userInput = rememberSaveable {
|
||||
mutableStateOf("")
|
||||
}
|
||||
val userInputResult: MutableState<Async<List<HomeserverData>>> = remember {
|
||||
mutableStateOf(Async.Uninitialized)
|
||||
}
|
||||
val data by homeserverResolver.flow().collectAsState()
|
||||
|
||||
fun handleEvents(event: ChangeAccountProviderFormEvents) {
|
||||
when (event) {
|
||||
is ChangeAccountProviderFormEvents.UserInput -> {
|
||||
currentJob?.cancel()
|
||||
currentJob = localCoroutineScope.userInput(event.input, userInputResult)
|
||||
localCoroutineScope.userInput(event.input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ChangeAccountProviderFormState(
|
||||
userInput = userInput.value,
|
||||
userInputResult = userInputResult.value,
|
||||
userInputResult = data,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
// Could be reworked using LaunchedEffect
|
||||
private fun CoroutineScope.userInput(userInput: String, state: MutableState<Async<List<HomeserverData>>>) = launch {
|
||||
state.value = Async.Uninitialized
|
||||
// Debounce
|
||||
delay(300)
|
||||
state.value = Async.Loading()
|
||||
try {
|
||||
val result = homeserverResolver.resolve(userInput)
|
||||
state.value = Async.Success(result)
|
||||
} catch (error: Throwable) {
|
||||
state.value = Async.Failure(error)
|
||||
}
|
||||
private fun CoroutineScope.userInput(userInput: String) = launch {
|
||||
homeserverResolver.accept(userInput)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,20 @@
|
||||
package io.element.android.features.login.impl.changeaccountprovider.form
|
||||
|
||||
import io.element.android.features.login.impl.changeaccountprovider.form.network.WellknownRequest
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.core.uri.ensureProtocol
|
||||
import io.element.android.libraries.core.uri.isValidUrl
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -33,45 +41,59 @@ class HomeserverResolver @Inject constructor(
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val wellknownRequest: WellknownRequest,
|
||||
) {
|
||||
private val mutableFlow: MutableStateFlow<Async<List<HomeserverData>>> = MutableStateFlow(Async.Uninitialized)
|
||||
|
||||
suspend fun resolve(userInput: String): List<HomeserverData> {
|
||||
return withContext(dispatchers.io) {
|
||||
val cleanedUpUserInput = userInput.trim()
|
||||
if (cleanedUpUserInput.length < 4) {
|
||||
// Wait for more chars
|
||||
emptyList()
|
||||
} else {
|
||||
fun flow(): StateFlow<Async<List<HomeserverData>>> = mutableFlow
|
||||
|
||||
private var currentJob: Job? = null
|
||||
|
||||
suspend fun accept(userInput: String) {
|
||||
currentJob?.cancel()
|
||||
val cleanedUpUserInput = userInput.trim()
|
||||
mutableFlow.tryEmit(Async.Uninitialized)
|
||||
if (cleanedUpUserInput.length > 3) {
|
||||
delay(300)
|
||||
mutableFlow.tryEmit(Async.Loading())
|
||||
withContext(dispatchers.io) {
|
||||
val list = getUrlCandidate(cleanedUpUserInput)
|
||||
val resolvedList = resolveList(userInput, list)
|
||||
|
||||
// If list is empty, and the user as entered an URL, do not block the user.
|
||||
if (resolvedList.isEmpty() && userInput.isValidUrl()) {
|
||||
listOf(
|
||||
HomeserverData(
|
||||
userInput = userInput,
|
||||
homeserverUrl = userInput,
|
||||
isWellknownValid = false
|
||||
)
|
||||
)
|
||||
} else {
|
||||
resolvedList
|
||||
}
|
||||
currentJob = resolveList(userInput, list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun resolveList(userInput: String, list: List<String>): List<HomeserverData> {
|
||||
return coroutineScope {
|
||||
buildList {
|
||||
list.map {
|
||||
async {
|
||||
val isValid = wellknownRequest.execute(it)
|
||||
if (isValid) {
|
||||
add(HomeserverData(userInput, it, true))
|
||||
private fun CoroutineScope.resolveList(userInput: String, list: List<String>): Job {
|
||||
val currentList = mutableListOf<HomeserverData>()
|
||||
return launch {
|
||||
list.map {
|
||||
async {
|
||||
val isValid = tryOrNull { wellknownRequest.execute(it) }.orFalse()
|
||||
if (isValid) {
|
||||
// Emit the list as soon as possible
|
||||
currentList.add(HomeserverData(userInput, it, true))
|
||||
mutableFlow.tryEmit(Async.Success(currentList))
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
.also {
|
||||
// If list is empty, and the user as entered an URL, do not block the user.
|
||||
if (currentList.isEmpty()) {
|
||||
if (userInput.isValidUrl()) {
|
||||
mutableFlow.tryEmit(
|
||||
Async.Success(
|
||||
listOf(
|
||||
HomeserverData(
|
||||
userInput = userInput,
|
||||
homeserverUrl = userInput,
|
||||
isWellknownValid = false
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
mutableFlow.tryEmit(Async.Uninitialized)
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user