Skip to content
Open
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
2 changes: 1 addition & 1 deletion android-holder-tutorial-sample-app/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ dependencies {
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
implementation("global.mattr.mobilecredential:holder:6.1.2")
implementation("global.mattr.mobilecredential:holder:7.0.0")
implementation("androidx.navigation:navigation-compose:2.9.0")
implementation("com.google.accompanist:accompanist-permissions:0.36.0")
implementation("com.journeyapps:zxing-android-embedded:4.3.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import global.mattr.mobilecredential.holder.MobileCredentialHolder
import global.mattr.mobilecredential.holder.ProximityPresentationSession
import global.mattr.mobilecredential.holder.issuance.CredentialIssuanceConfiguration
import global.mattr.mobilecredential.holder.issuance.dto.DiscoveredCredentialOffer
import global.mattr.mobilecredential.holder.issuance.dto.RetrieveCredentialResult
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

Expand Down Expand Up @@ -142,11 +143,11 @@ fun HomeScreen(activity: Activity, navController: NavController) {
SharedData.discoveredCredentialOffer?.let { discoveredOffer ->
Text("Received Credential Offer from ${discoveredOffer.issuer}")
LazyColumn(Modifier.fillMaxWidth()) {
items(discoveredOffer.credentials, key = { it.doctype }) { credential ->
items(discoveredOffer.credentials, key = { it.docType }) { credential ->
Card(Modifier.fillMaxWidth()) {
Column(Modifier.padding(4.dp)) {
Text("Name: ${credential.name ?: ""}")
Text("DocType: ${credential.doctype}")
Text("DocType: ${credential.docType}")
}
}
}
Expand Down Expand Up @@ -196,16 +197,24 @@ private fun onRetrieveCredentials(
)

// Claim Credential - Step 4.6: Display retrieved credentials
SharedData.retrievedCredentials = retrieveCredentialResults.mapNotNull {
try {
// The credential ID can be used to get the full credential from the SDK storage.
// fetchUpdatedStatusList - Whether to enforce the online revocation status check for the credential.
// Returned object contains all credential data, including the user's PII.
mdocHolder.getCredential(it.credentialId!!, fetchUpdatedStatusList = false)
} catch (e: Exception) {
val msg = "Failed to get credential from storage"
Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show()
null
// RetrieveCredentialResult is a sealed interface with Success and Failure variants.
SharedData.retrievedCredentials = retrieveCredentialResults.mapNotNull { result ->
when (result) {
is RetrieveCredentialResult.Success -> try {
// The credential ID can be used to get the full credential from the SDK storage.
// fetchUpdatedStatusList - Whether to enforce the online revocation status check for the credential.
// Returned object contains all credential data, including the user's PII.
mdocHolder.getCredential(result.credentialId, fetchUpdatedStatusList = false)
} catch (e: Exception) {
val msg = "Failed to get credential from storage"
Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show()
null
}
is RetrieveCredentialResult.Failure -> {
val msg = "Failed to retrieve ${result.docType}: ${result.error}"
Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show()
null
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ fun OnlinePresentationScreen(activity: Activity, requestUri: String) {
}
}

// session.matchedCredentials - Map that pairs credential requests to lists of the stored credentials that match those requests.
// session.getMatchedCredentials() - Map that pairs credential requests to lists of the stored credentials that match those requests.
// Credentials in the list will only contain the requested claim names (e.g. "given_name", "family_name") without values.
// Values can be retrieved from storage by calling getCredential() with the credential id.
val (requested, matched) = session?.matchedCredentials?.entries?.firstOrNull() ?: return
val (requested, matched) = session?.getMatchedCredentials()?.entries?.firstOrNull() ?: return

var matchedCredentials by remember { mutableStateOf(matched) }
var selectedCredentialId by remember { mutableStateOf(matchedCredentials.first().id) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
//

import SwiftUI
import Combine
// Claim Credential - Step 1.2: Import MobileCredentialHolderSDK
import MobileCredentialHolderSDK

struct ContentView: View {
@ObservedObject var viewModel: ViewModel = ViewModel()
@State var viewModel: ViewModel = ViewModel()
var body: some View {
NavigationStack(path: $viewModel.navigationPath) {
VStack {
Expand Down Expand Up @@ -73,6 +72,10 @@ struct ContentView: View {
// Navigate to online presentation view
viewModel.navigationPath.append(NavigationState.onlinePresentation)
}
// Claim Credential - Step 1.4: Initialize the SDK when the view appears
.task {
await viewModel.initialize()
}
}
}

Expand Down Expand Up @@ -112,7 +115,7 @@ struct ContentView: View {
Text("No. of claims:")
.bold()
Spacer()
Text("\(credential.claims.count)")
Text("\(credential.claims?.count ?? 0)")
}
}
}
Expand Down Expand Up @@ -191,32 +194,32 @@ struct ContentView: View {
}
}

class ViewModel: ObservableObject {
@Published var navigationPath = NavigationPath()
@Observable class ViewModel {
var navigationPath = NavigationPath()
// Claim Credential - Step 1.3: Add MobileCredentialHolder var
var mobileCredentialHolder: MobileCredentialHolder

// Claim Credential - Step 3.1: Add DiscoveredCredentialOffer and discoveredCredentialOfferURL vars
@Published var discoveredCredentialOffer: DiscoveredCredentialOffer?
var discoveredCredentialOffer: DiscoveredCredentialOffer?
var discoveredCredentialOfferURL = ""

// Claim Credential - Step 4.1: Add retrievedCredentials var
@Published var retrievedCredentials: [MobileCredential] = []
var retrievedCredentials: [MobileCredential] = []


// Proximity Presentation - Step 1.2: Create deviceEngagementString and proximityPresentationSession variables
@Published var deviceEngagementString: String?
@Published var proximityPresentationSession: ProximityPresentationSession?
var deviceEngagementString: String?
var proximityPresentationSession: ProximityPresentationSession?


// Proximity and Online Presentation: Create variables for credential presentations
@Published var matchedCredentials: [MobileCredential] = []
@Published var matchedMetadata: [MobileCredentialMetadata] = []
@Published var credentialRequest: [MobileCredentialRequest] = []
var matchedCredentials: [MobileCredential] = []
var matchedMetadata: [MobileCredentialMetadata] = []
var credentialRequest: [MobileCredentialRequest] = []


// Online Presentation - Step 2.1: Create a variable to hold the online presentation session object
@Published var onlinePresentationSession: OnlinePresentationSession?
var onlinePresentationSession: OnlinePresentationSession?


var shouldDisplayOnlinePresentation: Bool {
Expand All @@ -226,18 +229,24 @@ class ViewModel: ObservableObject {

// Claim Credential - Step 1.4: Initialize MobileCredentialHolder SDK
init() {
do {
mobileCredentialHolder = MobileCredentialHolder.shared
try mobileCredentialHolder.initialize(
userAuthenticationConfiguration: UserAuthenticationConfiguration(userAuthenticationBehavior: .onDeviceKeyAccess),
credentialIssuanceConfiguration: CredentialIssuanceConfiguration(
redirectUri: Constants.redirectUri,
autoTrustMobileCredentialIaca: true
)
)
} catch {
print(error)
}

// `initialize` is asynchronous as of iOS Holder SDK v6.0.0, so it runs in an async method called
// from the view's `.task` modifier when the view appears.
@MainActor
func initialize() async {
do {
try await mobileCredentialHolder.initialize(
userAuthenticationConfiguration: UserAuthenticationConfiguration(userAuthenticationBehavior: .onDeviceKeyAccess),
credentialIssuanceConfiguration: CredentialIssuanceConfiguration(
redirectUri: Constants.redirectUri,
autoTrustMobileCredentialIaca: true
)
)
} catch {
print(error)
}
}

@MainActor
Expand Down Expand Up @@ -286,10 +295,13 @@ extension ViewModel {
Task {
var credentials: [MobileCredential] = []
for result in retrievedCredentialResults {
if let credentialId = result.credentialId {
switch result {
case .success(_, let credentialId):
if let credential = try? await mobileCredentialHolder.getCredential(credentialId: credentialId) {
credentials.append(credential)
}
case .failure(let docType, let error):
print("Failed to retrieve \(docType): \(error)")
}
}
self.retrievedCredentials = credentials
Expand All @@ -313,13 +325,12 @@ extension ViewModel {
Task {
do {
onlinePresentationSession = try await mobileCredentialHolder.createOnlinePresentationSession(authorizationRequestUri: authorizationRequestURI, requireTrustedVerifier: false)
matchedMetadata = onlinePresentationSession?.matchedCredentials?
let matched = onlinePresentationSession?.getMatchedCredentials() ?? []
matchedMetadata = matched
.flatMap { $0.matchedMobileCredentials }
.compactMap { $0 } ?? []

credentialRequest = onlinePresentationSession?.matchedCredentials?
.compactMap { $0.request }
.compactMap { $0 } ?? []
credentialRequest = matched
.map { $0.request }
} catch {
print(error.localizedDescription)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import MobileCredentialHolderSDK
import SwiftUI
import Combine

struct DocumentView: View {

Expand Down Expand Up @@ -53,23 +52,23 @@ import Combine

// MARK: DocumentViewModel

class DocumentViewModel: ObservableObject {
class DocumentViewModel {
var docType: String

var namespacesAndClaims: [String: [String: String?]]

init(from credential: MobileCredential) {
self.docType = credential.docType
self.namespacesAndClaims = credential.claims?.reduce(into: [String: [String: String]]()) { result, outerElement in
self.namespacesAndClaims = credential.claims.reduce(into: [String: [String: String]]()) { result, outerElement in
let (outerKey, innerDict) = outerElement
result[outerKey] = innerDict.mapValues { $0.textRepresentation }
} ?? [:]
}
}

init(from credentialMetadata: MobileCredentialMetadata) {
self.docType = credentialMetadata.docType
var result: [String: [String: String?]] = [:]
credentialMetadata.claims?.forEach { namespace, claimIDs in
credentialMetadata.claims.forEach { namespace, claimIDs in
var transformedClaims: [String: String?] = [:]
claimIDs.forEach { claimID in
transformedClaims[claimID] = Optional<String>.none
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,11 @@

import MobileCredentialHolderSDK
import SwiftUI
import Combine

struct PresentCredentialsView: View {
@ObservedObject var viewModel: PresentCredentialsViewModel
var viewModel: PresentCredentialsViewModel
@State var selectedID: String?

init(viewModel: PresentCredentialsViewModel) {
self.viewModel = viewModel
}

var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
Expand Down Expand Up @@ -81,7 +76,7 @@ struct PresentCredentialsView: View {

// MARK: PresentCredentialsViewModel

class PresentCredentialsViewModel: ObservableObject {
class PresentCredentialsViewModel {
@Binding var requestedDocuments: [MobileCredentialRequest]
@Binding var matchedCredentials: [MobileCredential]
@Binding var matchedMetadata: [MobileCredentialMetadata]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import SwiftUI

struct TransactionCodeInputView: View {
@ObservedObject var viewModel: ViewModel
var viewModel: ViewModel
@State private var transactionCode = ""
@Environment(\.dismiss) private var dismiss

Expand Down
Loading