diff --git a/example/src/main/AndroidManifest.xml b/example/src/main/AndroidManifest.xml
index a6ceb82..bb2684a 100644
--- a/example/src/main/AndroidManifest.xml
+++ b/example/src/main/AndroidManifest.xml
@@ -16,7 +16,7 @@
-
+
+
@@ -51,14 +52,25 @@
+
-
+
+
+
\ No newline at end of file
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/MainActivity.kt b/example/src/main/java/com/spruceid/mobilesdkexample/MainActivity.kt
index ef33d9d..fe551f6 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/MainActivity.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/MainActivity.kt
@@ -13,6 +13,7 @@ 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.VerificationActivityLogsRepository
import com.spruceid.mobilesdkexample.db.VerificationMethodsRepository
import com.spruceid.mobilesdkexample.navigation.Screen
import com.spruceid.mobilesdkexample.navigation.SetupNavGraph
@@ -20,6 +21,9 @@ import com.spruceid.mobilesdkexample.ui.theme.ColorBase1
import com.spruceid.mobilesdkexample.ui.theme.MobileSdkTheme
import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModel
import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModelFactory
+import com.spruceid.mobilesdkexample.viewmodels.HelpersViewModel
+import com.spruceid.mobilesdkexample.viewmodels.VerificationActivityLogsViewModel
+import com.spruceid.mobilesdkexample.viewmodels.VerificationActivityLogsViewModelFactory
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModel
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModelFactory
@@ -71,14 +75,22 @@ class MainActivity : ComponentActivity() {
VerificationMethodsViewModelFactory((application as MainApplication).verificationMethodsRepository)
}
+ val verificationActivityLogsViewModel: VerificationActivityLogsViewModel by viewModels {
+ VerificationActivityLogsViewModelFactory((application as MainApplication).verificationActivityLogsRepository)
+ }
+
val credentialPacksViewModel: CredentialPacksViewModel by viewModels {
CredentialPacksViewModelFactory(application as MainApplication)
}
+ val helpersViewModel: HelpersViewModel by viewModels()
+
SetupNavGraph(
navController,
verificationMethodsViewModel = verificationMethodsViewModel,
- credentialPacksViewModel = credentialPacksViewModel
+ verificationActivityLogsViewModel = verificationActivityLogsViewModel,
+ credentialPacksViewModel = credentialPacksViewModel,
+ helpersViewModel = helpersViewModel
)
}
}
@@ -92,4 +104,5 @@ class MainApplication : Application() {
// val rawCredentialsRepository by lazy { RawCredentialsRepository(db.rawCredentialsDao()) }
val verificationMethodsRepository by lazy { VerificationMethodsRepository(db.verificationMethodsDao()) }
+ val verificationActivityLogsRepository by lazy { VerificationActivityLogsRepository(db.verificationActivityLogsDao()) }
}
\ No newline at end of file
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/db/AppDatabase.kt b/example/src/main/java/com/spruceid/mobilesdkexample/db/AppDatabase.kt
index c798847..708d79e 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/db/AppDatabase.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/db/AppDatabase.kt
@@ -14,7 +14,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
RawCredentials::class,
VerificationMethods::class
],
- version = 3
+ version = 4
)
@TypeConverters(*[DateConverter::class])
abstract class AppDatabase : RoomDatabase() {
@@ -35,6 +35,7 @@ abstract class AppDatabase : RoomDatabase() {
"referenceAppDb",
)
.addMigrations(MIGRATION_2_3)
+ .addMigrations(MIGRATION_3_4)
.allowMainThreadQueries()
.build()
dbInstance = instance
@@ -46,13 +47,30 @@ 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`))")
+ 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`))"
+ )
+ }
+}
+
+val MIGRATION_3_4 = object : Migration(3, 4) {
+ override fun migrate(database: SupportSQLiteDatabase) {
+ database.execSQL("DROP TABLE verification_activity_logs")
+ database.execSQL(
+ "CREATE TABLE `verification_activity_logs` (" +
+ "`id` INTEGER NOT NULL, " +
+ "`credentialTitle` TEXT NOT NULL, " +
+ "`issuer` TEXT NOT NULL, " +
+ "`verificationDateTime` INTEGER NOT NULL, " +
+ "`additionalInformation` TEXT NOT NULL, " +
+ "PRIMARY KEY(`id`))"
+ )
}
}
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/db/Daos.kt b/example/src/main/java/com/spruceid/mobilesdkexample/db/Daos.kt
index ed0f376..f8c28eb 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/db/Daos.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/db/Daos.kt
@@ -9,8 +9,18 @@ interface VerificationActivityLogsDao {
@Insert
suspend fun insertVerificationActivity(verificationActivityLogs: VerificationActivityLogs)
- @Query("SELECT * FROM verification_activity_logs")
+ @Query("SELECT * FROM verification_activity_logs ORDER BY verificationDateTime DESC")
fun getAllVerificationActivityLogs(): List
+
+ @Query(
+ "SELECT * FROM verification_activity_logs " +
+ "WHERE verificationDateTime > :fromDate " +
+ "ORDER BY verificationDateTime DESC"
+ )
+ fun getFilteredVerificationActivityLogs(fromDate: Long): List
+
+ @Query("SELECT DISTINCT credentialTitle FROM verification_activity_logs")
+ fun getDistinctCredentialTitles(): List
}
@Dao
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/db/Entities.kt b/example/src/main/java/com/spruceid/mobilesdkexample/db/Entities.kt
index f6f7b5c..5c14eaf 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/db/Entities.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/db/Entities.kt
@@ -7,11 +7,10 @@ import java.sql.Date
@Entity(tableName = "verification_activity_logs")
data class VerificationActivityLogs(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
- val name: String,
val credentialTitle: String,
- val date: Date,
- val expirationDate: Date,
- val status: String,
+ val issuer: String,
+ val verificationDateTime: Date,
+ val additionalInformation: String,
)
@Entity(tableName = "raw_credentials")
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/db/Repositories.kt b/example/src/main/java/com/spruceid/mobilesdkexample/db/Repositories.kt
index 4d5161b..4dbc671 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/db/Repositories.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/db/Repositories.kt
@@ -1,6 +1,7 @@
package com.spruceid.mobilesdkexample.db
import androidx.annotation.WorkerThread
+import java.sql.Date
class VerificationActivityLogsRepository(private val verificationActivityLogsDao: VerificationActivityLogsDao) {
val verificationActivityLogs: List =
@@ -15,6 +16,19 @@ class VerificationActivityLogsRepository(private val verificationActivityLogsDao
suspend fun getVerificationActivityLogs(): List {
return verificationActivityLogsDao.getAllVerificationActivityLogs()
}
+
+ // TODO: Add fromDate and credentialType filter params
+ @WorkerThread
+ fun getFilteredVerificationActivityLogs(): List {
+ return verificationActivityLogsDao.getFilteredVerificationActivityLogs(
+ fromDate = Date(Long.MIN_VALUE).time
+ )
+ }
+
+ @WorkerThread
+ fun getDistinctCredentialTitles(): List {
+ return verificationActivityLogsDao.getDistinctCredentialTitles()
+ }
}
class RawCredentialsRepository(private val rawCredentialsDao: RawCredentialsDao) {
@@ -42,7 +56,8 @@ class RawCredentialsRepository(private val rawCredentialsDao: RawCredentialsDao)
}
class VerificationMethodsRepository(private val verificationMethodsDao: VerificationMethodsDao) {
- val verificationMethods: List = verificationMethodsDao.getAllVerificationMethods()
+ val verificationMethods: List =
+ verificationMethodsDao.getAllVerificationMethods()
@WorkerThread
suspend fun insertVerificationMethod(verificationMethod: VerificationMethods) {
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/navigation/Screen.kt b/example/src/main/java/com/spruceid/mobilesdkexample/navigation/Screen.kt
index 19eb22a..10268d9 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/navigation/Screen.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/navigation/Screen.kt
@@ -7,6 +7,7 @@ const val VERIFY_VC_PATH = "verify_vc"
const val VERIFY_MDOC_PATH = "verify_mdoc"
const val VERIFY_DELEGATED_OID4VP_PATH = "verify_delegated_oid4vp/{id}"
const val VERIFIER_SETTINGS_HOME_PATH = "verifier_settings_home"
+const val VERIFIER_SETTINGS_ACTIVITY_LOG = "verifier_settings_activity_log"
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}"
@@ -22,6 +23,7 @@ sealed class Screen(val route: String) {
object VerifyMDocScreen : Screen(VERIFY_MDOC_PATH)
object VerifyDelegatedOid4vpScreen : Screen(VERIFY_DELEGATED_OID4VP_PATH)
object VerifierSettingsHomeScreen : Screen(VERIFIER_SETTINGS_HOME_PATH)
+ object VerifierSettingsActivityLogScreen : Screen(VERIFIER_SETTINGS_ACTIVITY_LOG)
object AddVerificationMethodScreen : Screen(ADD_VERIFICATION_METHOD_PATH)
object WalletSettingsHomeScreen : Screen(WALLET_SETTINGS_HOME_PATH)
object AddToWalletScreen : Screen(ADD_TO_WALLET_PATH)
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/navigation/SetupNavGraph.kt b/example/src/main/java/com/spruceid/mobilesdkexample/navigation/SetupNavGraph.kt
index aabc737..96103fd 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/navigation/SetupNavGraph.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/navigation/SetupNavGraph.kt
@@ -15,8 +15,11 @@ import com.spruceid.mobilesdkexample.verifier.VerifyDelegatedOid4vpView
import com.spruceid.mobilesdkexample.verifier.VerifyEAView
import com.spruceid.mobilesdkexample.verifier.VerifyMDocView
import com.spruceid.mobilesdkexample.verifier.VerifyVCView
+import com.spruceid.mobilesdkexample.verifiersettings.VerifierSettingsActivityLogScreen
import com.spruceid.mobilesdkexample.verifiersettings.VerifierSettingsHomeView
import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModel
+import com.spruceid.mobilesdkexample.viewmodels.HelpersViewModel
+import com.spruceid.mobilesdkexample.viewmodels.VerificationActivityLogsViewModel
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModel
import com.spruceid.mobilesdkexample.wallet.DispatchQRView
import com.spruceid.mobilesdkexample.wallet.HandleOID4VCIView
@@ -27,7 +30,9 @@ import com.spruceid.mobilesdkexample.walletsettings.WalletSettingsHomeView
fun SetupNavGraph(
navController: NavHostController,
verificationMethodsViewModel: VerificationMethodsViewModel,
- credentialPacksViewModel: CredentialPacksViewModel
+ verificationActivityLogsViewModel: VerificationActivityLogsViewModel,
+ credentialPacksViewModel: CredentialPacksViewModel,
+ helpersViewModel: HelpersViewModel
) {
NavHost(navController = navController, startDestination = Screen.HomeScreen.route) {
composable(
@@ -48,16 +53,33 @@ fun SetupNavGraph(
}
composable(
route = Screen.VerifyDLScreen.route,
- ) { VerifyDLView(navController) }
+ ) {
+ VerifyDLView(
+ navController,
+ verificationActivityLogsViewModel = verificationActivityLogsViewModel
+ )
+ }
composable(
route = Screen.VerifyEAScreen.route,
- ) { VerifyEAView(navController) }
+ ) {
+ VerifyEAView(
+ navController,
+ verificationActivityLogsViewModel = verificationActivityLogsViewModel
+ )
+ }
composable(
route = Screen.VerifyVCScreen.route,
- ) { VerifyVCView(navController) }
+ ) {
+ VerifyVCView(navController)
+ }
composable(
route = Screen.VerifyMDocScreen.route,
- ) { VerifyMDocView(navController) }
+ ) {
+ VerifyMDocView(
+ navController,
+ verificationActivityLogsViewModel = verificationActivityLogsViewModel
+ )
+ }
composable(
route = Screen.VerifyDelegatedOid4vpScreen.route,
) { backStackEntry ->
@@ -65,7 +87,8 @@ fun SetupNavGraph(
VerifyDelegatedOid4vpView(
navController,
verificationId = id,
- verificationMethodsViewModel
+ verificationMethodsViewModel = verificationMethodsViewModel,
+ verificationActivityLogsViewModel = verificationActivityLogsViewModel
)
}
composable(
@@ -76,6 +99,15 @@ fun SetupNavGraph(
verificationMethodsViewModel = verificationMethodsViewModel
)
}
+ composable(
+ route = Screen.VerifierSettingsActivityLogScreen.route,
+ ) {
+ VerifierSettingsActivityLogScreen(
+ navController,
+ verificationActivityLogsViewModel = verificationActivityLogsViewModel,
+ helpersViewModel = helpersViewModel
+ )
+ }
composable(
route = Screen.AddVerificationMethodScreen.route,
) {
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/utils/DropdownInput.kt b/example/src/main/java/com/spruceid/mobilesdkexample/utils/DropdownInput.kt
new file mode 100644
index 0000000..2a18a9b
--- /dev/null
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/utils/DropdownInput.kt
@@ -0,0 +1,133 @@
+package com.spruceid.mobilesdkexample.utils
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MenuDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import com.spruceid.mobilesdkexample.R
+import com.spruceid.mobilesdkexample.ui.theme.ColorBase1
+import com.spruceid.mobilesdkexample.ui.theme.ColorBase300
+import com.spruceid.mobilesdkexample.ui.theme.ColorStone500
+import com.spruceid.mobilesdkexample.ui.theme.ColorStone600
+import com.spruceid.mobilesdkexample.ui.theme.ColorStone950
+import com.spruceid.mobilesdkexample.ui.theme.MobileSdkTheme
+
+@Composable
+fun DropdownInput(
+ options: List,
+ onSelect: (String) -> Unit
+) {
+ var selected by remember { mutableStateOf("") }
+ var expanded by remember { mutableStateOf(false) }
+ var buttonSize by remember { mutableStateOf(IntSize.Zero) }
+
+ Box(
+ modifier = Modifier.fillMaxWidth(),
+ contentAlignment = Alignment.TopStart
+ ) {
+ // Input field
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .onGloballyPositioned { coordinates ->
+ buttonSize = coordinates.size
+ }
+ .border(
+ width = 1.dp,
+ color = ColorBase300,
+ shape = RoundedCornerShape(8.dp)
+ )
+ .padding(
+ start = 16.dp,
+ end = 10.dp,
+ top = 10.dp,
+ bottom = 10.dp
+ )
+ .clickable {
+ expanded = !expanded
+ }
+ ) {
+ if (selected.isEmpty()) {
+ Text(
+ text = "Select...",
+ color = ColorStone500
+ )
+ } else {
+ Text(
+ text = selected,
+ color = ColorStone950
+ )
+ }
+ Spacer(Modifier.weight(1f))
+ Icon(
+ painter = painterResource(id = R.drawable.chevron),
+ contentDescription = "Collapse menu button",
+ tint = ColorStone600,
+ modifier = Modifier
+ .rotate(90f)
+ .height(12.dp)
+ )
+ }
+
+ // Dropdown menu
+ DropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { expanded = false },
+ modifier = Modifier
+ .width(with(LocalDensity.current) {
+ buttonSize.width.toDp()
+ })
+ .background(ColorBase1)
+ ) {
+ options.forEach { option ->
+ DropdownMenuItem(
+ text = { Text(option) },
+ onClick = {
+ selected = option
+ onSelect(option)
+ expanded = false
+ },
+ colors = MenuDefaults.itemColors(textColor = ColorStone600),
+ )
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun DropdownInputPreview() {
+ MobileSdkTheme {
+ DropdownInput(
+ options = listOf("Option 1", "Option 2", "Option 3"),
+ onSelect = { }
+ )
+ }
+}
\ No newline at end of file
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/utils/Utils.kt b/example/src/main/java/com/spruceid/mobilesdkexample/utils/Utils.kt
index b4982dc..e79cbe3 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/utils/Utils.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/utils/Utils.kt
@@ -16,10 +16,11 @@ import com.spruceid.mobile.sdk.rs.JwtVc
import com.spruceid.mobile.sdk.rs.Mdoc
import com.spruceid.mobile.sdk.rs.Uuid
import com.spruceid.mobile.sdk.rs.Vcdm2SdJwt
-import com.spruceid.mobilesdkexample.credentials.AchievementCredentialItem
import com.spruceid.mobilesdkexample.credentials.GenericCredentialItem
import com.spruceid.mobilesdkexample.credentials.ICredentialView
import org.json.JSONObject
+import java.sql.Date
+import java.text.SimpleDateFormat
const val keyPEM =
"-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEAqKZdZQgPVtjlEB\nfz2ItHG8oXIONenOxRePtqOQ42yhRANCAATA43gI2Ib8+qKK4YEOfNCRiNOhyHaC\nLgAvKdhHS+y6wpG3oJ2xudXagzKKbcfvUda4x0j8zR1/oD56mpm85GbO\n-----END PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIICgDCCAiWgAwIBAgIUTp04dh8m8Vxa/hX5LmTvjSWrAS8wCgYIKoZIzj0EAwIw\ngZQxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3\nIFlvcmsxEjAQBgNVBAoMCVNwcnVjZSBJRDESMBAGA1UECwwJU3BydWNlIElkMRIw\nEAYDVQQDDAlTcHJ1Y2UgSUQxIzAhBgkqhkiG9w0BCQEWFGNvbnRhY3RAc3BydWNl\naWQuY29tMB4XDTI0MDIxMjE2NTEwMVoXDTI1MDIxMTE2NTEwMVowgZQxCzAJBgNV\nBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3IFlvcmsxEjAQ\nBgNVBAoMCVNwcnVjZSBJRDESMBAGA1UECwwJU3BydWNlIElkMRIwEAYDVQQDDAlT\ncHJ1Y2UgSUQxIzAhBgkqhkiG9w0BCQEWFGNvbnRhY3RAc3BydWNlaWQuY29tMFkw\nEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwON4CNiG/PqiiuGBDnzQkYjToch2gi4A\nLynYR0vsusKRt6CdsbnV2oMyim3H71HWuMdI/M0df6A+epqZvORmzqNTMFEwHQYD\nVR0OBBYEFPbjKnGAa0aSXw0oe4KfHdN5M1ssMB8GA1UdIwQYMBaAFPbjKnGAa0aS\nXw0oe4KfHdN5M1ssMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIh\nAO2msc7LSdakGcw3q7DxEySqzepr+LeWWNvPbQypQxd8AiEAj7dVI3V00gq3K3OU\nCbkeKnYiGtVCZnXnR/MW91mPeGE=\n-----END CERTIFICATE-----"
@@ -29,8 +30,15 @@ const val keyBase64 =
val trustedDids = MutableList(1) { "did:web:companion.ler-sandbox.spruceid.xyz:oid4vp:client" }
-val delegatedVerifierBaseUrl = "https://credible.ler-sandbox.spruceid.xyz/oid4vp"
-val delegatedVerifierUrl = "/api2/verifier/1/delegate"
+fun getCurrentSqlDate(): Date {
+ val currentTimeMillis = System.currentTimeMillis()
+ return Date(currentTimeMillis)
+}
+
+fun formatSqlDateTime(sqlDate: Date): String {
+ val formatter = SimpleDateFormat("MMM dd, yyyy 'at' h:mm a")
+ return formatter.format(sqlDate)
+}
fun String.splitCamelCase() = replace(
String.format(
@@ -44,6 +52,7 @@ fun String.splitCamelCase() = replace(
fun String.removeUnderscores() = replace("_", "")
+fun String.removeCommas() = replace(",", "")
fun String.isDate(): Boolean {
@@ -112,14 +121,14 @@ fun keyPathFinder(json: Any, path: MutableList): Any {
}
fun credentialDisplaySelector(rawCredential: String, onDelete: (() -> Unit)?): ICredentialView {
-/* This is temporarily commented on until we define the specific AchievementCredentialItem design */
+ /* This is temporarily commented on until we define the specific AchievementCredentialItem design */
// try {
// Test if it is SdJwt
// val credentialPack = CredentialPack()
// credentialPack.addSdJwt(Vcdm2SdJwt.newFromCompactSdJwt(rawCredential))
// return AchievementCredentialItem(credentialPack, onDelete)
// } catch (_: Exception) {
- return GenericCredentialItem(rawCredential, onDelete)
+ return GenericCredentialItem(rawCredential, onDelete)
// }
}
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierCredentialSuccessView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierCredentialSuccessView.kt
index 785cb03..345ce99 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierCredentialSuccessView.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierCredentialSuccessView.kt
@@ -33,6 +33,7 @@ import com.spruceid.mobilesdkexample.utils.splitCamelCase
fun VerifierCredentialSuccessView(
rawCredential: String,
onClose: () -> Unit,
+ logVerification: (String, String) -> Unit
) {
val credentialItem = credentialDisplaySelector(rawCredential, null)
var title by remember { mutableStateOf(null) }
@@ -64,6 +65,8 @@ fun VerifierCredentialSuccessView(
issuer = claims?.getJSONObject("issuer")?.getString("name").toString()
} catch (_: Exception) {
}
+
+ logVerification(title ?: "", issuer ?: "")
}
Column(
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierMDocResultView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierMDocResultView.kt
index 17faa35..1247dcf 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierMDocResultView.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierMDocResultView.kt
@@ -87,6 +87,7 @@ fun VerifierMDocResultView(
val eyeColor = getDiscriminant(result["org.iso.18013.5.1"]?.get("eye_colour")!!)
val hairColor = getDiscriminant(result["org.iso.18013.5.1"]?.get("hair_colour")!!)
val portrait = result["org.iso.18013.5.1"]?.get("portrait")!! as MDocItem.Array
+ val issuingAuthority = getDiscriminant(result["org.iso.18013.5.1"]?.get("issuing_authority")!!)
val portraitBytes = mDocArrayToByteArray(portrait)
Box(
@@ -115,7 +116,7 @@ fun VerifierMDocResultView(
style = MaterialTheme.typography.headerH2
)
Text(
- text = "Issuer",
+ text = issuingAuthority,
color = ColorStone600,
style = MaterialTheme.typography.bodyMdDefault
)
@@ -328,7 +329,8 @@ fun MDocVerifyPreview() {
"eye_colour" to MDocItem.Text("green"),
"hair_colour" to MDocItem.Text("unknown"),
"resident_address" to MDocItem.Text("2415 1ST AVE, SACRAMENTO 95818"),
- "document_number" to MDocItem.Text("I8882610")
+ "document_number" to MDocItem.Text("I8882610"),
+ "issuing_authority" to MDocItem.Text("SpruceID")
),
"org.iso.18013.5.1.aamva" to mapOf(
"DHS_compliance" to MDocItem.Text("F"),
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyDLView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyDLView.kt
index 0646abd..8ceb116 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyDLView.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyDLView.kt
@@ -9,9 +9,13 @@ import androidx.compose.runtime.setValue
import androidx.navigation.NavController
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.spruceid.mobile.sdk.rs.verifyPdf417Barcode
+import com.spruceid.mobilesdkexample.LoadingView
import com.spruceid.mobilesdkexample.ScanningComponent
import com.spruceid.mobilesdkexample.ScanningType
+import com.spruceid.mobilesdkexample.db.VerificationActivityLogs
import com.spruceid.mobilesdkexample.navigation.Screen
+import com.spruceid.mobilesdkexample.utils.getCurrentSqlDate
+import com.spruceid.mobilesdkexample.viewmodels.VerificationActivityLogsViewModel
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@@ -19,19 +23,32 @@ import kotlinx.coroutines.launch
@Composable
fun VerifyDLView(
navController: NavController,
+ verificationActivityLogsViewModel: VerificationActivityLogsViewModel,
) {
- var success by remember {
- mutableStateOf(null)
- }
+ var success by remember { mutableStateOf(null) }
+ var verifying by remember { mutableStateOf(false) }
+
fun onRead(content: String) {
- GlobalScope.launch {
- try {
- verifyPdf417Barcode(payload = content)
- success = true
- } catch (e: Exception) {
- success = false
- e.printStackTrace()
+ if (!verifying) {
+ verifying = true
+ GlobalScope.launch {
+ try {
+ verifyPdf417Barcode(payload = content)
+ success = true
+ verificationActivityLogsViewModel.saveVerificationActivityLog(
+ VerificationActivityLogs(
+ credentialTitle = "Driver's License",
+ issuer = "Utopia Department of Motor Vehicles",
+ verificationDateTime = getCurrentSqlDate(),
+ additionalInformation = ""
+ )
+ )
+ } catch (e: Exception) {
+ success = false
+ e.printStackTrace()
+ }
+ verifying = false
}
}
}
@@ -44,7 +61,9 @@ fun VerifyDLView(
}
}
- if (success == null) {
+ if (verifying) {
+ LoadingView(loadingText = "Verifying...")
+ } else if (success == null) {
ScanningComponent(
subtitle = "Scan the\nback of your driver's license",
scanningType = ScanningType.PDF417,
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyDelegatedOid4vpView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyDelegatedOid4vpView.kt
index 893aac9..b5881d2 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyDelegatedOid4vpView.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyDelegatedOid4vpView.kt
@@ -29,12 +29,15 @@ import com.spruceid.mobile.sdk.rs.DelegatedVerifier
import com.spruceid.mobile.sdk.rs.DelegatedVerifierStatus
import com.spruceid.mobilesdkexample.ErrorView
import com.spruceid.mobilesdkexample.LoadingView
+import com.spruceid.mobilesdkexample.db.VerificationActivityLogs
import com.spruceid.mobilesdkexample.db.VerificationMethods
import com.spruceid.mobilesdkexample.navigation.Screen
import com.spruceid.mobilesdkexample.rememberQrBitmapPainter
import com.spruceid.mobilesdkexample.ui.theme.ColorStone300
import com.spruceid.mobilesdkexample.ui.theme.ColorStone950
import com.spruceid.mobilesdkexample.ui.theme.Inter
+import com.spruceid.mobilesdkexample.utils.getCurrentSqlDate
+import com.spruceid.mobilesdkexample.viewmodels.VerificationActivityLogsViewModel
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModel
import io.ktor.http.Url
import kotlinx.coroutines.launch
@@ -50,7 +53,8 @@ enum class VerifyDelegatedOid4vpViewSteps {
fun VerifyDelegatedOid4vpView(
navController: NavController,
verificationId: String,
- verificationMethodsViewModel: VerificationMethodsViewModel
+ verificationMethodsViewModel: VerificationMethodsViewModel,
+ verificationActivityLogsViewModel: VerificationActivityLogsViewModel,
) {
val scope = rememberCoroutineScope()
@@ -181,6 +185,18 @@ fun VerifyDelegatedOid4vpView(
VerifierCredentialSuccessView(
rawCredential = presentation!!,
onClose = { back() },
+ logVerification = { title, issuer ->
+ scope.launch {
+ verificationActivityLogsViewModel.saveVerificationActivityLog(
+ VerificationActivityLogs(
+ credentialTitle = title,
+ issuer = issuer,
+ verificationDateTime = getCurrentSqlDate(),
+ additionalInformation = ""
+ )
+ )
+ }
+ }
)
}
}
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyEAView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyEAView.kt
index da35ae7..6c55ac8 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyEAView.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyEAView.kt
@@ -13,7 +13,10 @@ import com.spruceid.mobile.sdk.rs.verifyVcbQrcodeAgainstMrz
import com.spruceid.mobilesdkexample.LoadingView
import com.spruceid.mobilesdkexample.ScanningComponent
import com.spruceid.mobilesdkexample.ScanningType
+import com.spruceid.mobilesdkexample.db.VerificationActivityLogs
import com.spruceid.mobilesdkexample.navigation.Screen
+import com.spruceid.mobilesdkexample.utils.getCurrentSqlDate
+import com.spruceid.mobilesdkexample.viewmodels.VerificationActivityLogsViewModel
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@@ -24,19 +27,13 @@ enum class VerifyEASteps {
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)
@Composable
fun VerifyEAView(
- navController: NavController
+ navController: NavController,
+ verificationActivityLogsViewModel: VerificationActivityLogsViewModel,
) {
- var step by remember {
- mutableStateOf(VerifyEASteps.STEP_ONE)
- }
-
- var success by remember {
- mutableStateOf(null)
- }
-
- var stepOne by remember {
- mutableStateOf(null)
- }
+ var step by remember { mutableStateOf(VerifyEASteps.STEP_ONE) }
+ var success by remember { mutableStateOf(null) }
+ var stepOne by remember { mutableStateOf(null) }
+ var verifying by remember { mutableStateOf(false) }
fun onReadStepOne(content: String) {
stepOne = content
@@ -47,16 +44,27 @@ fun VerifyEAView(
}
fun onReadStepTwo(content: String) {
- success = true
- GlobalScope.launch {
- try {
- verifyVcbQrcodeAgainstMrz(mrzPayload = content, qrPayload = stepOne!!)
- success = true
- } catch (e: Exception) {
- success = false
- e.printStackTrace()
+ if (!verifying) {
+ verifying = true
+ GlobalScope.launch {
+ try {
+ verifyVcbQrcodeAgainstMrz(mrzPayload = content, qrPayload = stepOne!!)
+ success = true
+ verificationActivityLogsViewModel.saveVerificationActivityLog(
+ VerificationActivityLogs(
+ credentialTitle = "Employment Authorization",
+ issuer = "State of Utopia",
+ verificationDateTime = getCurrentSqlDate(),
+ additionalInformation = ""
+ )
+ )
+ } catch (e: Exception) {
+ success = false
+ e.printStackTrace()
+ }
+ step = VerifyEASteps.SUCCESS
+ verifying = false
}
- step = VerifyEASteps.SUCCESS
}
}
@@ -68,38 +76,42 @@ fun VerifyEAView(
}
}
- when (step) {
- VerifyEASteps.STEP_ONE -> {
- ScanningComponent(
- subtitle = "Scan the front of your\nemployment authorization",
- scanningType = ScanningType.QRCODE,
- onRead = ::onReadStepOne,
- onCancel = ::back
- )
- }
+ if (verifying) {
+ LoadingView(loadingText = "Verifying...")
+ } else {
+ when (step) {
+ VerifyEASteps.STEP_ONE -> {
+ ScanningComponent(
+ subtitle = "Scan the front of your\nemployment authorization",
+ scanningType = ScanningType.QRCODE,
+ onRead = ::onReadStepOne,
+ onCancel = ::back
+ )
+ }
- VerifyEASteps.INTERMEDIATE -> {
- LoadingView(
- loadingText = "Verifying..."
- )
- }
+ VerifyEASteps.INTERMEDIATE -> {
+ LoadingView(
+ loadingText = "Verifying..."
+ )
+ }
- VerifyEASteps.STEP_TWO -> {
- ScanningComponent(
- title = "Scan MRZ",
- subtitle = "Scan the back of your document",
- scanningType = ScanningType.MRZ,
- onRead = ::onReadStepTwo,
- onCancel = ::back
- )
- }
+ VerifyEASteps.STEP_TWO -> {
+ ScanningComponent(
+ title = "Scan MRZ",
+ subtitle = "Scan the back of your document",
+ scanningType = ScanningType.MRZ,
+ onRead = ::onReadStepTwo,
+ onCancel = ::back
+ )
+ }
- VerifyEASteps.SUCCESS -> {
- VerifierBinarySuccessView(
- success = success!!,
- description = if (success!!) "Valid Employment Authorization" else "Invalid Employment Authorization",
- onClose = ::back
- )
+ VerifyEASteps.SUCCESS -> {
+ VerifierBinarySuccessView(
+ success = success!!,
+ description = if (success!!) "Valid Employment Authorization" else "Invalid Employment Authorization",
+ onClose = ::back
+ )
+ }
}
}
}
\ No newline at end of file
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyMDocView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyMDocView.kt
index ead3dcf..0598986 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyMDocView.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifyMDocView.kt
@@ -30,8 +30,11 @@ import com.spruceid.mobile.sdk.rs.MDocItem
import com.spruceid.mobilesdkexample.LoadingView
import com.spruceid.mobilesdkexample.ScanningComponent
import com.spruceid.mobilesdkexample.ScanningType
+import com.spruceid.mobilesdkexample.db.VerificationActivityLogs
import com.spruceid.mobilesdkexample.navigation.Screen
import com.spruceid.mobilesdkexample.utils.checkAndRequestBluetoothPermissions
+import com.spruceid.mobilesdkexample.utils.getCurrentSqlDate
+import com.spruceid.mobilesdkexample.viewmodels.VerificationActivityLogsViewModel
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@@ -85,7 +88,10 @@ enum class State {
ExperimentalPermissionsApi::class
)
@Composable
-fun VerifyMDocView(navController: NavController) {
+fun VerifyMDocView(
+ navController: NavController,
+ verificationActivityLogsViewModel: VerificationActivityLogsViewModel,
+) {
val context = LocalContext.current
var reader: IsoMdlReader? = null
@@ -116,6 +122,14 @@ fun VerifyMDocView(navController: NavController) {
if (state.containsKey("mdl")) {
result = reader?.handleResponse(state["mdl"] as ByteArray)
scanProcessState = State.DONE
+ VerificationActivityLogs(
+ credentialTitle = "Driver's License",
+ issuer = getDiscriminant(
+ result!!["org.iso.18013.5.1"]?.get("issuing_authority")!!
+ ),
+ verificationDateTime = getCurrentSqlDate(),
+ additionalInformation = ""
+ )
}
}
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerificationActivityLogScreen.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerificationActivityLogScreen.kt
deleted file mode 100644
index cef8f36..0000000
--- a/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerificationActivityLogScreen.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-package com.spruceid.mobilesdkexample.verifiersettings
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import com.spruceid.mobilesdkexample.db.VerificationActivityLogs
-import com.spruceid.mobilesdkexample.ui.theme.ColorBlue600
-import com.spruceid.mobilesdkexample.ui.theme.ColorStone300
-import com.spruceid.mobilesdkexample.ui.theme.ColorStone600
-import com.spruceid.mobilesdkexample.ui.theme.ColorStone950
-import com.spruceid.mobilesdkexample.ui.theme.Inter
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-
-@Composable
-fun VerificationActivityLogsScreen() {
-// val verificationActivityLogs by verificationActivityLogsViewModel.verificationActivityLogs.collectAsState()
- val verificationActivityLogs = listOf()
- val dateFormatter = SimpleDateFormat("MM/dd/yyyy", Locale.getDefault())
-
- LazyColumn(
- Modifier
- .padding(horizontal = 20.dp)
- .padding(top = 10.dp),
- ) {
- item {
- Text(
- text = "Coming Soon",
- fontFamily = Inter,
- fontWeight = FontWeight.SemiBold,
- fontSize = 17.sp,
- color = ColorStone950,
- modifier = Modifier.padding(bottom = 4.dp),
- )
- }
- items(verificationActivityLogs) { log ->
- Column {
- Text(
- text = log.name,
- fontFamily = Inter,
- fontWeight = FontWeight.SemiBold,
- fontSize = 17.sp,
- color = ColorStone950,
- modifier = Modifier.padding(bottom = 4.dp),
- )
- Text(
- text = log.credentialTitle,
- fontFamily = Inter,
- fontWeight = FontWeight.Normal,
- fontSize = 14.sp,
- color = ColorStone600,
- modifier = Modifier.padding(bottom = 4.dp),
- )
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- modifier =
- Modifier
- .fillMaxWidth(),
- ) {
- Text(
- text = log.status,
- fontFamily = Inter,
- fontWeight = FontWeight.Normal,
- fontSize = 14.sp,
- color = ColorStone600,
- modifier =
- Modifier
- .padding(bottom = 4.dp),
- )
- Text(
- text = "${
- if (log.expirationDate.before(
- Date(),
- )
- ) {
- "expired"
- } else {
- "expires"
- }
- } on ${dateFormatter.format(log.expirationDate)}",
- fontFamily = Inter,
- fontWeight = FontWeight.Normal,
- fontSize = 14.sp,
- color = ColorStone600,
- modifier = Modifier.padding(bottom = 4.dp),
- )
- }
- Text(
- text = "Scanned on ${dateFormatter.format(log.date)}",
- fontFamily = Inter,
- fontWeight = FontWeight.Normal,
- fontSize = 14.sp,
- fontStyle = FontStyle.Italic,
- textAlign = TextAlign.End,
- color = ColorStone300,
- modifier = Modifier.padding(top = 8.dp, bottom = 4.dp),
- )
- HorizontalDivider(modifier = Modifier.padding(bottom = 12.dp))
- }
- }
- item {
- Button(
- onClick = {
-// settingsViewModel.exportMetrics(logsViewModel.generateActivityLogCSV(), "activity_logs.csv")
- },
- modifier =
- Modifier
- .fillMaxWidth()
- .padding(20.dp),
- colors =
- ButtonDefaults.buttonColors(
- containerColor = ColorBlue600,
- contentColor = Color.White,
- ),
- ) {
- Text(
- text = "Export",
- fontFamily = Inter,
- fontWeight = FontWeight.SemiBold,
- fontSize = 16.sp,
- color = Color.White,
- )
- }
- }
- }
-}
\ No newline at end of file
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerifierSettingsActivityLogScreen.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerifierSettingsActivityLogScreen.kt
new file mode 100644
index 0000000..185c511
--- /dev/null
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerifierSettingsActivityLogScreen.kt
@@ -0,0 +1,367 @@
+package com.spruceid.mobilesdkexample.verifiersettings
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.draw.scale
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.window.Dialog
+import androidx.navigation.NavController
+import com.spruceid.mobilesdkexample.R
+import com.spruceid.mobilesdkexample.db.VerificationActivityLogs
+import com.spruceid.mobilesdkexample.ui.theme.ColorBase1
+import com.spruceid.mobilesdkexample.ui.theme.ColorBase50
+import com.spruceid.mobilesdkexample.ui.theme.ColorStone300
+import com.spruceid.mobilesdkexample.ui.theme.ColorStone400
+import com.spruceid.mobilesdkexample.ui.theme.ColorStone600
+import com.spruceid.mobilesdkexample.ui.theme.ColorStone700
+import com.spruceid.mobilesdkexample.ui.theme.ColorStone900
+import com.spruceid.mobilesdkexample.ui.theme.ColorStone950
+import com.spruceid.mobilesdkexample.ui.theme.Inter
+import com.spruceid.mobilesdkexample.ui.theme.MobileSdkTheme
+import com.spruceid.mobilesdkexample.utils.DropdownInput
+import com.spruceid.mobilesdkexample.utils.formatSqlDateTime
+import com.spruceid.mobilesdkexample.viewmodels.HelpersViewModel
+import com.spruceid.mobilesdkexample.viewmodels.VerificationActivityLogsViewModel
+
+@Composable
+fun VerifierSettingsActivityLogScreen(
+ navController: NavController,
+ verificationActivityLogsViewModel: VerificationActivityLogsViewModel,
+ helpersViewModel: HelpersViewModel
+) {
+ // TODO: WIP: we will finish these filters in the future
+ // val distinctCredentialTitles = verificationActivityLogsViewModel.getDistinctCredentialTitles()
+ val verificationActivityLogs by verificationActivityLogsViewModel.verificationActivityLogs.collectAsState()
+
+ Column(
+ Modifier
+ .padding(all = 20.dp)
+ .padding(top = 20.dp)
+ ) {
+ VerifierSettingsActivityLogScreenHeader(
+ onBack = {
+ navController.popBackStack()
+ }
+ )
+ VerifierSettingsActivityLogScreenBody(
+ verificationActivityLogs = verificationActivityLogs,
+ export = { logs ->
+ helpersViewModel.exportCSV(
+ verificationActivityLogsViewModel.generateVerificationActivityLogCSV(logs = logs),
+ "activity_logs.csv"
+ )
+ }
+ )
+ // TODO: WIP: we will finish these filters in the future
+ // FilterModal()
+ }
+}
+
+@Composable
+fun VerifierSettingsActivityLogScreenHeader(onBack: () -> Unit) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .height(36.dp)
+ .clickable {
+ onBack()
+ }
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.chevron),
+ contentDescription = stringResource(id = R.string.chevron),
+ modifier = Modifier
+ .rotate(180f)
+ .scale(0.4f)
+ )
+ Text(
+ text = "Activity Log",
+ fontFamily = Inter,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 20.sp,
+ color = ColorStone950
+ )
+ Spacer(Modifier.weight(1f))
+
+ // TODO: WIP: we will finish these filters in the future
+ // val distinctCredentialTitles = verificationActivityLogsViewModel.getDistinctCredentialTitles()
+// Box(
+// contentAlignment = Alignment.Center,
+// modifier = Modifier
+// .width(36.dp)
+// .height(36.dp)
+// .padding(start = 4.dp)
+// .shadow(
+// elevation = 10.dp,
+// spotColor = ColorStone950,
+// shape = RoundedCornerShape(6.dp)
+// )
+// .border(
+// width = 1.dp,
+// color = ColorBase100,
+// shape = RoundedCornerShape(6.dp)
+// )
+// .background(ColorBase1)
+// .clickable {
+//
+// }
+// ) {
+// Image(
+// painter = painterResource(id = R.drawable.filter),
+// contentDescription = stringResource(id = R.string.filter),
+// modifier = Modifier
+// .width(20.dp)
+// .height(20.dp)
+// )
+// }
+ }
+}
+
+@Composable
+fun VerifierSettingsActivityLogScreenBody(
+ verificationActivityLogs: List,
+ export: (List) -> Unit
+) {
+ Column(
+ Modifier
+ .padding(top = 10.dp)
+ .navigationBarsPadding(),
+ ) {
+ if (verificationActivityLogs.isEmpty()) {
+ Column(
+ Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = "No Activity Log Found",
+ fontFamily = Inter,
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Normal,
+ color = ColorStone400
+ )
+ }
+ } else {
+ LazyColumn(
+ Modifier
+ .padding(top = 10.dp)
+ .fillMaxSize()
+ .weight(weight = 1f, fill = false),
+ ) {
+ items(verificationActivityLogs) { log ->
+ Column {
+ Text(
+ text = log.credentialTitle,
+ fontFamily = Inter,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 17.sp,
+ color = ColorStone950,
+ modifier = Modifier.padding(bottom = 4.dp),
+ )
+ Text(
+ text = log.issuer,
+ fontFamily = Inter,
+ fontWeight = FontWeight.Normal,
+ fontSize = 15.sp,
+ color = ColorStone600,
+ modifier = Modifier.padding(bottom = 4.dp),
+ )
+ Text(
+ text = formatSqlDateTime(log.verificationDateTime),
+ fontFamily = Inter,
+ fontWeight = FontWeight.Normal,
+ fontSize = 15.sp,
+ color = ColorStone600,
+ modifier = Modifier.padding(bottom = 4.dp),
+ )
+ HorizontalDivider(modifier = Modifier.padding(bottom = 12.dp))
+ }
+ }
+ }
+ Button(
+ onClick = {
+ export(verificationActivityLogs)
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .border(
+ width = 1.dp,
+ color = ColorStone300,
+ shape = RoundedCornerShape(100.dp)
+ ),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = ColorBase1,
+ contentColor = ColorStone950,
+ ),
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.export),
+ contentDescription = stringResource(id = R.string.export),
+ modifier = Modifier.padding(end = 5.dp),
+ )
+ Text(
+ text = "Export",
+ fontFamily = Inter,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 16.sp,
+ color = ColorStone950,
+ )
+ }
+ }
+ }
+ }
+}
+
+// TODO: WIP: We will finish these filters in the future
+@Composable
+fun FilterModal() {
+ Dialog(onDismissRequest = { }) {
+ Surface(
+ shape = RoundedCornerShape(8.dp),
+ color = ColorBase1,
+ tonalElevation = 8.dp,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Column {
+ // Title
+ Column(
+ Modifier
+ .padding(horizontal = 24.dp)
+ .padding(vertical = 12.dp)
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(
+ "Filters",
+ fontFamily = Inter,
+ fontWeight = FontWeight.Normal,
+ fontSize = 24.sp,
+ color = ColorStone900
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ Icon(
+ imageVector = Icons.Default.Close,
+ contentDescription = "Close",
+ modifier = Modifier.clickable { }
+ )
+ }
+ }
+
+ // Title divider
+ HorizontalDivider()
+
+ // Body
+ Column(
+ Modifier
+ .padding(horizontal = 24.dp)
+ .padding(vertical = 12.dp)
+ ) {
+ DropdownInput(
+ options = listOf("Option 1", "Option 2", "Option 3"),
+ onSelect = { option ->
+
+ }
+ )
+ }
+
+ // Footer divider
+ HorizontalDivider()
+
+ Row(
+ Modifier
+ .padding(horizontal = 14.dp)
+ .padding(vertical = 12.dp)
+ ) {
+ Button(
+ onClick = {
+
+ },
+ shape = RoundedCornerShape(100.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.Transparent,
+ contentColor = Color.Black,
+ ),
+ border = BorderStroke(1.dp, ColorStone300),
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxWidth()
+ .padding(end = 6.dp)
+ ) {
+ Text(
+ text = "Cancel",
+ fontFamily = Inter,
+ fontWeight = FontWeight.SemiBold,
+ color = Color.Black,
+ )
+ }
+ Button(
+ onClick = {
+
+ },
+ shape = RoundedCornerShape(100.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = ColorStone700,
+ contentColor = ColorBase50,
+ ),
+ border = BorderStroke(1.dp, ColorStone700),
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxWidth()
+ .padding(start = 6.dp)
+ ) {
+ Text(
+ text = "Apply",
+ fontFamily = Inter,
+ fontWeight = FontWeight.SemiBold,
+ color = ColorBase50,
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun FilterModalPreview() {
+ MobileSdkTheme {
+ FilterModal()
+ }
+}
\ No newline at end of file
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerifierSettingsHomeView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerifierSettingsHomeView.kt
index a819719..c1be24b 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerifierSettingsHomeView.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerifierSettingsHomeView.kt
@@ -1,6 +1,7 @@
package com.spruceid.mobilesdkexample.verifiersettings
import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -8,24 +9,21 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.outlined.List
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
@@ -35,6 +33,7 @@ import androidx.navigation.NavController
import com.spruceid.mobilesdkexample.R
import com.spruceid.mobilesdkexample.navigation.Screen
import com.spruceid.mobilesdkexample.ui.theme.ColorRose600
+import com.spruceid.mobilesdkexample.ui.theme.ColorStone50
import com.spruceid.mobilesdkexample.ui.theme.ColorStone600
import com.spruceid.mobilesdkexample.ui.theme.ColorStone950
import com.spruceid.mobilesdkexample.ui.theme.Inter
@@ -42,20 +41,11 @@ import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModel
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
-enum class VerifierSubSettings {
- VERIFICATION_ACTIVITY_LOG,
-}
-
@Composable
fun VerifierSettingsHomeView(
navController: NavController,
verificationMethodsViewModel: VerificationMethodsViewModel
) {
-
- var subpage by remember {
- mutableStateOf(null)
- }
-
Column(
Modifier
.padding(all = 20.dp)
@@ -63,142 +53,134 @@ fun VerifierSettingsHomeView(
) {
VerifierSettingsHomeHeader(
onBack = {
- if (subpage != null) {
- subpage = null
- } else {
- navController.navigate(
- Screen.HomeScreen.route.replace("{tab}", "verifier")
- ) {
- popUpTo(0)
- }
+ navController.navigate(
+ Screen.HomeScreen.route.replace("{tab}", "verifier")
+ ) {
+ popUpTo(0)
}
}
)
VerifierSettingsHomeBody(
- subpage = subpage,
+ navController = navController,
verificationMethodsViewModel = verificationMethodsViewModel,
- changeSubPage = { sp ->
- subpage = sp
- }
)
}
}
@Composable
-fun VerifierSettingsHomeHeader(
- onBack: () -> Unit
-) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.clickable {
- onBack()
- }
- ) {
- Image(
- painter = painterResource(id = R.drawable.chevron),
- contentDescription = stringResource(id = R.string.chevron),
- modifier = Modifier
- .rotate(180f)
- .scale(0.7f)
- )
+fun VerifierSettingsHomeHeader(onBack: () -> Unit) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
Text(
- text = "Verifier Settings",
+ text = "Settings",
fontFamily = Inter,
fontWeight = FontWeight.SemiBold,
- fontSize = 24.sp,
- color = ColorStone950,
- modifier = Modifier.padding(start = 10.dp)
+ fontSize = 20.sp,
+ color = ColorStone950
)
Spacer(Modifier.weight(1f))
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier
+ .width(36.dp)
+ .height(36.dp)
+ .padding(start = 4.dp)
+ .clip(shape = RoundedCornerShape(8.dp))
+ .background(ColorStone950)
+ .clickable {
+ onBack()
+ }
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.cog),
+ contentDescription = stringResource(id = R.string.cog),
+ colorFilter = ColorFilter.tint(ColorStone50),
+ modifier = Modifier
+ .width(20.dp)
+ .height(20.dp)
+ )
+ }
}
-
}
@Composable
fun VerifierSettingsHomeBody(
- subpage: VerifierSubSettings?,
+ navController: NavController,
verificationMethodsViewModel: VerificationMethodsViewModel,
- changeSubPage: (VerifierSubSettings?) -> Unit
) {
- if (subpage == null) {
- Column(
+ Column(
+ Modifier
+ .padding(top = 10.dp)
+ .navigationBarsPadding(),
+ ) {
+ Box(
Modifier
- .padding(horizontal = 20.dp)
- .padding(top = 10.dp)
- .navigationBarsPadding(),
+ .fillMaxWidth()
+ .clickable {
+ navController.navigate(Screen.VerifierSettingsActivityLogScreen.route)
+ },
) {
- Box(
- Modifier
- .fillMaxWidth()
- .clickable {
- changeSubPage(VerifierSubSettings.VERIFICATION_ACTIVITY_LOG)
- },
- ) {
- Column {
+ Column {
+ Row(
+ Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
Row(
- Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Image(
- Icons.AutoMirrored.Outlined.List,
- contentDescription = stringResource(id = R.string.verification_activity_log),
- modifier = Modifier.padding(end = 5.dp),
- )
- Text(
- text = "Verification Activity Log",
- fontFamily = Inter,
- fontWeight = FontWeight.Medium,
- fontSize = 14.sp,
- color = ColorStone600,
- modifier = Modifier.padding(bottom = 5.dp, top = 5.dp),
- )
- }
-
Image(
- painter = painterResource(id = R.drawable.chevron),
- contentDescription = stringResource(id = R.string.chevron),
- modifier = Modifier.scale(0.5f)
+ painter = painterResource(id = R.drawable.verification_activity_log),
+ contentDescription = stringResource(id = R.string.verification_activity_log),
+ modifier = Modifier.padding(end = 5.dp),
+ )
+ Text(
+ text = "Activity Log",
+ fontFamily = Inter,
+ fontWeight = FontWeight.Medium,
+ fontSize = 17.sp,
+ color = ColorStone950,
+ modifier = Modifier.padding(bottom = 5.dp, top = 5.dp),
)
}
- Text(
- text = "view and export verification history",
- fontFamily = Inter,
- fontWeight = FontWeight.Normal,
- fontSize = 14.sp,
- color = ColorStone600,
+ Image(
+ painter = painterResource(id = R.drawable.chevron),
+ contentDescription = stringResource(id = R.string.chevron),
+ modifier = Modifier.scale(0.5f)
)
}
- }
- Spacer(Modifier.weight(1f))
- Button(
- onClick = {
- GlobalScope.launch {
- verificationMethodsViewModel.deleteAllVerificationMethods()
- }
- },
- shape = RoundedCornerShape(5.dp),
- colors = ButtonDefaults.buttonColors(
- containerColor = ColorRose600,
- contentColor = Color.White,
- ),
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 30.dp)
- ) {
+
Text(
- text = "Delete all added verification methods",
+ text = "View and export verification history",
fontFamily = Inter,
- fontWeight = FontWeight.SemiBold,
- color = Color.White,
+ fontWeight = FontWeight.Normal,
+ fontSize = 15.sp,
+ color = ColorStone600,
)
}
}
- } else if (subpage == VerifierSubSettings.VERIFICATION_ACTIVITY_LOG) {
- VerificationActivityLogsScreen()
+ Spacer(Modifier.weight(1f))
+ Button(
+ onClick = {
+ GlobalScope.launch {
+ verificationMethodsViewModel.deleteAllVerificationMethods()
+ }
+ },
+ shape = RoundedCornerShape(5.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = ColorRose600,
+ contentColor = Color.White,
+ ),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 30.dp)
+ ) {
+ Text(
+ text = "Delete all added verification methods",
+ fontFamily = Inter,
+ fontWeight = FontWeight.SemiBold,
+ color = Color.White,
+ )
+ }
}
}
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/HelpersViewModel.kt b/example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/HelpersViewModel.kt
new file mode 100644
index 0000000..5e29c48
--- /dev/null
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/HelpersViewModel.kt
@@ -0,0 +1,44 @@
+package com.spruceid.mobilesdkexample.viewmodels
+
+import android.app.Application
+import android.content.Intent
+import androidx.core.content.ContextCompat
+import androidx.core.content.FileProvider
+import androidx.lifecycle.AndroidViewModel
+import java.io.File
+import java.io.PrintWriter
+
+fun File.clearText() {
+ PrintWriter(this).also {
+ it.print("")
+ it.close()
+ }
+}
+
+fun File.updateText(content: String) {
+ clearText()
+ appendText(content)
+}
+
+class HelpersViewModel(application: Application) : AndroidViewModel(application) {
+ fun exportCSV(content: String, filename: String) {
+ val app = getApplication()
+ val file = File(app.cacheDir, filename)
+ file.updateText(content)
+
+ val uri =
+ FileProvider.getUriForFile(
+ app.baseContext,
+ app.baseContext.packageName + ".provider",
+ file,
+ )
+ Intent(Intent.ACTION_SEND).apply {
+ type = "text/csv"
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ putExtra(Intent.EXTRA_STREAM, uri)
+ }.also { intent ->
+ ContextCompat.startActivity(app.baseContext, intent, null)
+ }
+ }
+}
\ No newline at end of file
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/VerifierActivityLogsViewModel.kt b/example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/VerifierActivityLogsViewModel.kt
index 195aec5..51824e9 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/VerifierActivityLogsViewModel.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/VerifierActivityLogsViewModel.kt
@@ -5,11 +5,13 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.spruceid.mobilesdkexample.db.VerificationActivityLogs
import com.spruceid.mobilesdkexample.db.VerificationActivityLogsRepository
+import com.spruceid.mobilesdkexample.utils.formatSqlDateTime
+import com.spruceid.mobilesdkexample.utils.removeCommas
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
-class LogsViewModel(private val verificationActivityLogsRepository: VerificationActivityLogsRepository) :
+class VerificationActivityLogsViewModel(private val verificationActivityLogsRepository: VerificationActivityLogsRepository) :
ViewModel() {
private val _verificationActivityLogs = MutableStateFlow(listOf())
val verificationActivityLogs = _verificationActivityLogs.asStateFlow()
@@ -27,17 +29,41 @@ class LogsViewModel(private val verificationActivityLogsRepository: Verification
verificationActivityLogsRepository.getVerificationActivityLogs()
}
- fun generateVerificationActivityLogCSV(): String {
- val heading = "ID, Full Name, Credential Title, Permit Expiration, Status, Date\n"
- return heading +
- verificationActivityLogs.value.joinToString("\n") {
- "${it.id}, ${it.name}, ${it.credentialTitle}, ${it.expirationDate}, ${it.status}, ${it.date}"
- }
+ // TODO: Add fromDate and credentialType filter params
+ fun getFilteredVerificationActivityLog() {
+ verificationActivityLogsRepository.getFilteredVerificationActivityLogs()
+ }
+
+ fun getDistinctCredentialTitles(): List {
+ return verificationActivityLogsRepository.getDistinctCredentialTitles()
+ }
+
+ fun generateVerificationActivityLogCSV(logs: List? = null): String {
+ val heading =
+ "ID, Credential Title, Issuer, Verification Date Time, Additional Information\n"
+
+ val rows = logs?.joinToString("\n") {
+ "${it.id}, " +
+ "${it.credentialTitle}, " +
+ "${it.issuer}, " +
+ "${formatSqlDateTime(it.verificationDateTime).removeCommas()}, " +
+ it.additionalInformation
+ }
+ ?: verificationActivityLogs.value.joinToString("\n") {
+ "${it.id}, " +
+ "${it.credentialTitle}, " +
+ "${it.issuer}, " +
+ "${formatSqlDateTime(it.verificationDateTime).removeCommas()}, " +
+ it.additionalInformation
+ }
+
+ return heading + rows
}
}
-class LogsViewModelFactory(private val repository: VerificationActivityLogsRepository) :
+class VerificationActivityLogsViewModelFactory(private val repository: VerificationActivityLogsRepository) :
ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST")
- override fun create(modelClass: Class): T = LogsViewModel(repository) as T
+ override fun create(modelClass: Class): T =
+ VerificationActivityLogsViewModel(repository) as T
}
diff --git a/example/src/main/res/drawable/export.xml b/example/src/main/res/drawable/export.xml
new file mode 100644
index 0000000..2b3baba
--- /dev/null
+++ b/example/src/main/res/drawable/export.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/example/src/main/res/drawable/filter.xml b/example/src/main/res/drawable/filter.xml
new file mode 100644
index 0000000..862378b
--- /dev/null
+++ b/example/src/main/res/drawable/filter.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/example/src/main/res/drawable/verification_activity_log.xml b/example/src/main/res/drawable/verification_activity_log.xml
new file mode 100644
index 0000000..32a03f6
--- /dev/null
+++ b/example/src/main/res/drawable/verification_activity_log.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml
index a73d65b..15b373e 100644
--- a/example/src/main/res/values/strings.xml
+++ b/example/src/main/res/values/strings.xml
@@ -22,4 +22,6 @@
Start flow
Restart
Settings
+ Click to filter
+ Export
\ No newline at end of file
diff --git a/example/src/main/res/xml/provider_paths.xml b/example/src/main/res/xml/provider_paths.xml
new file mode 100644
index 0000000..f742886
--- /dev/null
+++ b/example/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+