Skip to content

Commit

Permalink
[Android] Delegated Verifier (#18)
Browse files Browse the repository at this point in the history
This enables the app to display a QR Code with the delegated OID4VP URL to be presented to the holder and display the presented credential
  • Loading branch information
Juliano1612 authored Nov 1, 2024
1 parent 768ea3c commit 4fd6536
Show file tree
Hide file tree
Showing 43 changed files with 959 additions and 119 deletions.
2 changes: 1 addition & 1 deletion MobileSdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ android {
}

dependencies {
api("com.spruceid.mobile.sdk.rs:mobilesdkrs:0.2.2")
api("com.spruceid.mobile.sdk.rs:mobilesdkrs:0.3.0")
//noinspection GradleCompatible
implementation("com.android.support:appcompat-v7:28.0.0")
/* Begin UI dependencies */
Expand Down
20 changes: 9 additions & 11 deletions example/src/main/java/com/spruceid/mobilesdkexample/HomeView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.spruceid.mobilesdkexample.ui.theme.Bg
import com.spruceid.mobilesdkexample.ui.theme.Inter
import com.spruceid.mobilesdkexample.ui.theme.MobileSdkTheme
import com.spruceid.mobilesdkexample.verifier.VerifierHomeView
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModel
import com.spruceid.mobilesdkexample.wallet.WalletHomeView

enum class HomeTabs {
Expand All @@ -38,7 +39,10 @@ enum class HomeTabs {

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun HomeView(navController: NavController) {
fun HomeView(
navController: NavController,
verificationMethodsViewModel: VerificationMethodsViewModel
) {
var tab by remember {
mutableStateOf(HomeTabs.WALLET)
}
Expand Down Expand Up @@ -90,17 +94,11 @@ fun HomeView(navController: NavController) {
if (tab == HomeTabs.WALLET) {
WalletHomeView(navController)
} else {
VerifierHomeView(navController = navController)
VerifierHomeView(
navController = navController,
verificationMethodsViewModel = verificationMethodsViewModel
)
}
}
}
}

@Preview(showBackground = true)
@Composable
fun HomeViewPreview() {
val navController: NavHostController = rememberNavController()
MobileSdkTheme {
HomeView(navController)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
Expand All @@ -31,7 +32,13 @@ fun LoadingView(
cancelButtonLabel: String = "Cancel",
onCancel: (() -> Unit)? = null
) {
Box(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 40.dp)
.padding(horizontal = 30.dp)
.navigationBarsPadding()
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.spruceid.mobilesdkexample.db.AppDatabase
import com.spruceid.mobilesdkexample.db.VerificationMethodsRepository
import com.spruceid.mobilesdkexample.navigation.Screen
import com.spruceid.mobilesdkexample.navigation.SetupNavGraph
import com.spruceid.mobilesdkexample.ui.theme.Bg
import com.spruceid.mobilesdkexample.ui.theme.MobileSdkTheme
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModel
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModelFactory

class MainActivity : ComponentActivity() {
private lateinit var navController: NavHostController
Expand Down Expand Up @@ -61,7 +65,11 @@ class MainActivity : ComponentActivity() {
// RawCredentialsViewModelFactory((application as MainApplication).rawCredentialsRepository)
// }

SetupNavGraph(navController)
val verificationMethodsViewModel: VerificationMethodsViewModel by viewModels {
VerificationMethodsViewModelFactory((application as MainApplication).verificationMethodsRepository)
}

SetupNavGraph(navController, verificationMethodsViewModel = verificationMethodsViewModel)
}
}
}
Expand All @@ -72,4 +80,6 @@ class MainApplication : Application() {
val db by lazy { AppDatabase.getDatabase(applicationContext) }
// TODO: Completely remove RawCredentialsViewModel after confirming if credentials will be migrated
// val rawCredentialsRepository by lazy { RawCredentialsRepository(db.rawCredentialsDao()) }

val verificationMethodsRepository by lazy { VerificationMethodsRepository(db.verificationMethodsDao()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter

class AchievementCredentialItem : ICredentialView {
private var credentialPack: CredentialPack
override var credentialPack: CredentialPack
private val onDelete: (() -> Unit)?

constructor(credentialPack: CredentialPack, onDelete: (() -> Unit)? = null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import kotlinx.coroutines.launch
import org.json.JSONObject

class GenericCredentialItem : ICredentialView {
private var credentialPack: CredentialPack
override var credentialPack: CredentialPack
private val onDelete: (() -> Unit)?

constructor(credentialPack: CredentialPack, onDelete: (() -> Unit)? = null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.spruceid.mobilesdkexample.credentials

import androidx.compose.runtime.Composable
import com.spruceid.mobile.sdk.CredentialPack

interface ICredentialView {
var credentialPack: CredentialPack

@Composable
fun credentialListItem(withOptions: Boolean): Unit

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@ import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase

@Database(
entities = [
VerificationActivityLogs::class,
RawCredentials::class,
VerificationMethods::class
],
version = 2
version = 3
)
@TypeConverters(*[DateConverter::class])
abstract class AppDatabase : RoomDatabase() {
abstract fun verificationActivityLogsDao(): VerificationActivityLogsDao
abstract fun rawCredentialsDao(): RawCredentialsDao
abstract fun verificationMethodsDao(): VerificationMethodsDao

companion object {
@Volatile
Expand All @@ -30,6 +34,7 @@ abstract class AppDatabase : RoomDatabase() {
AppDatabase::class.java,
"referenceAppDb",
)
.addMigrations(MIGRATION_2_3)
.allowMainThreadQueries()
.build()
dbInstance = instance
Expand All @@ -38,3 +43,16 @@ abstract class AppDatabase : RoomDatabase() {
}
}
}

val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE `verification_methods` (" +
"`id` INTEGER NOT NULL, " +
"`type` TEXT NOT NULL, " +
"`name` TEXT NOT NULL, " +
"`description` TEXT NOT NULL, " +
"`verifierName` TEXT NOT NULL, " +
"`url` TEXT NOT NULL, " +
"PRIMARY KEY(`id`))")
}
}
18 changes: 18 additions & 0 deletions example/src/main/java/com/spruceid/mobilesdkexample/db/Daos.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,21 @@ interface RawCredentialsDao {
@Query("DELETE FROM raw_credentials WHERE id = :id")
fun deleteRawCredential(id: Long): Int
}

@Dao
interface VerificationMethodsDao {
@Insert
suspend fun insertVerificationMethod(verificationMethod: VerificationMethods)

@Query("SELECT * FROM verification_methods")
fun getAllVerificationMethods(): List<VerificationMethods>

@Query("SELECT * FROM verification_methods WHERE id = :id")
fun getVerificationMethod(id: Long): VerificationMethods

@Query("DELETE FROM verification_methods")
fun deleteAllVerificationMethods(): Int

@Query("DELETE FROM verification_methods WHERE id = :id")
fun deleteVerificationMethod(id: Long): Int
}
10 changes: 10 additions & 0 deletions example/src/main/java/com/spruceid/mobilesdkexample/db/Entities.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,14 @@ data class VerificationActivityLogs(
data class RawCredentials(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val rawCredential: String,
)

@Entity(tableName = "verification_methods")
data class VerificationMethods(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val type: String,
val name: String,
val description: String,
val verifierName: String,
val url: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,33 @@ class RawCredentialsRepository(private val rawCredentialsDao: RawCredentialsDao)
suspend fun deleteRawCredential(id: Long): Int {
return rawCredentialsDao.deleteRawCredential(id = id)
}
}

class VerificationMethodsRepository(private val verificationMethodsDao: VerificationMethodsDao) {
val verificationMethods: List<VerificationMethods> = verificationMethodsDao.getAllVerificationMethods()

@WorkerThread
suspend fun insertVerificationMethod(verificationMethod: VerificationMethods) {
verificationMethodsDao.insertVerificationMethod(verificationMethod)
}

@WorkerThread
suspend fun getVerificationMethods(): List<VerificationMethods> {
return verificationMethodsDao.getAllVerificationMethods()
}

@WorkerThread
suspend fun getVerificationMethod(id: Long): VerificationMethods {
return verificationMethodsDao.getVerificationMethod(id)
}

@WorkerThread
suspend fun deleteAllVerificationMethods(): Int {
return verificationMethodsDao.deleteAllVerificationMethods()
}

@WorkerThread
suspend fun deleteVerificationMethod(id: Long): Int {
return verificationMethodsDao.deleteVerificationMethod(id = id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ const val HOME_SCREEN_PATH = "home"
const val VERIFY_DL_PATH = "verify_dl"
const val VERIFY_EA_PATH = "verify_ea"
const val VERIFY_VC_PATH = "verify_vc"
const val VERIFY_DELEGATED_OID4VP_PATH = "verify_delegated_oid4vp/{id}"
const val VERIFIER_SETTINGS_HOME_PATH = "verifier_settings_home"
const val ADD_VERIFICATION_METHOD_PATH = "add_verification_method"
const val WALLET_SETTINGS_HOME_PATH = "wallet_settings_home"
const val ADD_TO_WALLET_PATH = "add_to_wallet/{rawCredential}"
const val SCAN_QR_PATH = "scan_qr"
Expand All @@ -16,7 +18,9 @@ sealed class Screen(val route: String) {
object VerifyDLScreen : Screen(VERIFY_DL_PATH)
object VerifyEAScreen : Screen(VERIFY_EA_PATH)
object VerifyVCScreen : Screen(VERIFY_VC_PATH)
object VerifyDelegatedOid4vpScreen : Screen(VERIFY_DELEGATED_OID4VP_PATH)
object VerifierSettingsHomeScreen : Screen(VERIFIER_SETTINGS_HOME_PATH)
object AddVerificationMethodScreen : Screen(ADD_VERIFICATION_METHOD_PATH)
object WalletSettingsHomeScreen : Screen(WALLET_SETTINGS_HOME_PATH)
object AddToWalletScreen : Screen(ADD_TO_WALLET_PATH)
object ScanQRScreen : Screen(SCAN_QR_PATH)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,32 @@ import androidx.navigation.compose.composable
import androidx.navigation.navDeepLink
import com.spruceid.mobilesdkexample.HomeView
import com.spruceid.mobilesdkexample.credentials.AddToWalletView
import com.spruceid.mobilesdkexample.verifier.AddVerificationMethodView
import com.spruceid.mobilesdkexample.verifier.VerifyDLView
import com.spruceid.mobilesdkexample.verifier.VerifyDelegatedOid4vpView
import com.spruceid.mobilesdkexample.verifier.VerifyEAView
import com.spruceid.mobilesdkexample.verifier.VerifyVCView
import com.spruceid.mobilesdkexample.verifiersettings.VerifierSettingsHomeView
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModel
import com.spruceid.mobilesdkexample.wallet.DispatchQRView
import com.spruceid.mobilesdkexample.wallet.HandleOID4VPView
import com.spruceid.mobilesdkexample.wallet.OID4VCIView
import com.spruceid.mobilesdkexample.walletsettings.WalletSettingsHomeView

@Composable
fun SetupNavGraph(navController: NavHostController) {
fun SetupNavGraph(
navController: NavHostController,
verificationMethodsViewModel: VerificationMethodsViewModel
) {
NavHost(navController = navController, startDestination = Screen.HomeScreen.route) {
composable(
route = Screen.HomeScreen.route,
) { HomeView(navController) }
) {
HomeView(
navController,
verificationMethodsViewModel = verificationMethodsViewModel
)
}
composable(
route = Screen.VerifyDLScreen.route,
) { VerifyDLView(navController) }
Expand All @@ -31,9 +42,32 @@ fun SetupNavGraph(navController: NavHostController) {
composable(
route = Screen.VerifyVCScreen.route,
) { VerifyVCView(navController) }
composable(
route = Screen.VerifyDelegatedOid4vpScreen.route,
) { backStackEntry ->
val id = backStackEntry.arguments?.getString("id")!!
VerifyDelegatedOid4vpView(
navController,
verificationId = id,
verificationMethodsViewModel
)
}
composable(
route = Screen.VerifierSettingsHomeScreen.route,
) { VerifierSettingsHomeView(navController) }
) {
VerifierSettingsHomeView(
navController,
verificationMethodsViewModel = verificationMethodsViewModel
)
}
composable(
route = Screen.AddVerificationMethodScreen.route,
) {
AddVerificationMethodView(
navController,
verificationMethodsViewModel = verificationMethodsViewModel
)
}
composable(
route = Screen.WalletSettingsHomeScreen.route,
) { WalletSettingsHomeView(navController) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,10 @@ val CredentialBorder = Color(0xFFE6E1D6)
val CodeBorder = Color(0xff949494)
val CTAButtonGreen = Color(0xFF087455)
val CTAButtonBlue = Color(0xFF488CF4)
val GreenValid = Color(0xFF059669)
val SecondaryButtonRed = Color(0xFFE11D48)
val TextBody = Color(0xFF57534E)
val TextHeader = Color(0xFF0C0A09)
val TextOnPrimary = Color(0xFFA8A29E)
val VerifierRequestBadgeBinaryBorder = Color(0xFF0C4A6E)
val VerifierRequestBadgeBinaryFill = Color(0xFFF0F9FF)
val VerifierRequestBadgeBinaryText = Color(0xFF0C4A6E)
val VerifierRequestBadgeFieldBorder = Color(0xFFE7E5E4)
val VerifierRequestBadgeFieldFill = Color(0xFFF7F5F0)
val VerifierRequestBadgeFieldText = Color(0xFF0C0A09)
val VerifiedGreenValid = Color(0xFF047857)
val VerifiedRedInvalid = Color(0xFFBE123C)
val VerifierCloseButton = Color(0xFF44403C)
Expand All @@ -30,11 +23,14 @@ val ColorBase800 = Color(0xFF75675C)
val ColorBlue600 = Color(0xFF2F6AE1)
val ColorStone50 = Color(0xFFFAFAF9)
val ColorStone300 = Color(0xFFD6D3D1)
val ColorStone400 = Color(0xFFA8A29E)
val ColorStone500 = Color(0xFF78716C)
val ColorStone600 = Color(0xFF57534E)
val ColorStone950 = Color(0xFF0C0A09)
val ColorEmerald900 = Color(0xFF084C3A)
val ColorRose600 = Color(0xFFE11D48)
val ColorPurple600 = Color(0xFF926CE6)
val ColorTerracotta600 = Color(0xFFE1674C)
val TextBase = Color(0xFFFBF9F6)
val BgSurfacePureBlue = Color(0xFF2F6AE1)
val BorderSecondary = Color(0xFFD6D3D1)
Loading

0 comments on commit 4fd6536

Please sign in to comment.