Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ jobs:
feature:login-check
feature:alert
feature:exit-form
feature:select-subject-age-group
reportsId: feature1

feature-unit-tests2:
Expand Down
5 changes: 2 additions & 3 deletions build-logic/build_properties.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ extra.apply {
* CI. Read more about our versioning here:
* https://simprints.atlassian.net/wiki/spaces/KB/pages/1761378305/Releasing+Simprints+ID
*
* Dev version >= 2023.4.1 is required for receiving encryption Tokens from BFSID [CORE-2502]
* Dev version >= 2023.4.0 is required for receiving new fingerprint configurations [CORE-3033]
* Dev version >= 2024.2.1 is required for receiving biometric sdk age restrictions
*/
set("VERSION_NAME", "2024.1.1")
set("VERSION_NAME", "2024.2.1")
Comment thread
luhmirin-s marked this conversation as resolved.

/**
* Build type. The version code describes which build type was used for the build.
Expand Down
5 changes: 1 addition & 4 deletions build-logic/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
# Dont warn about the missing files during the obfuscation
-dontwarn java.lang.invoke.StringConcatFactory
-dontwarn com.simprints.infra.resources.R$string
-dontwarn com.simprints.infra.resources.R$color
-dontwarn com.simprints.infra.resources.R$drawable
-dontwarn com.simprints.**
1 change: 1 addition & 0 deletions feature/orchestrator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
implementation(project(":feature:exit-form"))
implementation(project(":feature:matcher"))
implementation(project(":feature:validate-subject-pool"))
implementation(project(":feature:select-subject-age-group"))

implementation(project(":face:capture"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.simprints.feature.login.LoginContract
import com.simprints.feature.login.LoginResult
import com.simprints.feature.logincheck.LoginCheckViewModel
import com.simprints.feature.orchestrator.cache.OrchestratorCache
import com.simprints.feature.selectagegroup.SelectSubjectAgeGroupContract
import com.simprints.feature.selectsubject.SelectSubjectContract
import com.simprints.feature.setup.SetupContract
import com.simprints.feature.validatepool.ValidateSubjectPoolContract
Expand Down Expand Up @@ -116,6 +117,7 @@ internal class OrchestratorFragment : Fragment(R.layout.fragment_orchestrator) {
handleResult(FingerprintCaptureContract.DESTINATION, orchestratorVm::handleResult)
handleResult(FetchSubjectContract.DESTINATION, orchestratorVm::handleResult)
handleResult(ValidateSubjectPoolContract.DESTINATION, orchestratorVm::handleResult)
handleResult(SelectSubjectAgeGroupContract.DESTINATION, orchestratorVm::handleResult)
}

private fun <T : Serializable> handleResult(destination: Int, block: (T) -> Unit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.simprints.feature.enrollast.EnrolLastBiometricResult
import com.simprints.feature.exitform.ExitFormResult
import com.simprints.feature.fetchsubject.FetchSubjectResult
import com.simprints.feature.login.LoginResult
import com.simprints.feature.selectagegroup.SelectSubjectAgeGroupResult
import com.simprints.feature.selectsubject.SelectSubjectResult
import com.simprints.feature.setup.SetupResult
import com.simprints.feature.validatepool.ValidateSubjectPoolResult
Expand Down Expand Up @@ -56,6 +57,7 @@ import java.io.Serializable
JsonSubTypes.Type(value = AlertResult::class, name = "AlertResult"),
JsonSubTypes.Type(value = ExitFormResult::class, name = "ExitFormResult"),
JsonSubTypes.Type(value = ValidateSubjectPoolResult::class, name = "ValidateSubjectPoolResult"),
JsonSubTypes.Type(value = SelectSubjectAgeGroupResult::class, name = "SelectSubjectAgeResult"),
)
abstract class SerializableMixin : Serializable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ internal object StepId {
const val ENROL_LAST_BIOMETRIC = STEP_BASE_CORE + 4
const val CONFIRM_IDENTITY = STEP_BASE_CORE + 5
const val VALIDATE_ID_POOL = STEP_BASE_CORE + 6
const val SELECT_SUBJECT_AGE = STEP_BASE_CORE + 7

// Face step ids
private const val STEP_BASE_FINGERPRINT = 300
Expand All @@ -24,7 +25,6 @@ internal object StepId {

// Face step ids
private const val STEP_BASE_FACE = 500
const val FACE_CONFIGURATION = STEP_BASE_FACE + 1
const val FACE_CAPTURE = STEP_BASE_FACE + 2
const val FACE_MATCHER = STEP_BASE_FACE + 3
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,22 @@ import com.simprints.feature.orchestrator.steps.MatchStepStubPayload
import com.simprints.feature.orchestrator.steps.Step
import com.simprints.feature.orchestrator.steps.StepId
import com.simprints.feature.orchestrator.usecases.MapStepsForLastBiometricEnrolUseCase
import com.simprints.feature.selectagegroup.SelectSubjectAgeGroupContract
import com.simprints.feature.selectsubject.SelectSubjectContract
import com.simprints.feature.setup.SetupContract
import com.simprints.feature.validatepool.ValidateSubjectPoolContract
import com.simprints.fingerprint.capture.FingerprintCaptureContract
import com.simprints.infra.config.store.models.GeneralConfiguration.Modality
import com.simprints.infra.config.store.models.ProjectConfiguration
import com.simprints.infra.config.store.models.allowedAgeRanges
import com.simprints.infra.config.store.models.fromDomainToModuleApi
import com.simprints.infra.enrolment.records.store.domain.models.BiometricDataSource
import com.simprints.infra.enrolment.records.store.domain.models.SubjectQuery
import com.simprints.infra.orchestration.data.ActionRequest
import com.simprints.matcher.MatchContract
import javax.inject.Inject


@ExcludedFromGeneratedTestCoverageReports("Mapping code for steps")
internal class BuildStepsUseCase @Inject constructor(
private val buildMatcherSubjectQuery: BuildMatcherSubjectQueryUseCase,
Expand All @@ -38,6 +41,7 @@ internal class BuildStepsUseCase @Inject constructor(
is ActionRequest.EnrolActionRequest -> listOf(
buildSetupStep(),
buildConsentStep(ConsentType.ENROL),
buildAgeSelectionStep(action, projectConfiguration),
buildModalityCaptureSteps(
projectConfiguration,
FlowType.ENROL,
Expand All @@ -58,6 +62,7 @@ internal class BuildStepsUseCase @Inject constructor(
listOf(
buildSetupStep(),
buildValidateIdPoolStep(subjectQuery),
buildAgeSelectionStep(action, projectConfiguration),
buildConsentStep(ConsentType.IDENTIFY),
buildModalityCaptureSteps(
projectConfiguration,
Expand All @@ -74,6 +79,7 @@ internal class BuildStepsUseCase @Inject constructor(

is ActionRequest.VerifyActionRequest -> listOf(
buildSetupStep(),
buildAgeSelectionStep(action, projectConfiguration),
buildFetchGuidStep(action.projectId, action.verifyGuid),
buildConsentStep(ConsentType.VERIFY),
buildModalityCaptureSteps(
Expand All @@ -97,26 +103,51 @@ internal class BuildStepsUseCase @Inject constructor(
)
}.flatten()

private fun buildSetupStep() = listOf(Step(
id = StepId.SETUP,
navigationActionId = R.id.action_orchestratorFragment_to_setup,
destinationId = SetupContract.DESTINATION,
payload = bundleOf(),
))
private fun buildAgeSelectionStep(
action: ActionRequest,
projectConfiguration: ProjectConfiguration
): List<Step> {
if (projectConfiguration.allowedAgeRanges().isEmpty()) {
return emptyList()
}
// Todo check if the action request contains the age parameter
return listOf(
Step(
id = StepId.SELECT_SUBJECT_AGE,
navigationActionId = R.id.action_orchestratorFragment_to_age_group_selection,
destinationId = SelectSubjectAgeGroupContract.DESTINATION,
payload = bundleOf()
)
)

private fun buildFetchGuidStep(projectId: String, subjectId: String) = listOf(Step(
id = StepId.FETCH_GUID,
navigationActionId = R.id.action_orchestratorFragment_to_fetchSubject,
destinationId = FetchSubjectContract.DESTINATION,
payload = FetchSubjectContract.getArgs(projectId, subjectId),
))
}

private fun buildConsentStep(consentType: ConsentType) = listOf(Step(
id = StepId.CONSENT,
navigationActionId = R.id.action_orchestratorFragment_to_consent,
destinationId = ConsentContract.DESTINATION,
payload = ConsentContract.getArgs(consentType),
))
private fun buildSetupStep() = listOf(
Step(
id = StepId.SETUP,
navigationActionId = R.id.action_orchestratorFragment_to_setup,
destinationId = SetupContract.DESTINATION,
payload = bundleOf(),
)
)

private fun buildFetchGuidStep(projectId: String, subjectId: String) = listOf(
Step(
id = StepId.FETCH_GUID,
navigationActionId = R.id.action_orchestratorFragment_to_fetchSubject,
destinationId = FetchSubjectContract.DESTINATION,
payload = FetchSubjectContract.getArgs(projectId, subjectId),
)
)

private fun buildConsentStep(consentType: ConsentType) = listOf(
Step(
id = StepId.CONSENT,
navigationActionId = R.id.action_orchestratorFragment_to_consent,
destinationId = ConsentContract.DESTINATION,
payload = ConsentContract.getArgs(consentType),
)
)

private fun buildValidateIdPoolStep(subjectQuery: SubjectQuery) = listOf(Step(
id = StepId.VALIDATE_ID_POOL,
Expand All @@ -126,8 +157,8 @@ internal class BuildStepsUseCase @Inject constructor(
))

private fun buildModalityCaptureSteps(
projectConfiguration: ProjectConfiguration,
flowType: FlowType,
projectConfiguration: ProjectConfiguration,
flowType: FlowType,
) = projectConfiguration.general.modalities.map {
when (it) {
Modality.FINGERPRINT -> {
Expand Down Expand Up @@ -156,10 +187,10 @@ internal class BuildStepsUseCase @Inject constructor(
}

private fun buildModalityMatcherSteps(
projectConfiguration: ProjectConfiguration,
flowType: FlowType,
subjectQuery: SubjectQuery,
biometricDataSource: BiometricDataSource,
projectConfiguration: ProjectConfiguration,
flowType: FlowType,
subjectQuery: SubjectQuery,
biometricDataSource: BiometricDataSource,
) = projectConfiguration.general.modalities.map {
Step(
id = when (it) {
Expand All @@ -172,25 +203,31 @@ internal class BuildStepsUseCase @Inject constructor(
)
}

private fun buildEnrolLastBiometricStep(action: ActionRequest.EnrolLastBiometricActionRequest) = listOf(Step(
id = StepId.ENROL_LAST_BIOMETRIC,
navigationActionId = R.id.action_orchestratorFragment_to_enrolLast,
destinationId = EnrolLastBiometricContract.DESTINATION,
payload = EnrolLastBiometricContract.getArgs(
projectId = action.projectId,
userId = action.userId,
moduleId = action.moduleId,
steps = mapStepsForLastBiometrics(cache.steps.mapNotNull { it.result }),
),
))
private fun buildEnrolLastBiometricStep(action: ActionRequest.EnrolLastBiometricActionRequest) =
listOf(
Step(
id = StepId.ENROL_LAST_BIOMETRIC,
navigationActionId = R.id.action_orchestratorFragment_to_enrolLast,
destinationId = EnrolLastBiometricContract.DESTINATION,
payload = EnrolLastBiometricContract.getArgs(
projectId = action.projectId,
userId = action.userId,
moduleId = action.moduleId,
steps = mapStepsForLastBiometrics(cache.steps.mapNotNull { it.result }),
),
)
)

private fun buildConfirmIdentityStep(action: ActionRequest.ConfirmIdentityActionRequest) = listOf(Step(
id = StepId.CONFIRM_IDENTITY,
navigationActionId = R.id.action_orchestratorFragment_to_selectSubject,
destinationId = SelectSubjectContract.DESTINATION,
payload = SelectSubjectContract.getArgs(
projectId = action.projectId,
subjectId = action.selectedGuid,
),
))
private fun buildConfirmIdentityStep(action: ActionRequest.ConfirmIdentityActionRequest) =
listOf(
Step(
id = StepId.CONFIRM_IDENTITY,
navigationActionId = R.id.action_orchestratorFragment_to_selectSubject,
destinationId = SelectSubjectContract.DESTINATION,
payload = SelectSubjectContract.getArgs(
projectId = action.projectId,
subjectId = action.selectedGuid,
),
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
android:id="@+id/action_orchestratorFragment_to_setup"
app:destination="@id/graph_setup" />

<include app:graph="@navigation/graph_age_group_selection" />
<action
android:id="@+id/action_orchestratorFragment_to_age_group_selection"
app:destination="@id/graph_age_group_selection" />
<include app:graph="@navigation/graph_consent" />
<action
android:id="@+id/action_orchestratorFragment_to_consent"
Expand Down
1 change: 1 addition & 0 deletions feature/select-subject-age-group/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
17 changes: 17 additions & 0 deletions feature/select-subject-age-group/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
plugins {
id("simprints.feature")
id("kotlin-parcelize")
}

android {
namespace = "com.simprints.feature.selectagegroup"
}

dependencies {
implementation(project(":infra:events"))
implementation(project(":infra:auth-store"))
implementation(project(":infra:logging"))
implementation(project(":infra:config-store"))
implementation(project(":feature:exit-form"))

}
2 changes: 2 additions & 0 deletions feature/select-subject-age-group/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.simprints.feature.selectagegroup


object SelectSubjectAgeGroupContract {
val DESTINATION = R.id.selectSubjectAgeGroupFragment
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.simprints.feature.selectagegroup

import androidx.annotation.Keep
import com.simprints.infra.config.store.models.AgeGroup
import java.io.Serializable

@Keep
data class SelectSubjectAgeGroupResult(
val ageGroup: AgeGroup,
) : Serializable

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.simprints.feature.selectagegroup.screen

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.simprints.core.ExcludedFromGeneratedTestCoverageReports
import com.simprints.feature.selectagegroup.R
import com.simprints.infra.resources.R as IDR


// The age groups should be sorted as follows: Newborn, Baby, Child, Adult
// because the icons are in that order
@ExcludedFromGeneratedTestCoverageReports("UI classes are not unit tested")
internal class AgeGroupAdapter(
private val ageGroups: List<AgeGroupDisplayModel>,
private val onClick: (AgeGroupDisplayModel) -> Unit
) : RecyclerView.Adapter<AgeGroupAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.age_group_item, parent, false)
return ViewHolder(view)

}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val ageGroup = ageGroups[position]
holder.bind(ageGroup, position)
}

override fun getItemCount() = ageGroups.size

private val icons = intArrayOf(
IDR.drawable.ic_age_group_selection_new_born,
IDR.drawable.ic_age_group_selection_baby,
IDR.drawable.ic_age_group_selection_child,
IDR.drawable.ic_age_group_selection_adult
)

@ExcludedFromGeneratedTestCoverageReports("UI classes are not unit tested")
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val ageGroupTextView: TextView = itemView.findViewById(R.id.item_label)
private val ageGroupIcon: ImageView = itemView.findViewById(R.id.item_icon)
fun bind(ageGroupDisplayModel: AgeGroupDisplayModel, position: Int) {
ageGroupTextView.text = ageGroupDisplayModel.displayString
// if the position is greater than the number of icons, use the last icon
ageGroupIcon.setImageResource(icons.getOrNull(position) ?: icons.last())

itemView.setOnClickListener {
onClick(ageGroupDisplayModel)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.simprints.feature.selectagegroup.screen

import com.simprints.infra.config.store.models.AgeGroup

internal data class AgeGroupDisplayModel(val displayString: String, val range: AgeGroup)
Loading