Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Base Card, PDF417 and MRZ Scanners #22

Merged
merged 5 commits into from
Aug 28, 2024
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
6 changes: 3 additions & 3 deletions MobileSdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ android {
}

dependencies {
api("com.spruceid.mobile.sdk.rs:mobilesdkrs:0.0.26")
api("com.spruceid.mobile.sdk.rs:mobilesdkrs:0.0.28")
//noinspection GradleCompatible
implementation("com.android.support:appcompat-v7:28.0.0")
/* Begin UI dependencies */
Expand All @@ -128,10 +128,10 @@ dependencies {
implementation("androidx.camera:camera-view:1.3.2")
implementation("com.google.zxing:core:3.5.1")
implementation("com.google.accompanist:accompanist-permissions:0.34.0")
implementation("androidx.test.ext:junit-ktx:1.1.5")
implementation("androidx.camera:camera-mlkit-vision:1.3.0-alpha06")
implementation("com.google.android.gms:play-services-mlkit-text-recognition:19.0.0")
/* End UI dependencies */
testImplementation("junit:junit:4.13.2")

androidTestImplementation("com.android.support.test:runner:1.0.2")
androidTestImplementation("com.android.support.test.espresso:espresso-core:3.0.2")
}
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 MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialPack.kt
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) }
}
}
34 changes: 34 additions & 0 deletions MobileSdk/src/main/java/com/spruceid/mobile/sdk/W3CVC.kt
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 MobileSdk/src/main/java/com/spruceid/mobile/sdk/ui/BaseCard.kt
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())
}
}
}

}
Loading
Loading