Fix progress display hiding way before registration navigation.

Fixes #13850
Closes #13898

Co-authored-by: Sagar <sagar.0dev@gmail.com>
This commit is contained in:
Cody Henthorne 2025-01-15 12:47:43 -05:00 committed by GitHub
parent 93604f53d4
commit e9d80f4379
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 78 additions and 127 deletions

View file

@ -81,7 +81,6 @@ internal class ConfirmSvrPinFragment : BaseSvrPinFragment<ConfirmSvrPinViewModel
SaveAnimation.NONE -> confirm.cancelSpinning() SaveAnimation.NONE -> confirm.cancelSpinning()
SaveAnimation.LOADING -> confirm.setSpinning() SaveAnimation.LOADING -> confirm.setSpinning()
SaveAnimation.SUCCESS -> { SaveAnimation.SUCCESS -> {
confirm.cancelSpinning()
requireActivity().setResult(Activity.RESULT_OK) requireActivity().setResult(Activity.RESULT_OK)
closeNavGraphBranch() closeNavGraphBranch()
RegistrationUtil.maybeMarkRegistrationComplete() RegistrationUtil.maybeMarkRegistrationComplete()

View file

@ -83,7 +83,6 @@ class RegistrationActivity : BaseActivity() {
if (!needsProfile && !needsPin) { if (!needsProfile && !needsPin) {
sharedViewModel.completeRegistration() sharedViewModel.completeRegistration()
} }
sharedViewModel.setInProgress(false)
val startIntent = MainActivity.clearTop(this).apply { val startIntent = MainActivity.clearTop(this).apply {
if (needsPin) { if (needsPin) {

View file

@ -86,13 +86,8 @@ class RegistrationViewModel : ViewModel() {
private val password = Util.getSecret(18) private val password = Util.getSecret(18)
private val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> private val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
Log.w(TAG, "CoroutineExceptionHandler invoked.", exception) Log.w(TAG, "CoroutineExceptionHandler invoked!")
store.update { handleGenericError(exception)
it.copy(
networkError = exception,
inProgress = false
)
}
} }
val uiState = store.asLiveData() val uiState = store.asLiveData()
@ -238,8 +233,7 @@ class RegistrationViewModel : ViewModel() {
store.update { store.update {
it.copy( it.copy(
canSkipSms = true, canSkipSms = true,
isReRegister = true, isReRegister = true
inProgress = false
) )
} }
return return
@ -263,8 +257,7 @@ class RegistrationViewModel : ViewModel() {
isReRegister = true, isReRegister = true,
canSkipSms = true, canSkipSms = true,
svr2AuthCredentials = svrCredentialsResult.svr2Credentials, svr2AuthCredentials = svrCredentialsResult.svr2Credentials,
svr3AuthCredentials = svrCredentialsResult.svr3Credentials, svr3AuthCredentials = svrCredentialsResult.svr3Credentials
inProgress = false
) )
} }
return@launch return@launch
@ -315,7 +308,7 @@ class RegistrationViewModel : ViewModel() {
if (e164 == null) { if (e164 == null) {
Log.w(TAG, "Phone number was null after confirmation.") Log.w(TAG, "Phone number was null after confirmation.")
onErrorOccurred() setInProgress(false)
return return
} }
@ -404,8 +397,7 @@ class RegistrationViewModel : ViewModel() {
nextVerificationAttempt = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.body.nextVerificationAttempt), nextVerificationAttempt = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.body.nextVerificationAttempt),
allowedToRequestCode = networkResult.body.allowedToRequestCode, allowedToRequestCode = networkResult.body.allowedToRequestCode,
challengesRequested = Challenge.parse(networkResult.body.requestedInformation), challengesRequested = Challenge.parse(networkResult.body.requestedInformation),
verified = networkResult.body.verified, verified = networkResult.body.verified
inProgress = false
) )
} }
}, },
@ -451,12 +443,6 @@ class RegistrationViewModel : ViewModel() {
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a push challenge token.") } val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a push challenge token.") }
if (!Challenge.parse(session.body.requestedInformation).contains(Challenge.PUSH)) { if (!Challenge.parse(session.body.requestedInformation).contains(Challenge.PUSH)) {
Log.d(TAG, "Push submission no longer necessary, bailing.")
store.update {
it.copy(
inProgress = false
)
}
return@launch bail { Log.i(TAG, "Push challenge token no longer needed, bailing.") } return@launch bail { Log.i(TAG, "Push challenge token no longer needed, bailing.") }
} }
@ -486,8 +472,7 @@ class RegistrationViewModel : ViewModel() {
nextSmsTimestamp = sessionResult.nextSmsTimestamp, nextSmsTimestamp = sessionResult.nextSmsTimestamp,
nextCallTimestamp = sessionResult.nextCallTimestamp, nextCallTimestamp = sessionResult.nextCallTimestamp,
isAllowedToRequestCode = sessionResult.allowedToRequestCode, isAllowedToRequestCode = sessionResult.allowedToRequestCode,
challengesRequested = emptyList(), challengesRequested = emptyList()
inProgress = false
) )
} }
return true return true
@ -498,8 +483,7 @@ class RegistrationViewModel : ViewModel() {
store.update { store.update {
it.copy( it.copy(
registrationCheckpoint = RegistrationCheckpoint.CHALLENGE_RECEIVED, registrationCheckpoint = RegistrationCheckpoint.CHALLENGE_RECEIVED,
challengesRequested = sessionResult.challenges, challengesRequested = sessionResult.challenges
inProgress = false
) )
} }
return false return false
@ -534,9 +518,10 @@ class RegistrationViewModel : ViewModel() {
is AlreadyVerified -> Log.i(TAG, "Received AlreadyVerified", sessionResult.getCause()) is AlreadyVerified -> Log.i(TAG, "Received AlreadyVerified", sessionResult.getCause())
} }
setInProgress(false)
store.update { store.update {
it.copy( it.copy(
inProgress = false,
sessionStateError = sessionResult sessionStateError = sessionResult
) )
} }
@ -548,6 +533,7 @@ class RegistrationViewModel : ViewModel() {
*/ */
private suspend fun handleRegistrationResult(context: Context, registrationData: RegistrationData, registrationResult: RegisterAccountResult, reglockEnabled: Boolean): Boolean { private suspend fun handleRegistrationResult(context: Context, registrationData: RegistrationData, registrationResult: RegisterAccountResult, reglockEnabled: Boolean): Boolean {
Log.v(TAG, "handleRegistrationResult()") Log.v(TAG, "handleRegistrationResult()")
var stayInProgress = false
when (registrationResult) { when (registrationResult) {
is RegisterAccountResult.Success -> { is RegisterAccountResult.Success -> {
Log.i(TAG, "Register account result: Success! Registration lock: $reglockEnabled") Log.i(TAG, "Register account result: Success! Registration lock: $reglockEnabled")
@ -567,6 +553,7 @@ class RegistrationViewModel : ViewModel() {
is RegisterAccountResult.RegistrationLocked -> { is RegisterAccountResult.RegistrationLocked -> {
Log.i(TAG, "Account is registration locked!", registrationResult.getCause()) Log.i(TAG, "Account is registration locked!", registrationResult.getCause())
stayInProgress = true
} }
is RegisterAccountResult.SvrWrongPin -> { is RegisterAccountResult.SvrWrongPin -> {
@ -582,9 +569,9 @@ class RegistrationViewModel : ViewModel() {
is RegisterAccountResult.ValidationError, is RegisterAccountResult.ValidationError,
is RegisterAccountResult.UnknownError -> Log.i(TAG, "Received error when trying to register!", registrationResult.getCause()) is RegisterAccountResult.UnknownError -> Log.i(TAG, "Received error when trying to register!", registrationResult.getCause())
} }
setInProgress(false)
store.update { store.update {
it.copy( it.copy(
inProgress = stayInProgress,
registerAccountError = registrationResult registerAccountError = registrationResult
) )
} }
@ -636,7 +623,6 @@ class RegistrationViewModel : ViewModel() {
updateSvrTriesRemaining(0) updateSvrTriesRemaining(0)
setUserSkippedReRegisterFlow(true) setUserSkippedReRegisterFlow(true)
} }
setInProgress(false)
} }
return return
} }
@ -647,19 +633,17 @@ class RegistrationViewModel : ViewModel() {
Log.d(TAG, "Found recovery password, attempting to re-register.") Log.d(TAG, "Found recovery password, attempting to re-register.")
viewModelScope.launch(context = coroutineExceptionHandler) { viewModelScope.launch(context = coroutineExceptionHandler) {
verifyReRegisterInternal(context, pin, SignalStore.svr.masterKey) verifyReRegisterInternal(context, pin, SignalStore.svr.masterKey)
setInProgress(false)
} }
} else { } else {
Log.d(TAG, "Entered PIN did not match local PIN hash.") Log.d(TAG, "Entered PIN did not match local PIN hash.")
wrongPinHandler() wrongPinHandler()
setInProgress(false)
} }
return return
} }
Log.w(TAG, "Could not get credentials to skip SMS registration, aborting!") Log.w(TAG, "Could not get credentials to skip SMS registration, aborting!")
store.update { store.update {
it.copy(canSkipSms = false, inProgress = false) it.copy(canSkipSms = false)
} }
} }
@ -857,8 +841,7 @@ class RegistrationViewModel : ViewModel() {
store.update { store.update {
it.copy( it.copy(
registrationCheckpoint = RegistrationCheckpoint.LOCAL_REGISTRATION_COMPLETE, registrationCheckpoint = RegistrationCheckpoint.LOCAL_REGISTRATION_COMPLETE
inProgress = false
) )
} }
} }
@ -898,14 +881,6 @@ class RegistrationViewModel : ViewModel() {
return RegistrationData(code, e164, password, RegistrationRepository.getRegistrationId(), RegistrationRepository.getProfileKey(e164), currentState.fcmToken, RegistrationRepository.getPniRegistrationId(), recoveryPassword) return RegistrationData(code, e164, password, RegistrationRepository.getRegistrationId(), RegistrationRepository.getProfileKey(e164), currentState.fcmToken, RegistrationRepository.getPniRegistrationId(), recoveryPassword)
} }
/**
* This is a generic error UI handler that re-enables the UI so that the user can recover from errors.
* Do not forget to log any errors when calling this method!
*/
private fun onErrorOccurred() {
setInProgress(false)
}
/** /**
* Used for early returns in order to end the in-progress visual state, as well as print a log message explaining what happened. * Used for early returns in order to end the in-progress visual state, as well as print a log message explaining what happened.
* *

View file

@ -198,6 +198,7 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
object : AssertedSuccessListener<Boolean>() { object : AssertedSuccessListener<Boolean>() {
override fun onSuccess(result: Boolean?) { override fun onSuccess(result: Boolean?) {
findNavController().safeNavigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining)) findNavController().safeNavigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining))
sharedViewModel.setInProgress(false)
} }
} }
) )
@ -265,6 +266,7 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
private fun popBackStack() { private fun popBackStack() {
sharedViewModel.setRegistrationCheckpoint(RegistrationCheckpoint.PUSH_NETWORK_AUDITED) sharedViewModel.setRegistrationCheckpoint(RegistrationCheckpoint.PUSH_NETWORK_AUDITED)
NavHostFragment.findNavController(this).popBackStack() NavHostFragment.findNavController(this).popBackStack()
sharedViewModel.setInProgress(false)
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)

View file

@ -35,6 +35,7 @@ import com.google.android.material.textfield.TextInputEditText
import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.NumberParseException
import com.google.i18n.phonenumbers.PhoneNumberUtil import com.google.i18n.phonenumbers.PhoneNumberUtil
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber
import org.signal.core.util.ThreadUtil
import org.signal.core.util.isNotNullOrBlank import org.signal.core.util.isNotNullOrBlank
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.LoggingFragment import org.thoughtcrime.securesms.LoggingFragment
@ -114,7 +115,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
sharedViewModel.uiState.observe(viewLifecycleOwner) { sharedState -> sharedViewModel.uiState.observe(viewLifecycleOwner) { sharedState ->
presentRegisterButton(sharedState) presentRegisterButton(sharedState)
presentProgressBar(sharedState.inProgress, sharedState.isReRegister) updateEnabledControls(sharedState.inProgress, sharedState.isReRegister)
sharedState.networkError?.let { sharedState.networkError?.let {
presentNetworkError(it) presentNetworkError(it)
@ -400,6 +401,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
private fun presentRegistrationLocked(timeRemaining: Long) { private fun presentRegistrationLocked(timeRemaining: Long) {
findNavController().safeNavigate(EnterPhoneNumberFragmentDirections.actionPhoneNumberRegistrationLock(timeRemaining)) findNavController().safeNavigate(EnterPhoneNumberFragmentDirections.actionPhoneNumberRegistrationLock(timeRemaining))
sharedViewModel.setInProgress(false)
} }
private fun presentRateLimitedDialog() { private fun presentRateLimitedDialog() {
@ -408,10 +410,12 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
private fun presentAccountLocked() { private fun presentAccountLocked() {
findNavController().safeNavigate(EnterPhoneNumberFragmentDirections.actionPhoneNumberAccountLocked()) findNavController().safeNavigate(EnterPhoneNumberFragmentDirections.actionPhoneNumberAccountLocked())
ThreadUtil.postToMain { sharedViewModel.setInProgress(false) }
} }
private fun moveToCaptcha() { private fun moveToCaptcha() {
findNavController().safeNavigate(EnterPhoneNumberFragmentDirections.actionRequestCaptcha()) findNavController().safeNavigate(EnterPhoneNumberFragmentDirections.actionRequestCaptcha())
ThreadUtil.postToMain { sharedViewModel.setInProgress(false) }
} }
private fun presentRemoteErrorDialog(message: String, positiveButtonListener: DialogInterface.OnClickListener? = null) { private fun presentRemoteErrorDialog(message: String, positiveButtonListener: DialogInterface.OnClickListener? = null) {
@ -491,12 +495,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
} }
} }
private fun presentProgressBar(showProgress: Boolean, isReRegister: Boolean) { private fun updateEnabledControls(showProgress: Boolean, isReRegister: Boolean) {
if (showProgress) {
binding.registerButton.setSpinning()
} else {
binding.registerButton.cancelSpinning()
}
binding.countryCode.isEnabled = !showProgress binding.countryCode.isEnabled = !showProgress
binding.number.isEnabled = !showProgress binding.number.isEnabled = !showProgress
binding.cancelButton.visible = !showProgress && isReRegister binding.cancelButton.visible = !showProgress && isReRegister

View file

@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.registration.ui.RegistrationViewModel
import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.SupportEmailUtil import org.thoughtcrime.securesms.util.SupportEmailUtil
import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
import org.thoughtcrime.securesms.util.navigation.safeNavigate import org.thoughtcrime.securesms.util.navigation.safeNavigate
class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration_pin_restore_entry_v2) { class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration_pin_restore_entry_v2) {
@ -76,21 +77,24 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration
binding.pinRestoreKeyboardToggle.setIconResource(getPinEntryKeyboardType().other.iconResource) binding.pinRestoreKeyboardToggle.setIconResource(getPinEntryKeyboardType().other.iconResource)
registrationViewModel.uiState.observe(viewLifecycleOwner, ::updateViewState) LiveDataUtil
.combineLatest(registrationViewModel.uiState, reRegisterViewModel.uiState) { reg, rereg -> reg to rereg }
.observe(viewLifecycleOwner) { (registrationState, reRegisterState) -> updateViewState(registrationState, reRegisterState) }
} }
private fun updateViewState(state: RegistrationState) { private fun updateViewState(state: RegistrationState, reRegisterState: ReRegisterWithPinState) {
if (state.networkError != null) { if (state.networkError != null) {
genericErrorDialog() genericErrorDialog()
registrationViewModel.networkErrorShown() registrationViewModel.networkErrorShown()
} else if (!state.canSkipSms) { } else if (!state.canSkipSms) {
findNavController().safeNavigate(ReRegisterWithPinFragmentDirections.actionReRegisterWithPinFragmentToEnterPhoneNumberFragment()) findNavController().safeNavigate(ReRegisterWithPinFragmentDirections.actionReRegisterWithPinFragmentToEnterPhoneNumberFragment())
registrationViewModel.setInProgress(false)
} else if (state.isRegistrationLockEnabled && state.svrTriesRemaining == 0) { } else if (state.isRegistrationLockEnabled && state.svrTriesRemaining == 0) {
Log.w(TAG, "Unable to continue skip flow, KBS is locked") Log.w(TAG, "Unable to continue skip flow, KBS is locked")
onAccountLocked() onAccountLocked()
} else { } else {
presentProgress(state.inProgress) presentProgress(state.inProgress)
presentTriesRemaining(state.svrTriesRemaining) presentTriesRemaining(reRegisterState, state.svrTriesRemaining)
} }
state.registerAccountError?.let { error -> state.registerAccountError?.let { error ->
@ -131,14 +135,15 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration
context = requireContext(), context = requireContext(),
pin = pin, pin = pin,
wrongPinHandler = { wrongPinHandler = {
registrationViewModel.setInProgress(false)
reRegisterViewModel.markIncorrectGuess() reRegisterViewModel.markIncorrectGuess()
} }
) )
} }
private fun presentTriesRemaining(triesRemaining: Int) { private fun presentTriesRemaining(reRegisterState: ReRegisterWithPinState, triesRemaining: Int) {
if (reRegisterViewModel.hasIncorrectGuess) { if (reRegisterState.hasIncorrectGuess) {
if (triesRemaining == 1 && !reRegisterViewModel.isLocalVerification) { if (triesRemaining == 1 && !reRegisterState.isLocalVerification) {
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.PinRestoreEntryFragment_incorrect_pin) .setTitle(R.string.PinRestoreEntryFragment_incorrect_pin)
.setMessage(resources.getQuantityString(R.plurals.PinRestoreEntryFragment_you_have_d_attempt_remaining, triesRemaining, triesRemaining)) .setMessage(resources.getQuantityString(R.plurals.PinRestoreEntryFragment_you_have_d_attempt_remaining, triesRemaining, triesRemaining))
@ -155,7 +160,7 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration
} else { } else {
if (triesRemaining == 1) { if (triesRemaining == 1) {
binding.pinRestoreForgotPin.visibility = View.VISIBLE binding.pinRestoreForgotPin.visibility = View.VISIBLE
if (!reRegisterViewModel.isLocalVerification) { if (!reRegisterState.isLocalVerification) {
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setMessage(resources.getQuantityString(R.plurals.PinRestoreEntryFragment_you_have_d_attempt_remaining, triesRemaining, triesRemaining)) .setMessage(resources.getQuantityString(R.plurals.PinRestoreEntryFragment_you_have_d_attempt_remaining, triesRemaining, triesRemaining))
.setPositiveButton(android.R.string.ok, null) .setPositiveButton(android.R.string.ok, null)

View file

@ -6,21 +6,17 @@
package org.thoughtcrime.securesms.registration.ui.reregisterwithpin package org.thoughtcrime.securesms.registration.ui.reregisterwithpin
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import org.signal.core.util.logging.Log
class ReRegisterWithPinViewModel : ViewModel() { class ReRegisterWithPinViewModel : ViewModel() {
companion object {
private val TAG = Log.tag(ReRegisterWithPinViewModel::class.java)
}
private val store = MutableStateFlow(ReRegisterWithPinState()) private val store = MutableStateFlow(ReRegisterWithPinState())
val uiState = store.asLiveData()
val isLocalVerification: Boolean val isLocalVerification: Boolean
get() = store.value.isLocalVerification get() = store.value.isLocalVerification
val hasIncorrectGuess: Boolean
get() = store.value.hasIncorrectGuess
fun markAsRemoteVerification() { fun markAsRemoteVerification() {
store.update { store.update {

View file

@ -83,7 +83,6 @@ class RegistrationActivity : BaseActivity() {
if (!needsProfile && !needsPin) { if (!needsProfile && !needsPin) {
sharedViewModel.completeRegistration() sharedViewModel.completeRegistration()
} }
sharedViewModel.setInProgress(false)
val startIntent = MainActivity.clearTop(this) val startIntent = MainActivity.clearTop(this)

View file

@ -90,13 +90,8 @@ class RegistrationViewModel : ViewModel() {
private val password = Util.getSecret(18) private val password = Util.getSecret(18)
private val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> private val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
Log.w(TAG, "CoroutineExceptionHandler invoked.", exception) Log.w(TAG, "CoroutineExceptionHandler invoked!")
store.update { handleGenericError(exception)
it.copy(
networkError = exception,
inProgress = false
)
}
} }
val state: StateFlow<RegistrationState> = store val state: StateFlow<RegistrationState> = store
@ -244,8 +239,7 @@ class RegistrationViewModel : ViewModel() {
store.update { store.update {
it.copy( it.copy(
canSkipSms = true, canSkipSms = true,
isReRegister = true, isReRegister = true
inProgress = false
) )
} }
return return
@ -269,8 +263,7 @@ class RegistrationViewModel : ViewModel() {
isReRegister = true, isReRegister = true,
canSkipSms = true, canSkipSms = true,
svr2AuthCredentials = svrCredentialsResult.svr2Credentials, svr2AuthCredentials = svrCredentialsResult.svr2Credentials,
svr3AuthCredentials = svrCredentialsResult.svr3Credentials, svr3AuthCredentials = svrCredentialsResult.svr3Credentials
inProgress = false
) )
} }
return@launch return@launch
@ -321,7 +314,7 @@ class RegistrationViewModel : ViewModel() {
if (e164 == null) { if (e164 == null) {
Log.w(TAG, "Phone number was null after confirmation.") Log.w(TAG, "Phone number was null after confirmation.")
onErrorOccurred() setInProgress(false)
return return
} }
@ -410,8 +403,7 @@ class RegistrationViewModel : ViewModel() {
nextVerificationAttempt = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.body.nextVerificationAttempt), nextVerificationAttempt = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.body.nextVerificationAttempt),
allowedToRequestCode = networkResult.body.allowedToRequestCode, allowedToRequestCode = networkResult.body.allowedToRequestCode,
challengesRequested = Challenge.parse(networkResult.body.requestedInformation), challengesRequested = Challenge.parse(networkResult.body.requestedInformation),
verified = networkResult.body.verified, verified = networkResult.body.verified
inProgress = false
) )
} }
}, },
@ -457,12 +449,6 @@ class RegistrationViewModel : ViewModel() {
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a push challenge token.") } val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a push challenge token.") }
if (!Challenge.parse(session.body.requestedInformation).contains(Challenge.PUSH)) { if (!Challenge.parse(session.body.requestedInformation).contains(Challenge.PUSH)) {
Log.d(TAG, "Push submission no longer necessary, bailing.")
store.update {
it.copy(
inProgress = false
)
}
return@launch bail { Log.i(TAG, "Push challenge token no longer needed, bailing.") } return@launch bail { Log.i(TAG, "Push challenge token no longer needed, bailing.") }
} }
@ -492,8 +478,7 @@ class RegistrationViewModel : ViewModel() {
nextSmsTimestamp = sessionResult.nextSmsTimestamp, nextSmsTimestamp = sessionResult.nextSmsTimestamp,
nextCallTimestamp = sessionResult.nextCallTimestamp, nextCallTimestamp = sessionResult.nextCallTimestamp,
isAllowedToRequestCode = sessionResult.allowedToRequestCode, isAllowedToRequestCode = sessionResult.allowedToRequestCode,
challengesRequested = emptyList(), challengesRequested = emptyList()
inProgress = false
) )
} }
return true return true
@ -504,8 +489,7 @@ class RegistrationViewModel : ViewModel() {
store.update { store.update {
it.copy( it.copy(
registrationCheckpoint = RegistrationCheckpoint.CHALLENGE_RECEIVED, registrationCheckpoint = RegistrationCheckpoint.CHALLENGE_RECEIVED,
challengesRequested = sessionResult.challenges, challengesRequested = sessionResult.challenges
inProgress = false
) )
} }
return false return false
@ -540,10 +524,11 @@ class RegistrationViewModel : ViewModel() {
is AlreadyVerified -> Log.i(TAG, "Received AlreadyVerified", sessionResult.getCause()) is AlreadyVerified -> Log.i(TAG, "Received AlreadyVerified", sessionResult.getCause())
} }
setInProgress(false)
store.update { store.update {
it.copy( it.copy(
sessionStateError = sessionResult sessionStateError = sessionResult,
inProgress = false
) )
} }
return false return false
@ -554,6 +539,7 @@ class RegistrationViewModel : ViewModel() {
*/ */
private suspend fun handleRegistrationResult(context: Context, registrationData: RegistrationData, registrationResult: RegisterAccountResult, reglockEnabled: Boolean): Boolean { private suspend fun handleRegistrationResult(context: Context, registrationData: RegistrationData, registrationResult: RegisterAccountResult, reglockEnabled: Boolean): Boolean {
Log.v(TAG, "handleRegistrationResult()") Log.v(TAG, "handleRegistrationResult()")
var stayInProgress = false
when (registrationResult) { when (registrationResult) {
is RegisterAccountResult.Success -> { is RegisterAccountResult.Success -> {
Log.i(TAG, "Register account result: Success! Registration lock: $reglockEnabled") Log.i(TAG, "Register account result: Success! Registration lock: $reglockEnabled")
@ -573,6 +559,7 @@ class RegistrationViewModel : ViewModel() {
is RegisterAccountResult.RegistrationLocked -> { is RegisterAccountResult.RegistrationLocked -> {
Log.i(TAG, "Account is registration locked!", registrationResult.getCause()) Log.i(TAG, "Account is registration locked!", registrationResult.getCause())
stayInProgress = true
} }
is RegisterAccountResult.SvrWrongPin -> { is RegisterAccountResult.SvrWrongPin -> {
@ -588,10 +575,10 @@ class RegistrationViewModel : ViewModel() {
is RegisterAccountResult.ValidationError, is RegisterAccountResult.ValidationError,
is RegisterAccountResult.UnknownError -> Log.i(TAG, "Received error when trying to register!", registrationResult.getCause()) is RegisterAccountResult.UnknownError -> Log.i(TAG, "Received error when trying to register!", registrationResult.getCause())
} }
setInProgress(false)
store.update { store.update {
it.copy( it.copy(
registerAccountError = registrationResult registerAccountError = registrationResult,
inProgress = stayInProgress
) )
} }
return false return false
@ -649,7 +636,6 @@ class RegistrationViewModel : ViewModel() {
updateSvrTriesRemaining(0) updateSvrTriesRemaining(0)
setUserSkippedReRegisterFlow(true) setUserSkippedReRegisterFlow(true)
} }
setInProgress(false)
} }
return return
} }
@ -662,19 +648,17 @@ class RegistrationViewModel : ViewModel() {
val masterKey = SignalStore.svr.masterKey val masterKey = SignalStore.svr.masterKey
setRecoveryPassword(masterKey.deriveRegistrationRecoveryPassword()) setRecoveryPassword(masterKey.deriveRegistrationRecoveryPassword())
verifyReRegisterInternal(context, pin, masterKey) verifyReRegisterInternal(context, pin, masterKey)
setInProgress(false)
} }
} else { } else {
Log.d(TAG, "Entered PIN did not match local PIN hash.") Log.d(TAG, "Entered PIN did not match local PIN hash.")
wrongPinHandler() wrongPinHandler()
setInProgress(false)
} }
return return
} }
Log.w(TAG, "Could not get credentials to skip SMS registration, aborting!") Log.w(TAG, "Could not get credentials to skip SMS registration, aborting!")
store.update { store.update {
it.copy(canSkipSms = false, inProgress = false) it.copy(canSkipSms = false)
} }
} }
@ -875,8 +859,7 @@ class RegistrationViewModel : ViewModel() {
store.update { store.update {
it.copy( it.copy(
registrationCheckpoint = RegistrationCheckpoint.LOCAL_REGISTRATION_COMPLETE, registrationCheckpoint = RegistrationCheckpoint.LOCAL_REGISTRATION_COMPLETE
inProgress = false
) )
} }
} }
@ -916,14 +899,6 @@ class RegistrationViewModel : ViewModel() {
return RegistrationData(code, e164, password, RegistrationRepository.getRegistrationId(), RegistrationRepository.getProfileKey(e164), currentState.fcmToken, RegistrationRepository.getPniRegistrationId(), recoveryPassword) return RegistrationData(code, e164, password, RegistrationRepository.getRegistrationId(), RegistrationRepository.getProfileKey(e164), currentState.fcmToken, RegistrationRepository.getPniRegistrationId(), recoveryPassword)
} }
/**
* This is a generic error UI handler that re-enables the UI so that the user can recover from errors.
* Do not forget to log any errors when calling this method!
*/
private fun onErrorOccurred() {
setInProgress(false)
}
/** /**
* Used for early returns in order to end the in-progress visual state, as well as print a log message explaining what happened. * Used for early returns in order to end the in-progress visual state, as well as print a log message explaining what happened.
* *

View file

@ -198,6 +198,7 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
object : AssertedSuccessListener<Boolean>() { object : AssertedSuccessListener<Boolean>() {
override fun onSuccess(result: Boolean?) { override fun onSuccess(result: Boolean?) {
findNavController().safeNavigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining)) findNavController().safeNavigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining))
sharedViewModel.setInProgress(false)
} }
} }
) )
@ -265,6 +266,7 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
private fun popBackStack() { private fun popBackStack() {
sharedViewModel.setRegistrationCheckpoint(RegistrationCheckpoint.PUSH_NETWORK_AUDITED) sharedViewModel.setRegistrationCheckpoint(RegistrationCheckpoint.PUSH_NETWORK_AUDITED)
NavHostFragment.findNavController(this).popBackStack() NavHostFragment.findNavController(this).popBackStack()
sharedViewModel.setInProgress(false)
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)

View file

@ -36,6 +36,7 @@ import com.google.android.material.textfield.TextInputEditText
import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.NumberParseException
import com.google.i18n.phonenumbers.PhoneNumberUtil import com.google.i18n.phonenumbers.PhoneNumberUtil
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber
import org.signal.core.util.ThreadUtil
import org.signal.core.util.isNotNullOrBlank import org.signal.core.util.isNotNullOrBlank
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.LoggingFragment import org.thoughtcrime.securesms.LoggingFragment
@ -119,7 +120,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
sharedViewModel.uiState.observe(viewLifecycleOwner) { sharedState -> sharedViewModel.uiState.observe(viewLifecycleOwner) { sharedState ->
presentRegisterButton(sharedState) presentRegisterButton(sharedState)
presentProgressBar(sharedState.inProgress, sharedState.isReRegister) updateEnabledControls(sharedState.inProgress, sharedState.isReRegister)
sharedState.networkError?.let { sharedState.networkError?.let {
presentNetworkError(it) presentNetworkError(it)
@ -412,6 +413,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
private fun presentRegistrationLocked(timeRemaining: Long) { private fun presentRegistrationLocked(timeRemaining: Long) {
findNavController().safeNavigate(EnterPhoneNumberFragmentDirections.actionPhoneNumberRegistrationLock(timeRemaining)) findNavController().safeNavigate(EnterPhoneNumberFragmentDirections.actionPhoneNumberRegistrationLock(timeRemaining))
sharedViewModel.setInProgress(false)
} }
private fun presentRateLimitedDialog() { private fun presentRateLimitedDialog() {
@ -420,10 +422,12 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
private fun presentAccountLocked() { private fun presentAccountLocked() {
findNavController().safeNavigate(EnterPhoneNumberFragmentDirections.actionPhoneNumberAccountLocked()) findNavController().safeNavigate(EnterPhoneNumberFragmentDirections.actionPhoneNumberAccountLocked())
ThreadUtil.postToMain { sharedViewModel.setInProgress(false) }
} }
private fun moveToCaptcha() { private fun moveToCaptcha() {
findNavController().safeNavigate(EnterPhoneNumberFragmentDirections.actionRequestCaptcha()) findNavController().safeNavigate(EnterPhoneNumberFragmentDirections.actionRequestCaptcha())
ThreadUtil.postToMain { sharedViewModel.setInProgress(false) }
} }
private fun presentRemoteErrorDialog(message: String, positiveButtonListener: DialogInterface.OnClickListener? = null) { private fun presentRemoteErrorDialog(message: String, positiveButtonListener: DialogInterface.OnClickListener? = null) {
@ -513,12 +517,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
} }
} }
private fun presentProgressBar(showProgress: Boolean, isReRegister: Boolean) { private fun updateEnabledControls(showProgress: Boolean, isReRegister: Boolean) {
if (showProgress) {
binding.registerButton.setSpinning()
} else {
binding.registerButton.cancelSpinning()
}
binding.countryCode.isEnabled = !showProgress binding.countryCode.isEnabled = !showProgress
binding.number.isEnabled = !showProgress binding.number.isEnabled = !showProgress
binding.cancelButton.visible = !showProgress && isReRegister binding.cancelButton.visible = !showProgress && isReRegister

View file

@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.registrationv3.ui.phonenumber.EnterPhoneNumber
import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.SupportEmailUtil import org.thoughtcrime.securesms.util.SupportEmailUtil
import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
import org.thoughtcrime.securesms.util.navigation.safeNavigate import org.thoughtcrime.securesms.util.navigation.safeNavigate
class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration_pin_restore_entry_v2) { class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration_pin_restore_entry_v2) {
@ -77,21 +78,24 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration
binding.pinRestoreKeyboardToggle.setIconResource(getPinEntryKeyboardType().other.iconResource) binding.pinRestoreKeyboardToggle.setIconResource(getPinEntryKeyboardType().other.iconResource)
registrationViewModel.uiState.observe(viewLifecycleOwner, ::updateViewState) LiveDataUtil
.combineLatest(registrationViewModel.uiState, reRegisterViewModel.uiState) { reg, rereg -> reg to rereg }
.observe(viewLifecycleOwner) { (registrationState, reRegisterState) -> updateViewState(registrationState, reRegisterState) }
} }
private fun updateViewState(state: RegistrationState) { private fun updateViewState(state: RegistrationState, reRegisterState: ReRegisterWithPinState) {
if (state.networkError != null) { if (state.networkError != null) {
genericErrorDialog() genericErrorDialog()
registrationViewModel.networkErrorShown() registrationViewModel.networkErrorShown()
} else if (!state.canSkipSms) { } else if (!state.canSkipSms) {
findNavController().safeNavigate(ReRegisterWithPinFragmentDirections.actionReRegisterWithPinFragmentToEnterPhoneNumberFragment(EnterPhoneNumberMode.NORMAL)) findNavController().safeNavigate(ReRegisterWithPinFragmentDirections.actionReRegisterWithPinFragmentToEnterPhoneNumberFragment(EnterPhoneNumberMode.NORMAL))
registrationViewModel.setInProgress(false)
} else if (state.isRegistrationLockEnabled && state.svrTriesRemaining == 0) { } else if (state.isRegistrationLockEnabled && state.svrTriesRemaining == 0) {
Log.w(TAG, "Unable to continue skip flow, KBS is locked") Log.w(TAG, "Unable to continue skip flow, KBS is locked")
onAccountLocked() onAccountLocked()
} else { } else {
presentProgress(state.inProgress) presentProgress(state.inProgress)
presentTriesRemaining(state.svrTriesRemaining) presentTriesRemaining(reRegisterState, state.svrTriesRemaining)
} }
state.registerAccountError?.let { error -> state.registerAccountError?.let { error ->
@ -132,14 +136,15 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration
context = requireContext(), context = requireContext(),
pin = pin, pin = pin,
wrongPinHandler = { wrongPinHandler = {
registrationViewModel.setInProgress(false)
reRegisterViewModel.markIncorrectGuess() reRegisterViewModel.markIncorrectGuess()
} }
) )
} }
private fun presentTriesRemaining(triesRemaining: Int) { private fun presentTriesRemaining(reRegisterState: ReRegisterWithPinState, triesRemaining: Int) {
if (reRegisterViewModel.hasIncorrectGuess) { if (reRegisterState.hasIncorrectGuess) {
if (triesRemaining == 1 && !reRegisterViewModel.isLocalVerification) { if (triesRemaining == 1 && !reRegisterState.isLocalVerification) {
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.PinRestoreEntryFragment_incorrect_pin) .setTitle(R.string.PinRestoreEntryFragment_incorrect_pin)
.setMessage(resources.getQuantityString(R.plurals.PinRestoreEntryFragment_you_have_d_attempt_remaining, triesRemaining, triesRemaining)) .setMessage(resources.getQuantityString(R.plurals.PinRestoreEntryFragment_you_have_d_attempt_remaining, triesRemaining, triesRemaining))
@ -156,7 +161,7 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration
} else { } else {
if (triesRemaining == 1) { if (triesRemaining == 1) {
binding.pinRestoreForgotPin.visibility = View.VISIBLE binding.pinRestoreForgotPin.visibility = View.VISIBLE
if (!reRegisterViewModel.isLocalVerification) { if (!reRegisterState.isLocalVerification) {
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setMessage(resources.getQuantityString(R.plurals.PinRestoreEntryFragment_you_have_d_attempt_remaining, triesRemaining, triesRemaining)) .setMessage(resources.getQuantityString(R.plurals.PinRestoreEntryFragment_you_have_d_attempt_remaining, triesRemaining, triesRemaining))
.setPositiveButton(android.R.string.ok, null) .setPositiveButton(android.R.string.ok, null)

View file

@ -6,21 +6,17 @@
package org.thoughtcrime.securesms.registrationv3.ui.reregisterwithpin package org.thoughtcrime.securesms.registrationv3.ui.reregisterwithpin
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import org.signal.core.util.logging.Log
class ReRegisterWithPinViewModel : ViewModel() { class ReRegisterWithPinViewModel : ViewModel() {
companion object {
private val TAG = Log.tag(ReRegisterWithPinViewModel::class.java)
}
private val store = MutableStateFlow(ReRegisterWithPinState()) private val store = MutableStateFlow(ReRegisterWithPinState())
val uiState = store.asLiveData()
val isLocalVerification: Boolean val isLocalVerification: Boolean
get() = store.value.isLocalVerification get() = store.value.isLocalVerification
val hasIncorrectGuess: Boolean
get() = store.value.hasIncorrectGuess
fun markAsRemoteVerification() { fun markAsRemoteVerification() {
store.update { store.update {