From ba478c829efea8b7f5ae99baebb1c49326f8390f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juliano=20C=C3=A9zar=20Chagas=20Tavares?= Date: Mon, 23 Sep 2024 10:35:05 -0300 Subject: [PATCH] Reference app supports verification logs (#28) This configures the app to store data using SQLite and adds a view to display and export it. This won't be used yet, but we are letting it ready for further development. --- build.gradle.kts | 5 +- example/build.gradle.kts | 7 + .../spruceid/mobilesdkexample/MainActivity.kt | 5 +- .../mobilesdkexample/db/AppDatabase.kt | 33 +++ .../mobilesdkexample/db/Converters.kt | 16 ++ .../com/spruceid/mobilesdkexample/db/Daos.kt | 14 + .../spruceid/mobilesdkexample/db/Entities.kt | 15 + .../mobilesdkexample/db/Repositories.kt | 17 ++ .../mobilesdkexample/navigation/Screen.kt | 2 + .../navigation/SetupNavGraph.kt | 8 +- .../mobilesdkexample/ui/theme/Color.kt | 1 + .../verifier/VerifierHomeView.kt | 29 +- .../VerificationActivityLogScreen.kt | 146 ++++++++++ .../VerifierSettingsHomeView.kt | 270 ++++++++++++++++++ .../VerifierActivityLogsViewModel.kt | 39 +++ .../src/main/res/drawable-hdpi/chevron.png | Bin 0 -> 661 bytes .../src/main/res/drawable-mdpi/chevron.png | Bin 0 -> 427 bytes .../src/main/res/drawable-xhdpi/chevron.png | Bin 0 -> 985 bytes .../src/main/res/drawable-xxhdpi/chevron.png | Bin 0 -> 1441 bytes .../src/main/res/drawable-xxxhdpi/chevron.png | Bin 0 -> 1678 bytes example/src/main/res/values/colors.xml | 1 + example/src/main/res/values/strings.xml | 2 + gradle/wrapper/gradle-wrapper.properties | 2 +- 23 files changed, 604 insertions(+), 8 deletions(-) create mode 100644 example/src/main/java/com/spruceid/mobilesdkexample/db/AppDatabase.kt create mode 100644 example/src/main/java/com/spruceid/mobilesdkexample/db/Converters.kt create mode 100644 example/src/main/java/com/spruceid/mobilesdkexample/db/Daos.kt create mode 100644 example/src/main/java/com/spruceid/mobilesdkexample/db/Entities.kt create mode 100644 example/src/main/java/com/spruceid/mobilesdkexample/db/Repositories.kt create mode 100644 example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerificationActivityLogScreen.kt create mode 100644 example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerifierSettingsHomeView.kt create mode 100644 example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/VerifierActivityLogsViewModel.kt create mode 100644 example/src/main/res/drawable-hdpi/chevron.png create mode 100644 example/src/main/res/drawable-mdpi/chevron.png create mode 100644 example/src/main/res/drawable-xhdpi/chevron.png create mode 100644 example/src/main/res/drawable-xxhdpi/chevron.png create mode 100644 example/src/main/res/drawable-xxxhdpi/chevron.png diff --git a/build.gradle.kts b/build.gradle.kts index c7b5f84..d848fb4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.2.0" apply false + id("com.android.application") version "8.5.1" apply false id("org.jetbrains.kotlin.android") version "1.9.23" apply false - id("com.android.library") version "8.2.0" apply false + id("com.android.library") version "8.5.1" apply false id("com.gradleup.nmcp") version "0.0.4" apply true + id("com.google.devtools.ksp") version "1.9.23-1.0.20" apply false } diff --git a/example/build.gradle.kts b/example/build.gradle.kts index a72b3a3..2c1a1a2 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") + id("com.google.devtools.ksp") } android { @@ -50,6 +51,12 @@ android { } dependencies { + val roomVersion = "2.4.0" + + implementation("androidx.room:room-runtime:$roomVersion") + ksp("androidx.room:room-compiler:$roomVersion") + implementation("androidx.room:room-ktx:$roomVersion") + annotationProcessor("androidx.room:room-compiler:$roomVersion") implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.activity:activity-compose:1.8.2") diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/MainActivity.kt b/example/src/main/java/com/spruceid/mobilesdkexample/MainActivity.kt index 3930a86..a964878 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/MainActivity.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/MainActivity.kt @@ -1,17 +1,18 @@ package com.spruceid.mobilesdkexample +import android.app.Application import android.net.Uri 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.mobile.sdk.CredentialsViewModel +import com.spruceid.mobilesdkexample.db.AppDatabase +import com.spruceid.mobilesdkexample.db.VerificationActivityLogsRepository import com.spruceid.mobilesdkexample.navigation.SetupNavGraph import com.spruceid.mobilesdkexample.ui.theme.Bg import com.spruceid.mobilesdkexample.ui.theme.MobileSdkTheme diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/db/AppDatabase.kt b/example/src/main/java/com/spruceid/mobilesdkexample/db/AppDatabase.kt new file mode 100644 index 0000000..45f0214 --- /dev/null +++ b/example/src/main/java/com/spruceid/mobilesdkexample/db/AppDatabase.kt @@ -0,0 +1,33 @@ +package com.spruceid.mobilesdkexample.db + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters + +@Database(entities = [VerificationActivityLogs::class], version = 1) +@TypeConverters(*[DateConverter::class]) +abstract class AppDatabase : RoomDatabase() { + abstract fun verificationActivityLogsDao(): VerificationActivityLogsDao + + companion object { + @Volatile + private var dbInstance: AppDatabase? = null + + fun getDatabase(context: Context): AppDatabase { + return dbInstance ?: synchronized(this) { + val instance = + Room.databaseBuilder( + context.applicationContext, + AppDatabase::class.java, + "verification_activity_logs", + ) + .allowMainThreadQueries() + .build() + dbInstance = instance + instance + } + } + } +} diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/db/Converters.kt b/example/src/main/java/com/spruceid/mobilesdkexample/db/Converters.kt new file mode 100644 index 0000000..bddc22f --- /dev/null +++ b/example/src/main/java/com/spruceid/mobilesdkexample/db/Converters.kt @@ -0,0 +1,16 @@ +package com.spruceid.mobilesdkexample.db + +import androidx.room.TypeConverter +import java.sql.Date + +object DateConverter { + @TypeConverter + fun toDate(dateLong: Long?): Date? { + return if (dateLong == null) null else Date(dateLong) + } + + @TypeConverter + fun fromDate(date: Date?): Long? { + return date?.time + } +} diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/db/Daos.kt b/example/src/main/java/com/spruceid/mobilesdkexample/db/Daos.kt new file mode 100644 index 0000000..7d4b976 --- /dev/null +++ b/example/src/main/java/com/spruceid/mobilesdkexample/db/Daos.kt @@ -0,0 +1,14 @@ +package com.spruceid.mobilesdkexample.db + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query + +@Dao +interface VerificationActivityLogsDao { + @Insert + suspend fun insertVerificationActivity(verificationActivityLogs: VerificationActivityLogs) + + @Query("SELECT * FROM verification_activity_logs") + fun getAllVerificationActivityLogs(): List +} diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/db/Entities.kt b/example/src/main/java/com/spruceid/mobilesdkexample/db/Entities.kt new file mode 100644 index 0000000..238dcd9 --- /dev/null +++ b/example/src/main/java/com/spruceid/mobilesdkexample/db/Entities.kt @@ -0,0 +1,15 @@ +package com.spruceid.mobilesdkexample.db + +import androidx.room.Entity +import androidx.room.PrimaryKey +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, +) diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/db/Repositories.kt b/example/src/main/java/com/spruceid/mobilesdkexample/db/Repositories.kt new file mode 100644 index 0000000..cec2bdd --- /dev/null +++ b/example/src/main/java/com/spruceid/mobilesdkexample/db/Repositories.kt @@ -0,0 +1,17 @@ +package com.spruceid.mobilesdkexample.db + +import androidx.annotation.WorkerThread + +class VerificationActivityLogsRepository(private val verificationActivityLogsDao: VerificationActivityLogsDao) { + val verificationActivityLogs: List = verificationActivityLogsDao.getAllVerificationActivityLogs() + + @WorkerThread + suspend fun insertVerificationActivityLog(verificationActivityLogs: VerificationActivityLogs) { + verificationActivityLogsDao.insertVerificationActivity(verificationActivityLogs) + } + + @WorkerThread + suspend fun getVerificationActivityLogs(): List { + return verificationActivityLogsDao.getAllVerificationActivityLogs() + } +} 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 bedf768..0c2f11f 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/navigation/Screen.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/navigation/Screen.kt @@ -4,6 +4,7 @@ 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 VERIFIER_SETTINGS_HOME_PATH = "verifier_settings_home" sealed class Screen(val route: String) { @@ -11,4 +12,5 @@ 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 VerifierSettingsHomeScreen : Screen(VERIFIER_SETTINGS_HOME_PATH) } \ No newline at end of file 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 0e48115..3a23e8b 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/navigation/SetupNavGraph.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/navigation/SetupNavGraph.kt @@ -8,6 +8,7 @@ import com.spruceid.mobilesdkexample.HomeView import com.spruceid.mobilesdkexample.verifier.VerifyDLView import com.spruceid.mobilesdkexample.verifier.VerifyEAView import com.spruceid.mobilesdkexample.verifier.VerifyVCView +import com.spruceid.mobilesdkexample.verifiersettings.VerifierSettingsHomeView @Composable fun SetupNavGraph( @@ -15,7 +16,7 @@ fun SetupNavGraph( ) { NavHost( navController = navController, - startDestination = Screen.HomeScreen.route, + startDestination = Screen.HomeScreen.route ) { composable( route = Screen.HomeScreen.route, @@ -37,6 +38,11 @@ fun SetupNavGraph( ) { VerifyVCView(navController) } + composable( + route = Screen.VerifierSettingsHomeScreen.route, + ) { + VerifierSettingsHomeView(navController) + } } } diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/ui/theme/Color.kt b/example/src/main/java/com/spruceid/mobilesdkexample/ui/theme/Color.kt index 019746e..a0d932c 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/ui/theme/Color.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/ui/theme/Color.kt @@ -6,6 +6,7 @@ val SpruceBlue = Color(0xFF2F6AE1) val Primary = Color(0xFFF7F7F5) val Bg = Color(0xFFFDFDFC) val CredentialBorder = Color(0xFFE6E1D6) +val CodeBorder = Color(0xff949494) val GreenValid = Color(0xFF059669) val TextBody = Color(0xFF57534E) val TextHeader = Color(0xFF0C0A09) diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierHomeView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierHomeView.kt index e343dd5..783fdfc 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierHomeView.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierHomeView.kt @@ -4,10 +4,12 @@ import androidx.compose.foundation.Image 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.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.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState @@ -28,6 +30,7 @@ import androidx.navigation.NavController import com.spruceid.mobilesdkexample.R import com.spruceid.mobilesdkexample.navigation.Screen import com.spruceid.mobilesdkexample.ui.theme.Inter +import com.spruceid.mobilesdkexample.ui.theme.Primary import com.spruceid.mobilesdkexample.ui.theme.TextBody import com.spruceid.mobilesdkexample.ui.theme.TextHeader import com.spruceid.mobilesdkexample.ui.theme.TextOnPrimary @@ -47,13 +50,15 @@ fun VerifierHomeView( .padding(all = 20.dp) .padding(top = 20.dp) ) { - VerifierHomeHeader() + VerifierHomeHeader(navController = navController) VerifierHomeBody(navController = navController) } } @Composable -fun VerifierHomeHeader() { +fun VerifierHomeHeader( + navController: NavController +) { Row(verticalAlignment = Alignment.CenterVertically) { Text( text = "Spruce Verifier", @@ -63,6 +68,26 @@ fun VerifierHomeHeader() { color = TextHeader ) 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(Primary) + .clickable { + navController.navigate(Screen.VerifierSettingsHomeScreen.route) + } + ) { + Image( + painter = painterResource(id = R.drawable.user), + contentDescription = stringResource(id = R.string.user), + modifier = Modifier + .width(20.dp) + .height(20.dp) + ) + } } } @Composable diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerificationActivityLogScreen.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerificationActivityLogScreen.kt new file mode 100644 index 0000000..bff47cd --- /dev/null +++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerificationActivityLogScreen.kt @@ -0,0 +1,146 @@ +package com.spruceid.mobilesdkexample.verifiersettings + +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.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowLeft +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +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.CodeBorder +import com.spruceid.mobilesdkexample.ui.theme.Inter +import com.spruceid.mobilesdkexample.ui.theme.Primary +import com.spruceid.mobilesdkexample.ui.theme.SpruceBlue +import com.spruceid.mobilesdkexample.ui.theme.TextBody +import com.spruceid.mobilesdkexample.ui.theme.TextHeader +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 = TextHeader, + modifier = Modifier.padding(bottom = 4.dp), + ) + } + items(verificationActivityLogs) { log -> + Column { + Text( + text = log.name, + fontFamily = Inter, + fontWeight = FontWeight.SemiBold, + fontSize = 17.sp, + color = TextHeader, + modifier = Modifier.padding(bottom = 4.dp), + ) + Text( + text = log.credentialTitle, + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + color = TextBody, + 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 = TextBody, + 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 = TextBody, + 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 = CodeBorder, + 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 = SpruceBlue, + 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/VerifierSettingsHomeView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerifierSettingsHomeView.kt new file mode 100644 index 0000000..f140efc --- /dev/null +++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifiersettings/VerifierSettingsHomeView.kt @@ -0,0 +1,270 @@ +package com.spruceid.mobilesdkexample.verifiersettings + +import android.graphics.Bitmap +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.List +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +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.clip +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.scale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import com.spruceid.mobilesdkexample.R +import com.spruceid.mobilesdkexample.navigation.Screen +import com.spruceid.mobilesdkexample.ui.theme.Inter +import com.spruceid.mobilesdkexample.ui.theme.Primary +import com.spruceid.mobilesdkexample.ui.theme.TextBody +import com.spruceid.mobilesdkexample.ui.theme.TextHeader +import com.spruceid.mobilesdkexample.ui.theme.TextOnPrimary +import com.spruceid.mobilesdkexample.ui.theme.VerifierRequestBadgeBinaryBorder +import com.spruceid.mobilesdkexample.ui.theme.VerifierRequestBadgeBinaryFill +import com.spruceid.mobilesdkexample.ui.theme.VerifierRequestBadgeBinaryText +import com.spruceid.mobilesdkexample.ui.theme.VerifierRequestBadgeFieldBorder +import com.spruceid.mobilesdkexample.ui.theme.VerifierRequestBadgeFieldFill +import com.spruceid.mobilesdkexample.ui.theme.VerifierRequestBadgeFieldText + +enum class VerifierSubSettings { + VERIFICATION_ACTIVITY_LOG, +} + +@Composable +fun VerifierSettingsHomeView( + navController: NavController +) { + + var subpage by remember { + mutableStateOf(null) + } + + Column( + Modifier + .padding(all = 20.dp) + .padding(top = 20.dp) + ) { + VerifierSettingsHomeHeader( + onBack = { + if(subpage != null) { + subpage = null + } else { + navController.popBackStack() + } + } + ) + VerifierSettingsHomeBody( + subpage = subpage, + 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) + ) + Text( + text = "Verifier Settings", + fontFamily = Inter, + fontWeight = FontWeight.SemiBold, + fontSize = 24.sp, + color = TextHeader, + modifier = Modifier.padding(start = 10.dp) + ) + Spacer(Modifier.weight(1f)) + } + +} + +@Composable +fun VerifierSettingsHomeBody( + subpage: VerifierSubSettings?, + changeSubPage: (VerifierSubSettings?) -> Unit +) { + if(subpage == null) { + Column( + Modifier + .padding(horizontal = 20.dp) + .padding(top = 10.dp), + ) { + Box( + Modifier + .fillMaxWidth() + .clickable { + changeSubPage(VerifierSubSettings.VERIFICATION_ACTIVITY_LOG) + }, + ) { + Column { + 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 = TextBody, + 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) + ) + } + + Text( + text = "view and export verification history", + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + color = TextBody, + ) + } + } + } + } else if(subpage == VerifierSubSettings.VERIFICATION_ACTIVITY_LOG) { + VerificationActivityLogsScreen() + } +} + +@Composable +fun VerifierListItem( + title: String, + description: String, + binary: Boolean, + fields: Int, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier.padding(vertical = 12.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = title, + fontFamily = Inter, + fontWeight = FontWeight.SemiBold, + fontSize = 18.sp, + color = TextHeader, + modifier = Modifier.weight(2f) + ) + VerifierListItemTag(binary = binary, fields = fields) + Spacer(modifier = Modifier.weight(1f)) + Image( + painter = painterResource(id = R.drawable.arrow_right), + contentDescription = stringResource(id = R.string.arrow_right), + modifier = Modifier.width(24.dp) + ) + } + Text( + text = description, + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + color = TextBody, + ) + HorizontalDivider() + } +} + +@Composable +fun VerifierListItemTag( + binary: Boolean, + fields: Int +) { + if (binary) { + Text( + text = "Binary", + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 12.sp, + color = VerifierRequestBadgeBinaryText, + modifier = Modifier + .border( + width = 1.dp, + color = VerifierRequestBadgeBinaryBorder, + shape = RoundedCornerShape(8.dp) + ) + .clip(shape = RoundedCornerShape(8.dp, 8.dp, 8.dp, 8.dp)) + .background(VerifierRequestBadgeBinaryFill) + .padding(vertical = 2.dp) + .padding(horizontal = 8.dp), + ) + } else { + Text( + text = "$fields Fields", + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 12.sp, + color = VerifierRequestBadgeFieldText, + modifier = Modifier + .border( + width = 1.dp, + color = VerifierRequestBadgeFieldBorder, + shape = RoundedCornerShape(8.dp) + ) + .clip(shape = RoundedCornerShape(8.dp, 8.dp, 8.dp, 8.dp)) + .background(VerifierRequestBadgeFieldFill) + .padding(vertical = 2.dp) + .padding(horizontal = 8.dp), + ) + } +} \ 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 new file mode 100644 index 0000000..1401a43 --- /dev/null +++ b/example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/VerifierActivityLogsViewModel.kt @@ -0,0 +1,39 @@ +package com.spruceid.mobilesdkexample.viewmodels + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.spruceid.mobilesdkexample.db.VerificationActivityLogs +import com.spruceid.mobilesdkexample.db.VerificationActivityLogsRepository +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +class LogsViewModel(private val verificationActivityLogsRepository: VerificationActivityLogsRepository) : ViewModel() { + private val _verificationActivityLogs = MutableStateFlow(listOf()) + val verificationActivityLogs = _verificationActivityLogs.asStateFlow() + + init { + viewModelScope.launch { + _verificationActivityLogs.value = verificationActivityLogsRepository.verificationActivityLogs + } + } + + suspend fun saveVerificationActivityLog(verificationActivityLogs: VerificationActivityLogs) { + verificationActivityLogsRepository.insertVerificationActivityLog(verificationActivityLogs) + _verificationActivityLogs.value = 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}" + } + } +} + +class LogsViewModelFactory(private val repository: VerificationActivityLogsRepository) : ViewModelProvider.NewInstanceFactory() { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T = LogsViewModel(repository) as T +} diff --git a/example/src/main/res/drawable-hdpi/chevron.png b/example/src/main/res/drawable-hdpi/chevron.png new file mode 100644 index 0000000000000000000000000000000000000000..05da6ffac50cb4bf8e74d20f3a362e389bcefc74 GIT binary patch literal 661 zcmV;G0&4wQ|?8_U?Py-`dYw`$+%w?^ZDxWQQ8r{~ve|9!D~uFVKg|oX-b^wh%Z3c`fjbl@^;tbrzszg1#mq9J%Z3b5 z#H$qb^F?*B!*Mg$j4B(lG~b+6zailF{5@TKJ;XxW5D=*8Sw#WQG>6r1h-ANIHk+|! zLxwOl&1^8wRHr(o9+~}SO4*Ph@`oH+t7tXNtim9ndQi!x(0H@S+)?(fQE5ui6z8=}>`Yc9$VH*dDtYaXgLM8T8t zX`+wKGshGYW-&s)qyODCtIOi9#mUezn862CoB5u1FgA_Q5>A;VW?b3OrT7p6?Xv2Op^z!) z6N+?BnWcFMu2cNOq50aNgTGXq%v2@4%eb=K79zhr4L&wUht{NNyW-8&t``jMQq)zd vnEsk0DL*s3GlUI`c)K?fmG^XUUL@G00000NkvXXu0mjf|USJ9I+#+qEDORez%2R|TQ3LG2c9?7@HDr-I!jtTgLTHLr z1(_w+@H08dI3lI)8YAcM1eX~{gsNZzcMA)wAv_~fp((Gl@)=~W5$$3SR<=`DO(wYA1K+<3`GwfB-=Q;Z$eV1=XTACkB7t`5vG;K_a`aflsL1vD5WbT-M%p%jT z{!f`@ra5e0n&+m;TrjK5m`{JpD@)8R^RD28S*>ub*8l%Ip{!7xdT9Xsr?`pK?ljJ< zS5q_k&YGL%g{p@9YxbF6%+E^bQk?1lvsqz8X#f!9*Ub#m zH9pHF)B=2cs<~~R>2m|#DvbEc{9(GARv*4hB=~qwv(@~qsu9qnNVBsQ?#DN&1b4y+ zPWeme6^Iku=~vS$KFcK-LH-|Vj+(owy3-rQZJbe5a$1m7LXSjm^qbjfE~w1!es%sTVH zyw>Li@Eaq@V6#joC3Gj0!ljNYTJO>bpoVNWqZKztdQK$-DwP`}?yG75JXW|9g|S0}$^!w*-U;Q? zG*{tq~vr<#p_QV<5&iyk`anW4}6-eQYsFtMXJ7H1YW%3O7q5wTJFf>3S|Gx=9pI5v=vHP_dLPl?KK? zUTfHM(nh3knNpUi`hWp!p?Sfdq)3x#rO`t-TLrcYUq#*n;M_7g^y-SK00000NkvXX Hu0mjfJg(PD literal 0 HcmV?d00001 diff --git a/example/src/main/res/drawable-xxhdpi/chevron.png b/example/src/main/res/drawable-xxhdpi/chevron.png new file mode 100644 index 0000000000000000000000000000000000000000..a4ef7375868e70a9c3a3bc33fa7701f9af4a7370 GIT binary patch literal 1441 zcmV;S1z!4zP)*v5_p6?+ZX8zLy82+B2r4<;5OwkY=AmDmE}Aa*2Tz+Q;GVQhnCtbdg2 zm+u^M*(dwnkvzH1S@3%q&UrZBti9Ua`$~fiHrQbQUpq25K3EYv7~C7&8C)Bj9-O-I z(SF*Q!6*U~E(h2;38S5?UmRDfu>cc>#I{dxL~ym7_PtpLs4GYfp5-0hO>cuhi?kEukA6a3L27*1yt7O_utA_H zMi)=uJw^`1_o*vL$$}i3}J>)WCD+;Y&!mg1Dq5c4dx65g1$Jt2nB zngGjMY_>>XL~1<+2>xqOv!}Q5u?7m=uqIQ7#88pf^U1!$Am`CJ-LRm=}6u2-J4-i&69 zpzZ!&FBLGnSMiZT786X!<-IOBsb%xR$3 zHXs~(R`gV~3_$V6@=RC!4Yz$NM#IUK@1=4erl{k?CoL-i@u%|LdycChYJ12ao#(VS z4#_&iki}dxST8R@s$;w+<&8tq?-Kdw8)+QR!D}Y%b`o#cl~@&AEPKwZlK$WTA2-r) z>$z%I$bHpf+6Lmyy5JspXOy;KJ0-qVh)w%(lW|tbqY7eGZ;XsZOku)T3IfzV1Y_V9 v;R+?)$g9~JP5yBc{=TxImp_k_UZ8&gXE>>aLzhdB00000NkvXXu0mjfh?ua5 literal 0 HcmV?d00001 diff --git a/example/src/main/res/drawable-xxxhdpi/chevron.png b/example/src/main/res/drawable-xxxhdpi/chevron.png new file mode 100644 index 0000000000000000000000000000000000000000..2340bec49158d48e2182d62bdfde0d5b115a3432 GIT binary patch literal 1678 zcmV;9266d`P)X>nCAJ7Ev3JppqKIg)U^KCS1@(jFgC$BV`QoZ5*0^BQkl1S! zeQI3mALaRRXPEWeeYd<_b42C`zmIPhX6~DN&YU^BB0&%YK@j|J*fF`6x+HnTEDu)? zN-k1&uRazm6uTxT)m6zK)NhlYsxKs0swY+-3l@qR!Uq~4G`SKTK$rtTm?BkTiMTHH*LFCi#*2Q1W(_K9cpCq8zorw_!rP zLVSPlozw%~B+0PP(z+lC7~h5#A_0XVKvphjL$lFBdPF1vyOg6}#Cu9lCm&V!ld7e? zw*;;5ZLlZz8u2HZcS0WUCcm8ZSvy-eK!R2x0fnI)kpw&>d4WnPu{QNKsRulFMyis8 zZ1$c_t`%=-M>0CVnSp&9R*65+KT18|O@6MQ+bsUF=UWl95D6#@3-wYBkV33|cPYlkzuZ1X^Ok!v|8eA2>I*w#08z4|p#73Tt`XB;E>P{KZ-%aoW}aMi?iO$BQ@g z`fS>em%4qHa|&w`(l!rThy)adKye5|3z~x&oDx5<>GFjcsL^Dm12fRrddG^_h#eZhJoLh(l6ow6y-MTzfd~9XC_@=FEIkIsX%8`xH zkOUjAiE`A!&f-rlEogh9yIdBTE$;;T?0Moxp8Wivfk;4MC=pgJ7!YQl7atQJ_U)1k z%8?BdKQ2CGe60Az^PmMLwXMx?nt1!+J*fx0tJjI&gAQ7V1QdpHM7tRe6uU< z8lMNbNpAVu6|7)HmWm(Fydm` z<+Rj;+~gPH`-5OaA_3b8RzXZ+zQRHauRlo$|{P|hQS=L+>A5ffOdr*0Au14Yo{bnO`a*f#%JB02RWA)@{G!GuMO&6D*iI( z_XKmmguF~-y9K_s96Z2Om}9E#tnavvZM?l4J~O})J--j1exS;sUsSwmQe zOM2A$2h{T==tU%;0CY1Mw8guUPpVy3IVNT2V|7ZZ3mABk1g%Vp*Xh2uUd*^DKMx`S z+X?zOEdP*l!{!R9)i0`j>ON)XE%h0xmaQ279AXIUIBgxi5PvUdtN4-OatT_A1QdXB zGp^D|yIOrps>|0nka}=)DL2UJ)f4K2QqAz=VG_&#!jBHk;CKjb|G`uFzK$KQ@F zKqR05tai8>gp3BRRr&9EHg(perA&FUVcMI;FHo~|<$V(W5_bqZ_4aaPnQ7;?+oT@k zCh26_i<)JteUsQl2Mt653NR*Bxxve&x&nloq~DYpQ*GAMVOoL1cVrc1hw{N35SU4o z>!5jHyE-l2mWcl&)`LhuVb}<5P~Vq&V4Ec9wo*Dvyw>way$O5-zXOZK*Xdm4%!AzI zYvSj&ytsoAhy)adevI{My3A>A5dS{`ta5BdCuZ?Sgap`nzsm#zcemE4OC^{$k$@ij Y4O#xREY4t)WdHyG07*qoM6N<$f{#FFF7F7F5 #FFFDFDFC #FFE6E1D6 + #ff949494 #FF059669 #FF57534E #FF0C0A09 diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml index 689811e..f7e09e8 100644 --- a/example/src/main/res/values/strings.xml +++ b/example/src/main/res/values/strings.xml @@ -10,4 +10,6 @@ Arrow right Valid credential Invalid credential + Verification Activity Log + Start action \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34..b82aa23 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME