-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Base Card, PDF417 and MRZ Scanners (#22)
- generic multi-credential base card (+ credential pack) - a generic PDF417 scanner - an initial MRZ (OCR) scanner based on the first QR Code scanner implementation - refactors the previous QR Code Scanner version Co-authored-by: Gregório Granado Magalhães <[email protected]>
- Loading branch information
1 parent
6056c61
commit 0631c88
Showing
11 changed files
with
1,232 additions
and
306 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 22 additions & 1 deletion
23
MobileSdk/src/main/java/com/spruceid/mobile/sdk/BaseCredential.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,33 @@ | ||
package com.spruceid.mobile.sdk | ||
|
||
open class BaseCredential constructor(private val id: String?) { | ||
open class BaseCredential { | ||
private var id: String? | ||
|
||
constructor() { | ||
this.id = null | ||
} | ||
|
||
constructor(id: String) { | ||
this.id = id | ||
} | ||
|
||
fun getId(): String? { | ||
return this.id | ||
} | ||
|
||
fun setId(id: String) { | ||
this.id = id | ||
} | ||
|
||
override fun toString(): String { | ||
return "Credential($id)" | ||
} | ||
|
||
open fun get(keys: List<String>): Map<String, Any> { | ||
return if (keys.contains("id")) { | ||
mapOf("id" to this.id!!) | ||
} else { | ||
emptyMap() | ||
} | ||
} | ||
} |
108 changes: 108 additions & 0 deletions
108
MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialPack.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package com.spruceid.mobile.sdk | ||
|
||
import java.security.KeyFactory | ||
import java.security.KeyStore | ||
import java.security.cert.Certificate | ||
import java.security.cert.CertificateFactory | ||
import java.security.spec.PKCS8EncodedKeySpec | ||
import java.util.Base64 | ||
|
||
/** | ||
* Collection of BaseCredentials with methods to interact with all instances | ||
*/ | ||
class CredentialPack { | ||
private val credentials: MutableList<BaseCredential> | ||
|
||
constructor() { | ||
credentials = mutableListOf() | ||
} | ||
|
||
constructor(credentialsArray: MutableList<BaseCredential>) { | ||
this.credentials = credentialsArray | ||
} | ||
|
||
fun addW3CVC(credentialString: String): List<BaseCredential> { | ||
val vc = W3CVC(credentialString = credentialString) | ||
credentials.add(vc) | ||
return credentials | ||
} | ||
|
||
fun addMDoc( | ||
id: String, | ||
mdocBase64: String, | ||
keyPEM: String, | ||
keyBase64: String | ||
): List<BaseCredential> { | ||
try { | ||
val decodedKey = Base64.getDecoder().decode( | ||
keyBase64 | ||
) | ||
|
||
val privateKey = KeyFactory.getInstance( | ||
"EC" | ||
).generatePrivate( | ||
PKCS8EncodedKeySpec( | ||
decodedKey | ||
) | ||
) | ||
|
||
val cert: Array<Certificate> = arrayOf( | ||
CertificateFactory.getInstance( | ||
"X.509" | ||
).generateCertificate( | ||
keyPEM.byteInputStream() | ||
) | ||
) | ||
|
||
val ks: KeyStore = KeyStore.getInstance( | ||
"AndroidKeyStore" | ||
) | ||
|
||
ks.load( | ||
null | ||
) | ||
|
||
ks.setKeyEntry( | ||
"someAlias", | ||
privateKey, | ||
null, | ||
cert | ||
) | ||
|
||
credentials.add( | ||
MDoc( | ||
id, | ||
Base64.getDecoder().decode(mdocBase64), | ||
"someAlias" | ||
) | ||
) | ||
} catch (e: Throwable) { | ||
print( | ||
e | ||
) | ||
throw e | ||
} | ||
return credentials | ||
} | ||
|
||
fun get(keys: List<String>): Map<String, Map<String, Any>> { | ||
val values = emptyMap<String, Map<String, Any>>().toMutableMap() | ||
|
||
for (credential in credentials) { | ||
values[credential.getId()!!] = credential.get(keys) | ||
} | ||
return values | ||
} | ||
|
||
fun getCredentialsByIds(credentialsIds: List<String>): List<BaseCredential> { | ||
return credentials.filter { credential -> credentialsIds.contains(credential.getId()) } | ||
} | ||
|
||
fun getCredentials(): List<BaseCredential> { | ||
return credentials | ||
} | ||
|
||
fun getCredentialById(credentialId: String): BaseCredential? { | ||
return credentials.find { credential -> credential.getId().equals(credentialId) } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.spruceid.mobile.sdk | ||
|
||
import org.json.JSONObject | ||
|
||
class W3CVC(credentialString: String): BaseCredential() { | ||
private var credential: JSONObject = JSONObject(credentialString) | ||
|
||
init { | ||
super.setId(credential.getString("id")) | ||
} | ||
|
||
override fun get(keys: List<String>): Map<String, Any> { | ||
val res = mutableMapOf<String,Any>() | ||
|
||
for (key in keys) { | ||
res[key] = keyPathFinder(credential, key.split(".").toMutableList()) | ||
} | ||
return res | ||
} | ||
|
||
private fun keyPathFinder(json: Any, path: MutableList<String>): Any { | ||
try { | ||
val firstKey = path.first() | ||
val element = (json as JSONObject)[firstKey] | ||
path.removeAt(0) | ||
if (path.isNotEmpty()) { | ||
return keyPathFinder(element, path) | ||
} | ||
return element | ||
} catch (e: Exception) { | ||
return "" | ||
} | ||
} | ||
} |
173 changes: 173 additions & 0 deletions
173
MobileSdk/src/main/java/com/spruceid/mobile/sdk/ui/BaseCard.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
package com.spruceid.mobile.sdk.ui | ||
|
||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.IntrinsicSize | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
import com.spruceid.mobile.sdk.CredentialPack | ||
|
||
/** | ||
* Data class with the specification to display the credential pack in a list view | ||
* @property titleKeys A list of keys that will be used to generate a list of values extracted from the credentials | ||
* @property titleFormatter Method used to create a custom title field. Receives an array of values based on the array of keys for the same field | ||
* @property descriptionKeys A list of keys that will be used to generate a list of values extracted from the credentials | ||
* @property descriptionFormatter Method used to create a custom description field. Receives an array of values based on the array of keys for the same field | ||
* @property leadingIconKeys A list of keys that will be used to generate a list of values extracted from the credentials | ||
* @property leadingIconFormatter Method used to create a custom leading icon formatter. Receives an array of values based on the array of keys for the same field | ||
* @property trailingActionKeys A list of keys that will be used to generate a list of values extracted from the credentials | ||
* @property trailingActionButton Method used to create a custom trailing action button. Receives an array of values based on the array of keys for the same field | ||
*/ | ||
data class CardRenderingListView( | ||
val titleKeys: List<String>, | ||
val titleFormatter: @Composable ((values: Map<String, Map<String, Any>>) -> Unit)? = null, | ||
val descriptionKeys: List<String>? = null, | ||
val descriptionFormatter: @Composable ((values: Map<String, Map<String, Any>>) -> Unit)? = null, | ||
val leadingIconKeys: List<String>? = null, | ||
val leadingIconFormatter: @Composable ((values: Map<String, Map<String, Any>>) -> Unit)? = null, | ||
val trailingActionKeys: List<String>? = null, | ||
val trailingActionButton: @Composable ((values: Map<String, Map<String, Any>>) -> Unit)? = null | ||
) | ||
|
||
/** | ||
* Data class with the specification to display the credential field in a details view | ||
* @property keys A list of keys that will be used to generate a list of values extracted from the credentials | ||
* @property formatter Method used to create a custom field. Receives an array of values based on the array of keys for the same field | ||
*/ | ||
data class CardRenderingDetailsField( | ||
val keys: List<String>, | ||
val formatter: @Composable ((values: Map<String, Map<String, Any>>) -> Unit)? = null | ||
) | ||
|
||
/** | ||
* Data class with the specification to display the credential in a details view | ||
* @property fields A list of field render settings that will be used to generate a UI element with the defined keys | ||
*/ | ||
data class CardRenderingDetailsView( | ||
val fields: List<CardRenderingDetailsField> | ||
) | ||
|
||
|
||
/** | ||
* Interface aggregating two types: | ||
* (LIST == CardRenderingListView) and | ||
* (DETAILS == CardRenderingDetailsView) | ||
*/ | ||
sealed interface CardRendering | ||
@JvmInline | ||
value class LIST(val rendering: CardRenderingListView) : CardRendering | ||
@JvmInline | ||
value class DETAILS(val rendering: CardRenderingDetailsView) : CardRendering | ||
|
||
/** | ||
* Method to convert CardRenderingListView to CardRendering | ||
*/ | ||
fun CardRenderingListView.toCardRendering() = LIST(this) | ||
/** | ||
* Method to convert CardRenderingDetailsView to CardRendering | ||
*/ | ||
fun CardRenderingDetailsView.toCardRendering() = DETAILS(this) | ||
|
||
/** | ||
* Manages the card rendering type according with the render object | ||
* @property credentialPack CredentialPack instance | ||
* @property rendering CardRendering instance | ||
*/ | ||
@Composable | ||
fun BaseCard( | ||
credentialPack: CredentialPack, | ||
rendering: CardRendering | ||
) { | ||
when(rendering) { | ||
is LIST -> | ||
CardListView(credentialPack = credentialPack, rendering = rendering.rendering) | ||
is DETAILS -> | ||
CardDetailsView(credentialPack = credentialPack, rendering = rendering.rendering) | ||
} | ||
} | ||
|
||
/** | ||
* Renders the credential as a list view item | ||
* @property credentialPack CredentialPack instance | ||
* @property rendering CardRenderingListView instance | ||
*/ | ||
@Composable | ||
fun CardListView( | ||
credentialPack: CredentialPack, | ||
rendering: CardRenderingListView | ||
) { | ||
val titleValues = credentialPack.get(rendering.titleKeys) | ||
val descriptionValues = credentialPack.get(rendering.descriptionKeys ?: emptyList()) | ||
|
||
Row( | ||
Modifier.height(intrinsicSize = IntrinsicSize.Max) | ||
) { | ||
// Leading icon | ||
if(rendering.leadingIconFormatter != null) { | ||
rendering.leadingIconFormatter.invoke( | ||
credentialPack.get(rendering.leadingIconKeys ?: emptyList()) | ||
) | ||
} | ||
|
||
Column { | ||
// Title | ||
if(rendering.titleFormatter != null) { | ||
rendering.titleFormatter.invoke(titleValues) | ||
} else { | ||
Text(text = titleValues.values | ||
.fold(emptyList<String>()) { acc, next -> acc + next.values | ||
.joinToString(" ") { value -> value.toString() } | ||
}.joinToString("").trim()) | ||
} | ||
|
||
// Description | ||
if(rendering.descriptionFormatter != null) { | ||
rendering.descriptionFormatter.invoke(descriptionValues) | ||
} else { | ||
Text(text = descriptionValues.values | ||
.fold(emptyList<String>()) { acc, next -> acc + next.values | ||
.joinToString(" ") { value -> value.toString() } | ||
}.joinToString("").trim()) | ||
} | ||
} | ||
|
||
Spacer(modifier = Modifier.weight(1.0f)) | ||
|
||
// Trailing action button | ||
if(rendering.trailingActionButton != null) { | ||
rendering.trailingActionButton.invoke( | ||
credentialPack.get(rendering.trailingActionKeys ?: emptyList()) | ||
) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Renders the credential as a details view | ||
* @property credentialPack CredentialPack instance | ||
* @property rendering CardRenderingDetailsView instance | ||
*/ | ||
@Composable | ||
fun CardDetailsView( | ||
credentialPack: CredentialPack, | ||
rendering: CardRenderingDetailsView | ||
) { | ||
Column { | ||
rendering.fields.forEach { | ||
val values = credentialPack.get(it.keys) | ||
|
||
if(it.formatter != null) { | ||
it.formatter.invoke(values) | ||
} else { | ||
Text(text = values.values | ||
.fold(emptyList<String>()) { acc, next -> acc + next.values | ||
.joinToString(" ") { value -> value.toString() } | ||
}.joinToString("").trim()) | ||
} | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.