From 11c46ad0eae29efdd073f27204685bd1931ebb3c Mon Sep 17 00:00:00 2001 From: alexandr Date: Thu, 22 Feb 2024 17:20:56 +0200 Subject: [PATCH] [MS-220] Moving loading state indication to a separate LiveData field within LoginFormViewModel, and observing it from LoginFormFragment --- .../login/screens/form/LoginFormFragment.kt | 22 ++++++++++--------- .../login/screens/form/LoginFormViewModel.kt | 19 ++++++++++++++-- .../screens/form/LoginFormViewModelTest.kt | 20 +++++++++++++++++ 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt index 7b40a63b97..122803a286 100644 --- a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt +++ b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt @@ -52,6 +52,7 @@ internal class LoginFormFragment : Fragment(R.layout.fragment_login_form) { private val viewModel by viewModels() private lateinit var checkForPlayServicesResultLauncher: ActivityResultLauncher + init { checkForPlayServicesResultLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { @@ -93,11 +94,6 @@ internal class LoginFormFragment : Fragment(R.layout.fragment_login_form) { } binding.loginButtonSignIn.setOnClickListener { Simber.tag(LoggingConstants.CrashReportTag.LOGIN.name).i("Login button clicked") - - binding.loginProgress.isVisible = true - binding.loginButtonScanQr.isEnabled = false - binding.loginButtonSignIn.isEnabled = false - viewModel.signInClicked( args.loginParams, binding.loginProjectId.text.toString(), @@ -107,17 +103,18 @@ internal class LoginFormFragment : Fragment(R.layout.fragment_login_form) { } private fun observeUiState() { + viewModel.isProcessingSignIn.observe(viewLifecycleOwner) { isProcessingSignIn -> + binding.loginProgress.isVisible = isProcessingSignIn + binding.loginButtonScanQr.isEnabled = !isProcessingSignIn + binding.loginButtonSignIn.isEnabled = !isProcessingSignIn + } viewModel.signInState.observe(viewLifecycleOwner) { event -> event?.getContentIfNotHandled()?.let(::handleSignInResult) } } private fun handleSignInResult(result: SignInState) { - binding.loginProgress.isVisible = false binding.loginErrorCard.isVisible = false - binding.loginButtonScanQr.isEnabled = true - binding.loginButtonSignIn.isEnabled = true - when (result) { // Showing toast @@ -153,7 +150,12 @@ internal class LoginFormFragment : Fragment(R.layout.fragment_login_form) { private fun showOutageErrorCard(estimatedOutage: String?) { binding.loginErrorText.text = estimatedOutage - ?.let { getString(IDR.string.error_backend_maintenance_with_time_message, estimatedOutage) } + ?.let { + getString( + IDR.string.error_backend_maintenance_with_time_message, + estimatedOutage + ) + } ?: getString(IDR.string.error_backend_maintenance_message) binding.loginErrorCard.isVisible = true } diff --git a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt index 401fcd7a37..1df40e932c 100644 --- a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt +++ b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt @@ -30,6 +30,9 @@ internal class LoginFormViewModel @Inject constructor( private val jsonHelper: JsonHelper, ) : ViewModel() { + val isProcessingSignIn: LiveData + get() = _isProcessingSignIn + private val _isProcessingSignIn = MutableLiveData() val signInState: LiveData> get() = _signInState private val _signInState = MutableLiveData>(null) @@ -46,8 +49,15 @@ internal class LoginFormViewModel @Inject constructor( _signInState.send(SignInState.ProjectIdMismatch) } else { viewModelScope.launch { - val result = authManager.authenticateSafely(loginParams.userId.value, projectId, projectSecret, deviceId) + _isProcessingSignIn.value = true + val result = authManager.authenticateSafely( + userId = loginParams.userId.value, + projectId = projectId, + projectSecret = projectSecret, + deviceId = deviceId + ) _signInState.send(mapAuthDataResult(result)) + _isProcessingSignIn.value = false } } } @@ -81,7 +91,12 @@ internal class LoginFormViewModel @Inject constructor( Simber.tag(CrashReportTag.LOGIN.name).i("QR scanning successful") qrContent.apiBaseUrl?.let { simNetwork.setApiBaseUrl(it) } - _signInState.send(SignInState.QrCodeValid(qrContent.projectId, qrContent.projectSecret)) + _signInState.send( + SignInState.QrCodeValid( + qrContent.projectId, + qrContent.projectSecret + ) + ) } catch (e: Exception) { Simber.tag(CrashReportTag.LOGIN.name).i("QR scanning unsuccessful") _signInState.send(SignInState.QrInvalidCode) diff --git a/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt b/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt index eb63ccae62..ad5f8ac137 100644 --- a/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt +++ b/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt @@ -1,6 +1,7 @@ package com.simprints.feature.login.screens.form import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Observer import com.google.common.truth.Truth.assertThat import com.simprints.core.domain.tokenization.asTokenizableRaw import com.simprints.core.tools.json.JsonHelper @@ -18,6 +19,8 @@ import io.mockk.clearMocks import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.slot import io.mockk.verify import org.junit.Before import org.junit.Rule @@ -93,6 +96,23 @@ internal class LoginFormViewModelTest { assertThat(result.getContentIfNotHandled()).isInstanceOf(SignInState.ProjectIdMismatch::class.java) } + @Test + fun `returns false from isProcessingSignIn sign in request is processed`() { + coEvery { authManager.authenticateSafely(any(), any(), any(), any()) } returns AuthenticateDataResult.Authenticated + val observer = mockk>() + val slot = slot() + val capturedValues = mutableListOf() + every { observer.onChanged(capture(slot)) } answers { + capturedValues.add(slot.captured) + } + viewModel.isProcessingSignIn.observeForever(observer) + + viewModel.signInClicked(LoginParams(PROJECT_ID, USER_ID), PROJECT_ID, PROJECT_SECRET) + + // Checking that captured values sequence contains 'true' and 'false', in that order + assertThat(capturedValues).isEqualTo(listOf(true, false)) + } + @Test fun `returns correct SignInState for each auth result class`() { mapOf(