diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/newmessage/NewMessageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/newmessage/NewMessageFragment.kt index 16ca590cf1..df0fded3a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/newmessage/NewMessageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/newmessage/NewMessageFragment.kt @@ -183,6 +183,7 @@ fun EnterAccountId( .padding(horizontal = LocalDimensions.current.marginLarge) .fillMaxWidth() .contentDescription(R.string.next), + enabled = state.isNextButtonEnabled, onClick = { callbacks.onContinue() } ) { LoadingArcOr(state.loading) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/newmessage/NewMessageViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/newmessage/NewMessageViewModel.kt index 9698e5d835..a1ffa29782 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/newmessage/NewMessageViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/newmessage/NewMessageViewModel.kt @@ -4,27 +4,29 @@ import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout import network.loki.messenger.R -import nl.komponents.kovenant.ui.failUi -import nl.komponents.kovenant.ui.successUi import org.session.libsession.snode.SnodeAPI import org.session.libsignal.utilities.PublicKeyValidation import org.thoughtcrime.securesms.ui.GetString import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds @HiltViewModel class NewMessageViewModel @Inject constructor( private val application: Application ): AndroidViewModel(application), Callbacks { - private val _state = MutableStateFlow(State()) val state = _state.asStateFlow() @@ -34,11 +36,13 @@ class NewMessageViewModel @Inject constructor( private val _qrErrors = Channel() val qrErrors: Flow = _qrErrors.receiveAsFlow() + private var job: Job? = null + override fun onChange(value: String) { - _state.update { it.copy( - newMessageIdOrOns = value, - error = null - ) } + job?.cancel() + job = null + + _state.update { State(newMessageIdOrOns = value) } } override fun onContinue() { @@ -54,6 +58,8 @@ class NewMessageViewModel @Inject constructor( } private fun createPrivateChatIfPossible(onsNameOrPublicKey: String) { + if (job?.isActive == true) return + if (PublicKeyValidation.isValid(onsNameOrPublicKey, isPrefixRequired = false)) { if (PublicKeyValidation.hasValidPrefix(onsNameOrPublicKey)) { onPublicKey(onsNameOrPublicKey) @@ -64,11 +70,18 @@ class NewMessageViewModel @Inject constructor( // This could be an ONS name _state.update { it.copy(error = null, loading = true) } - SnodeAPI.getSessionID(onsNameOrPublicKey).successUi { hexEncodedPublicKey -> - _state.update { it.copy(loading = false) } - onPublicKey(onsNameOrPublicKey) - }.failUi { exception -> - _state.update { it.copy(loading = false, error = GetString(exception) { it.toMessage() }) } + job = viewModelScope.launch(Dispatchers.IO) { + try { + withTimeout(5.seconds) { + SnodeAPI.getSessionID(onsNameOrPublicKey).get() + } + if (isActive) { + _state.update { it.copy(loading = false) } + onPublicKey(onsNameOrPublicKey) + } + } catch (e: Exception) { + if (isActive) _state.update { it.copy(loading = false, error = GetString(e) { it.toMessage() }) } + } } } } @@ -87,7 +100,9 @@ data class State( val newMessageIdOrOns: String = "", val error: GetString? = null, val loading: Boolean = false -) +) { + val isNextButtonEnabled: Boolean get() = newMessageIdOrOns.isNotBlank() +} sealed interface Event { data class Success(val key: String): Event diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Button.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/Button.kt index e27e3e6d7b..c2963c3188 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Button.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/Button.kt @@ -93,12 +93,14 @@ fun OutlineButton( @Composable fun OutlineButton( modifier: Modifier = Modifier, + enabled: Boolean = true, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, onClick: () -> Unit, content: @Composable () -> Unit = {} ) { OutlinedButton( modifier = modifier.applyButtonSize(), + enabled = enabled, interactionSource = interactionSource, onClick = onClick, border = BorderStroke(1.dp, LocalButtonColor.current),