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
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@ internal class IntentToActionMapper @Inject constructor(
return when (actionIdentifier.packageName) {
OdkConstants.PACKAGE_NAME -> mapOdkAction(actionIdentifier, extras, project)
CommCareConstants.PACKAGE_NAME -> mapCommCareAction(actionIdentifier, extras, project)
LibSimprintsConstants.PACKAGE_NAME -> {
mapLibSimprintsAction(actionIdentifier, extras, project)
}

LibSimprintsConstants.PACKAGE_NAME -> mapLibSimprintsAction(actionIdentifier, extras, project)
else -> throw InvalidRequestException(
"Unsupported package name", ClientApiError.INVALID_STATE_FOR_INTENT_ACTION
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ internal class EnrolRequestBuilder(
actionIdentifier = actionIdentifier,
projectId = extractor.getProjectId(),
userId = extractor.getUserId().asTokenizableRaw(),
moduleId = extractor.getModuleId().asTokenizableRaw(),
biometricDataSource = extractor.getBiometricDataSource(),
subjectAge = extractor.getSubjectAge(),
callerPackageName = extractor.getCallerPackageName(),
metadata = extractor.getMetadata(),
moduleId = extractor.getModuleId().asTokenizableRaw(),
unknownExtras = extractor.getUnknownExtras()
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ internal class IdentifyRequestBuilder(
userId = extractor.getUserId().asTokenizableRaw(),
moduleId = extractor.getModuleId().asTokenizableRaw(),
biometricDataSource = extractor.getBiometricDataSource(),
subjectAge = extractor.getSubjectAge(),
callerPackageName = extractor.getCallerPackageName(),
metadata = extractor.getMetadata(),
unknownExtras = extractor.getUnknownExtras()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ internal class VerifyRequestBuilder(
userId = extractor.getUserId().asTokenizableRaw(),
moduleId = extractor.getModuleId().asTokenizableRaw(),
biometricDataSource = extractor.getBiometricDataSource(),
subjectAge = extractor.getSubjectAge(),
callerPackageName = extractor.getCallerPackageName(),
metadata = extractor.getMetadata(),
verifyGuid = extractor.getVerifyGuid(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.simprints.feature.clientapi.mappers.request.extractors

import android.content.Intent
import com.simprints.core.tools.json.JsonHelper
import com.simprints.feature.clientapi.extensions.extractString
import com.simprints.feature.clientapi.models.ClientApiConstants
import com.simprints.libsimprints.Constants
Expand All @@ -14,7 +15,7 @@ internal abstract class ActionRequestExtractor(private val extras: Map<String, A
Constants.SIMPRINTS_PROJECT_ID,
Constants.SIMPRINTS_USER_ID,
Constants.SIMPRINTS_MODULE_ID,
Constants.SIMPRINTS_METADATA
Constants.SIMPRINTS_METADATA,
)

open fun getProjectId(): String = extras.extractString(Constants.SIMPRINTS_PROJECT_ID)
Expand All @@ -29,6 +30,15 @@ internal abstract class ActionRequestExtractor(private val extras: Map<String, A

open fun getMetadata(): String = extras.extractString(Constants.SIMPRINTS_METADATA)

open fun getSubjectAge(): Int? {
return try {
val parsedMetadata = JsonHelper.fromJson<Map<String, Any>>(getMetadata())
parsedMetadata[Constants.SIMPRINTS_SUBJECT_AGE] as? Int
} catch (e: Exception) {
null
}
}

protected open fun Intent.extractString(key: String): String = this.getStringExtra(key) ?: ""

open fun getUnknownExtras(): Map<String, Any?> = extras.filter { it.key.isNotBlank() && !expectedKeys.contains(it.key) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ internal class LibSimprintsResponseMapper @Inject constructor() {
AppErrorReason.BACKEND_MAINTENANCE_ERROR -> Constants.SIMPRINTS_BACKEND_MAINTENANCE_ERROR
AppErrorReason.PROJECT_PAUSED -> Constants.SIMPRINTS_PROJECT_PAUSED
AppErrorReason.PROJECT_ENDING -> Constants.SIMPRINTS_PROJECT_ENDING
AppErrorReason.AGE_GROUP_NOT_SUPPORTED -> Constants.SIMPRINTS_AGE_GROUP_NOT_SUPPORTED

/*
TODO incorporate these error codes into the client api
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal class IsFlowCompletedWithErrorUseCase @Inject constructor() {
AppErrorReason.GUID_NOT_FOUND_OFFLINE,
AppErrorReason.PROJECT_PAUSED,
AppErrorReason.PROJECT_ENDING,
AppErrorReason.AGE_GROUP_NOT_SUPPORTED,
-> true

AppErrorReason.ROOTED_DEVICE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ internal abstract class RequestActionFactory {
every { mockExtractor.getUserId() } returns MOCK_USER_ID
every { mockExtractor.getModuleId() } returns MOCK_MODULE_ID
every { mockExtractor.getMetadata() } returns MOCK_METADATA
every { mockExtractor.getSubjectAge() } returns null
every { mockExtractor.getBiometricDataSource() } returns MOCK_BIOMETRIC_DATA_SOURCE
every { mockExtractor.getCallerPackageName() } returns MOCK_CALLER_PACKAGE_NAME
every { mockExtractor.getUnknownExtras() } returns emptyMap()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ class LibSimprintsResponseMapperTest {
AppErrorReason.BACKEND_MAINTENANCE_ERROR to Constants.SIMPRINTS_BACKEND_MAINTENANCE_ERROR,
AppErrorReason.PROJECT_PAUSED to Constants.SIMPRINTS_PROJECT_PAUSED,
AppErrorReason.PROJECT_ENDING to Constants.SIMPRINTS_PROJECT_ENDING,
AppErrorReason.AGE_GROUP_NOT_SUPPORTED to Constants.SIMPRINTS_AGE_GROUP_NOT_SUPPORTED,
).forEach { (reason, expectedCode) ->
val extras = mapper(
ActionResponse.ErrorActionResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,82 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.simprints.feature.dashboard.databinding.ItemFingerSelectionBinding
import com.simprints.feature.dashboard.databinding.HeaderSdkNameBinding
import com.simprints.infra.config.store.models.Finger
import com.simprints.infra.resources.R as IDR

internal class FingerSelectionItemAdapter(
private val getItems: () -> List<FingerSelectionItem>,
) :
RecyclerView.Adapter<FingerSelectionItemAdapter.FingerSelectionItemViewHolder>() {
private val getItems: () -> List<FingerSelectionSection>,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): FingerSelectionItemViewHolder {
companion object {
private const val TYPE_HEADER = 0
private const val TYPE_ITEM = 1
}

override fun getItemCount(): Int = getItems().sumOf { it.items.size + 1 }

override fun getItemViewType(position: Int): Int {
var pos = 0
for (section in getItems()) {
if (position == pos) {
return TYPE_HEADER
}
pos += section.items.size + 1
if (position < pos) {
return TYPE_ITEM
}
}
throw IllegalArgumentException("Invalid position")
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ItemFingerSelectionBinding.inflate(inflater, parent, false)
return FingerSelectionItemViewHolder(
parent.context,
getItems,
binding
)
return if (viewType == TYPE_HEADER) {
val binding = HeaderSdkNameBinding.inflate(inflater, parent, false)
HeaderViewHolder(parent.context, binding)
} else {
val binding = ItemFingerSelectionBinding.inflate(inflater, parent, false)
return FingerSelectionItemViewHolder(parent.context, binding)
}
}

override fun getItemCount(): Int = getItems().size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
var totalItems = 0
for (section in getItems()) {
if (position == totalItems && holder is HeaderViewHolder) {
holder.bind(section.sdkName)
return
} else if (position < totalItems + section.items.size + 1 && holder is FingerSelectionItemViewHolder) {
holder.bind(section.items[position - totalItems - 1])
return
}
totalItems += section.items.size + 1
}
}

class HeaderViewHolder(
val context: Context,
binding: HeaderSdkNameBinding
) : RecyclerView.ViewHolder(binding.root) {
private val textView = binding.headerText

override fun onBindViewHolder(viewHolder: FingerSelectionItemViewHolder, position: Int) {
viewHolder.bind()
fun bind(sdkName: String) {
textView.text = sdkName
}
}

class FingerSelectionItemViewHolder(
val context: Context,
private val getItems: () -> List<FingerSelectionItem>,
binding: ItemFingerSelectionBinding
) : RecyclerView.ViewHolder(binding.root) {

private val fingerNameTextView = binding.fingerNameTextView
private val fingerQuantityTextView = binding.fingerQuantityTextView

fun bind() {
fingerNameTextView.text = getItems()[adapterPosition].finger.toString(context)
fingerQuantityTextView.text = getItems()[adapterPosition].quantity.toString()
fun bind(item: FingerSelectionItem) {
fingerNameTextView.text = item.finger.toString(context)
fingerQuantityTextView.text = item.quantity.toString()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@ internal class FingerSelectionViewModel @Inject constructor(
private val configManager: ConfigManager,
) : ViewModel() {

val fingerSelections: LiveData<List<FingerSelectionItem>>
val fingerSelections: LiveData<List<FingerSelectionSection>>
get() = _fingerSelections
private val _fingerSelections = MutableLiveData<List<FingerSelectionItem>>()
private val _fingerSelections = MutableLiveData<List<FingerSelectionSection>>()

fun start() {
viewModelScope.launch {
_fingerSelections.postValue(
configManager.getProjectConfiguration().fingerprint!!.bioSdkConfiguration.fingersToCapture
.toFingerSelectionItems()
)
val fingerSelections = mutableListOf<FingerSelectionSection>()
configManager.getProjectConfiguration().fingerprint?.secugenSimMatcher?.fingersToCapture?.let {
fingerSelections.add(FingerSelectionSection("SimMatcher", it.toFingerSelectionItems()))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reasonable way to avoid hardcoding names?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have anything like a name parameter for SDK config. I can put the names in the resources so they can be reused but this is the first time they appear in UI.

}
configManager.getProjectConfiguration().fingerprint?.nec?.fingersToCapture?.let {
fingerSelections.add(FingerSelectionSection("NEC", it.toFingerSelectionItems()))
}
_fingerSelections.postValue(fingerSelections)
}
}

Expand All @@ -45,6 +49,11 @@ internal class FingerSelectionViewModel @Inject constructor(
}
}

data class FingerSelectionSection(
val sdkName: String,
val items: List<FingerSelectionItem>
)

data class FingerSelectionItem(
var finger: Finger,
var quantity: Int
Expand Down
18 changes: 18 additions & 0 deletions feature/dashboard/src/main/res/layout/header_sdk_name.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:padding="16dp">

<TextView
android:id="@+id/header_text"
style="@style/Text.Headline6.Bold"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:gravity="center"
tools:text="Header" />

</LinearLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config

private const val SIM_MATCHER_NAME = "SimMatcher"

@RunWith(AndroidJUnit4::class)
@HiltAndroidTest
@Config(application = HiltTestApplication::class)
Expand All @@ -33,56 +35,47 @@ class FingerSelectionFragmentTest {

@BindValue
@JvmField
internal val viewModel = mockk<FingerSelectionViewModel>(relaxed = true) {
every { fingerSelections } returns mockk {
every { value } returns listOf(
FingerSelectionItem(Finger.LEFT_THUMB, 1),
FingerSelectionItem(Finger.RIGHT_THUMB, 1),
FingerSelectionItem(Finger.LEFT_INDEX_FINGER, 2)
)
every { observe(any(), any()) } answers {
secondArg<Observer<List<FingerSelectionItem>>>().onChanged(
listOf(
FingerSelectionItem(Finger.LEFT_THUMB, 1),
FingerSelectionItem(Finger.RIGHT_THUMB, 1),
FingerSelectionItem(Finger.LEFT_INDEX_FINGER, 2)
)
)
}
}
}
internal val viewModel = mockk<FingerSelectionViewModel>(relaxed = true)

@Test
fun `should display the fingers correctly`() {
mockFingerSelections(
listOf(
FingerSelectionItem(Finger.LEFT_THUMB, 1),
FingerSelectionItem(Finger.RIGHT_THUMB, 3),
FingerSelectionItem(Finger.LEFT_INDEX_FINGER, 2)
FingerSelectionSection(
sdkName = SIM_MATCHER_NAME,
items = listOf(
FingerSelectionItem(Finger.LEFT_THUMB, 1),
FingerSelectionItem(Finger.RIGHT_THUMB, 2),
FingerSelectionItem(Finger.LEFT_INDEX_FINGER, 3)
)
),
)
)
launchFragmentInHiltContainer<FingerSelectionFragment>()

onView(withId(R.id.fingerSelectionRecyclerView)).check(matches(hasChildCount(3)))
onView(withId(R.id.fingerSelectionRecyclerView)).check(matches(hasChildCount(4)))

onView(nThFingerSelection(0))
.check(matches(hasDescendant(withText(SIM_MATCHER_NAME))))

onView(nThFingerSelection(1))
.check(matches(hasDescendant(withText(IDR.string.fingerprint_capture_finger_l_1))))
.check(matches(hasDescendant(withText("1"))))

onView(nThFingerSelection(1))
onView(nThFingerSelection(2))
.check(matches(hasDescendant(withText(IDR.string.fingerprint_capture_finger_r_1))))
.check(matches(hasDescendant(withText("3"))))
.check(matches(hasDescendant(withText("2"))))

onView(nThFingerSelection(2))
onView(nThFingerSelection(3))
.check(matches(hasDescendant(withText(IDR.string.fingerprint_capture_finger_l_2))))
.check(matches(hasDescendant(withText("2"))))
.check(matches(hasDescendant(withText("3"))))
}

private fun mockFingerSelections(fingers: List<FingerSelectionItem>) {
private fun mockFingerSelections(fingers: List<FingerSelectionSection>) {
every { viewModel.fingerSelections } returns mockk {
every { value } returns fingers
every { observe(any(), any()) } answers {
secondArg<Observer<List<FingerSelectionItem>>>().onChanged(fingers)
secondArg<Observer<List<FingerSelectionSection>>>().onChanged(fingers)
}
}
}
Expand Down
Loading