From d16651b1257334d6ab1c7aa2cad3b0db99b01b87 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 8 Oct 2025 18:57:31 +0300 Subject: [PATCH 1/6] [MS-1166] Initial implementation of the Skip Credential screen --- .../ExternalCredentialSearchResult.kt | 2 +- .../controller/ExternalCredentialViewModel.kt | 21 ++- .../ExternalCredentialSelectFragment.kt | 5 +- .../ExternalCredentialSelectViewModel.kt | 28 ---- .../skip/ExternalCredentialSkipFragment.kt | 88 +++++++++- .../fragment_external_credential_skip.xml | 154 +++++++++++++++++- .../resources/src/main/res/values/strings.xml | 13 +- 7 files changed, 269 insertions(+), 42 deletions(-) delete mode 100644 feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/select/ExternalCredentialSelectViewModel.kt diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/ExternalCredentialSearchResult.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/ExternalCredentialSearchResult.kt index 8942e3b815..d2584c3ed4 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/ExternalCredentialSearchResult.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/ExternalCredentialSearchResult.kt @@ -19,7 +19,7 @@ import com.simprints.feature.externalcredential.screens.search.model.ScannedCred @ExcludedFromGeneratedTestCoverageReports("Data class") data class ExternalCredentialSearchResult( val flowType: FlowType, - val scannedCredential: ScannedCredential, + val scannedCredential: ScannedCredential?, val matchResults: List, ) : StepResult { val goodMatches = matchResults.filter(CredentialMatch::isVerificationSuccessful) diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/controller/ExternalCredentialViewModel.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/controller/ExternalCredentialViewModel.kt index 751dd4b5db..d7f60e8678 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/controller/ExternalCredentialViewModel.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/controller/ExternalCredentialViewModel.kt @@ -3,17 +3,22 @@ package com.simprints.feature.externalcredential.screens.controller import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.simprints.core.domain.externalcredential.ExternalCredentialType import com.simprints.core.livedata.LiveDataEventWithContent import com.simprints.core.livedata.send import com.simprints.feature.externalcredential.ExternalCredentialSearchResult import com.simprints.feature.externalcredential.model.ExternalCredentialParams +import com.simprints.infra.config.sync.ConfigManager import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject -import com.simprints.infra.resources.R as IDR +import kotlin.collections.orEmpty @HiltViewModel -internal class ExternalCredentialViewModel @Inject internal constructor() : ViewModel() { +internal class ExternalCredentialViewModel @Inject internal constructor( + private val configManager: ConfigManager, +) : ViewModel() { private var isInitialized = false lateinit var params: ExternalCredentialParams private set @@ -28,6 +33,18 @@ internal class ExternalCredentialViewModel @Inject internal constructor() : View private val _stateLiveData = MutableLiveData(ExternalCredentialState.EMPTY) val stateLiveData: LiveData = _stateLiveData + val externalCredentialTypes: LiveData> + get() = _externalCredentialTypes + private val _externalCredentialTypes = MutableLiveData>() + + init { + viewModelScope.launch { + val config = configManager.getProjectConfiguration() + val allowedExternalCredentials = config.multifactorId?.allowedExternalCredentials.orEmpty() + _externalCredentialTypes.postValue(allowedExternalCredentials) + } + } + private fun updateState(state: (ExternalCredentialState) -> ExternalCredentialState) { this.state = state(this.state) } diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/select/ExternalCredentialSelectFragment.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/select/ExternalCredentialSelectFragment.kt index 54dce8c37a..6ab288a498 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/select/ExternalCredentialSelectFragment.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/select/ExternalCredentialSelectFragment.kt @@ -7,7 +7,6 @@ import android.widget.Button import android.widget.TextView import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -32,7 +31,6 @@ import com.simprints.infra.resources.R as IDR @AndroidEntryPoint internal class ExternalCredentialSelectFragment : Fragment(R.layout.fragment_external_credential_select) { private val mainViewModel: ExternalCredentialViewModel by activityViewModels() - private val viewModel by viewModels() private val binding by viewBinding(FragmentExternalCredentialSelectBinding::bind) private var dialog: Dialog? = null @@ -46,7 +44,6 @@ internal class ExternalCredentialSelectFragment : Fragment(R.layout.fragment_ext Simber.i("ExternalCredentialSelectFragment started", tag = ORCHESTRATION) observeChanges() - viewModel.loadExternalCredentials() } override fun onDestroy() { @@ -85,7 +82,7 @@ internal class ExternalCredentialSelectFragment : Fragment(R.layout.fragment_ext } private fun observeChanges() { - viewModel.externalCredentialTypes.observe(viewLifecycleOwner) { externalCredentialTypes -> + mainViewModel.externalCredentialTypes.observe(viewLifecycleOwner) { externalCredentialTypes -> updateSelectedCredentialType(null) fillRecyclerView(externalCredentialTypes) initViews(externalCredentialTypes) diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/select/ExternalCredentialSelectViewModel.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/select/ExternalCredentialSelectViewModel.kt deleted file mode 100644 index e55d9da033..0000000000 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/select/ExternalCredentialSelectViewModel.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.simprints.feature.externalcredential.screens.select - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.simprints.core.domain.externalcredential.ExternalCredentialType -import com.simprints.infra.config.sync.ConfigManager -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -internal class ExternalCredentialSelectViewModel @Inject internal constructor( - private val configManager: ConfigManager, -) : ViewModel() { - val externalCredentialTypes: LiveData> - get() = _externalCredentialTypes - private val _externalCredentialTypes = MutableLiveData>() - - fun loadExternalCredentials() { - viewModelScope.launch { - val config = configManager.getProjectConfiguration() - val allowedExternalCredentials = config.multifactorId?.allowedExternalCredentials.orEmpty() - _externalCredentialTypes.postValue(allowedExternalCredentials) - } - } -} diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/skip/ExternalCredentialSkipFragment.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/skip/ExternalCredentialSkipFragment.kt index d6f73c5f31..a91e985aed 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/skip/ExternalCredentialSkipFragment.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/skip/ExternalCredentialSkipFragment.kt @@ -1,8 +1,94 @@ package com.simprints.feature.externalcredential.screens.skip +import android.os.Bundle +import android.view.View +import androidx.core.view.isVisible +import androidx.core.widget.addTextChangedListener import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.navigation.fragment.findNavController +import com.simprints.core.domain.externalcredential.ExternalCredentialType +import com.simprints.feature.externalcredential.ExternalCredentialSearchResult import com.simprints.feature.externalcredential.R +import com.simprints.feature.externalcredential.databinding.FragmentExternalCredentialSkipBinding +import com.simprints.feature.externalcredential.ext.getCredentialTypeString +import com.simprints.feature.externalcredential.screens.controller.ExternalCredentialViewModel +import com.simprints.infra.uibase.viewbinding.viewBinding import dagger.hilt.android.AndroidEntryPoint +import com.simprints.infra.resources.R as IDR @AndroidEntryPoint -class ExternalCredentialSkipFragment : Fragment(R.layout.fragment_external_credential_skip) +class ExternalCredentialSkipFragment : Fragment(R.layout.fragment_external_credential_skip) { + private val binding by viewBinding(FragmentExternalCredentialSkipBinding::bind) + private val mainViewModel: ExternalCredentialViewModel by activityViewModels() + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + + initObservers() + } + + private fun initObservers() { + mainViewModel.externalCredentialTypes.observe(viewLifecycleOwner) { credentialTypes -> + initViews(credentialTypes) + initListeners() + } + } + + private fun initViews(credentialTypes: List) = with(binding) { + val dynamicTextReasonItemMap = + mapOf( + title to IDR.string.mfid_skip_title, + skipReasonDoesNotHaveDocument to IDR.string.mfid_skip_reason_does_not_have, + skipReasonDidNotBring to IDR.string.mfid_skip_reason_did_not_bring, + skipReasonIncorrect to IDR.string.mfid_skip_reason_incorrect, + skipReasonDoesNotWantToProvide to IDR.string.mfid_skip_reason_does_not_want_to_provide, + skipReasonDamaged to IDR.string.mfid_skip_reason_damaged, + skipReasonUnableToScan to IDR.string.mfid_skip_reason_unable_to_scan, + ) + dynamicTextReasonItemMap.forEach { entry -> + val textView = entry.key + val stringRes = entry.value + val credentialText = when (credentialTypes.size) { + 1 -> resources.getCredentialTypeString(credentialTypes.first()) + else -> getString(IDR.string.mfid_type_any_document) + } + textView.text = getString(stringRes, credentialText) + } + } + + private fun initListeners() = with(binding) { + skipCredentialScanRadioGroup.setOnCheckedChangeListener { _, checkedId -> + reasonTextInputLayout.isVisible = checkedId == R.id.skipReasonOther + + val isSkipButtonEnabled = when (checkedId) { + R.id.skipReasonOther -> { + reasonTextInput.text.toString().isNotEmpty() + } + + else -> true + } + buttonSkip.isEnabled = isSkipButtonEnabled + } + reasonTextInput.addTextChangedListener( + afterTextChanged = { + buttonSkip.isEnabled = it.toString().isNotEmpty() + }, + ) + buttonGoBack.setOnClickListener { + findNavController().popBackStack() + } + buttonSkip.setOnClickListener { + mainViewModel.finish( + ExternalCredentialSearchResult( + flowType = mainViewModel.params.flowType, + scannedCredential = null, + matchResults = emptyList(), + ), + ) + } + } +} diff --git a/feature/external-credential/src/main/res/layout/fragment_external_credential_skip.xml b/feature/external-credential/src/main/res/layout/fragment_external_credential_skip.xml index 1860c12b36..25d8233497 100644 --- a/feature/external-credential/src/main/res/layout/fragment_external_credential_skip.xml +++ b/feature/external-credential/src/main/res/layout/fragment_external_credential_skip.xml @@ -1,12 +1,156 @@ - + android:layout_height="match_parent" + android:background="@color/simprints_blue"> + + + android:padding="@dimen/padding_default" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="Why did you skip the document scan?" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +