From 18ccb4b9ad6c40f5c177aa51d5b2061b34f1f8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juliano=20C=C3=A9zar=20Chagas=20Tavares?= Date: Tue, 1 Oct 2024 09:33:45 -0300 Subject: [PATCH] Delete individual credentials and rename app (#33) This adds the feature to delete individual credentials and renames the app to SpruceKit. --- .../com/spruceid/mobilesdkexample/db/Daos.kt | 3 + .../mobilesdkexample/db/Repositories.kt | 5 + .../viewmodels/RawCredentialsViewModel.kt | 8 + .../wallet/AchievementCredentialItem.kt | 206 ++++++++++++++---- .../mobilesdkexample/wallet/WalletHomeView.kt | 13 +- .../res/drawable/three_dots_horizontal.xml | 10 + example/src/main/res/values/strings.xml | 3 +- 7 files changed, 209 insertions(+), 39 deletions(-) create mode 100644 example/src/main/res/drawable/three_dots_horizontal.xml 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 1f11174..6482ba9 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/db/Daos.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/db/Daos.kt @@ -23,4 +23,7 @@ interface RawCredentialsDao { @Query("DELETE FROM raw_credentials") fun deleteAllRawCredentials(): Int + + @Query("DELETE FROM raw_credentials WHERE id = :id") + fun deleteRawCredential(id: Long): Int } 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 67048f6..62a92b5 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/db/Repositories.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/db/Repositories.kt @@ -33,4 +33,9 @@ class RawCredentialsRepository(private val rawCredentialsDao: RawCredentialsDao) suspend fun deleteAllRawCredentials(): Int { return rawCredentialsDao.deleteAllRawCredentials() } + + @WorkerThread + suspend fun deleteRawCredential(id: Long): Int { + return rawCredentialsDao.deleteRawCredential(id = id) + } } \ No newline at end of file diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/RawCredentialsViewModel.kt b/example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/RawCredentialsViewModel.kt index e759a44..a2bbf3f 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/RawCredentialsViewModel.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/viewmodels/RawCredentialsViewModel.kt @@ -14,6 +14,7 @@ abstract class IRawCredentialsViewModel : ViewModel(){ abstract val rawCredentials: StateFlow> abstract suspend fun saveRawCredential(rawCredential: RawCredentials) abstract suspend fun deleteAllRawCredentials() + abstract suspend fun deleteRawCredential(id: Long) abstract fun generateRawCredentialsCSV(): String } @@ -37,6 +38,11 @@ class RawCredentialsViewModel(private val rawCredentialsRepository: RawCredentia _rawCredentials.value = rawCredentialsRepository.getRawCredentials() } + override suspend fun deleteRawCredential(id: Long) { + rawCredentialsRepository.deleteRawCredential(id = id) + _rawCredentials.value = rawCredentialsRepository.getRawCredentials() + } + override fun generateRawCredentialsCSV(): String { val heading = "ID, Raw Credential\n" return heading + @@ -58,6 +64,8 @@ class RawCredentialsViewModelPreview(override val rawCredentials: StateFlow Unit)? - constructor(credential: JSONObject) { + constructor(credential: JSONObject, onDelete: (() -> Unit)? = null) { this.credential = credential + this.onDelete = onDelete } - constructor(rawCredential: String) { + constructor(rawCredential: String, onDelete: (() -> Unit)? = null) { val decodedSdJwt = decodeRevealSdJwt(rawCredential) this.credential = JSONObject(decodedSdJwt) + this.onDelete = onDelete } + @OptIn(ExperimentalMaterial3Api::class) @Composable - fun listComponent() { + private fun listComponentTitleWithOptions() { + val sheetState = rememberModalBottomSheetState() + val scope = rememberCoroutineScope() + var showBottomSheet by remember { mutableStateOf(false) } + + Column { + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + Image( + painter = painterResource(id = R.drawable.three_dots_horizontal), + contentDescription = stringResource(id = R.string.three_dots), + modifier = Modifier + .width(15.dp) + .height(12.dp) + .clickable { + showBottomSheet = true + } + ) + } + listComponentTitle() + } + if (showBottomSheet) { + ModalBottomSheet( + onDismissRequest = { + showBottomSheet = false + }, + sheetState = sheetState + ) { + Text( + text = "Credential Options", + textAlign = TextAlign.Center, + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 12.sp, + color = TextHeader, + modifier = Modifier + .fillMaxWidth() + ) + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + Button( + onClick = { + onDelete?.invoke() + }, + shape = RoundedCornerShape(5.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = SecondaryButtonRed, + ), + modifier = Modifier + .fillMaxWidth() + ) { + Text( + text = "Delete", + fontFamily = Inter, + fontWeight = FontWeight.Normal, + color = SecondaryButtonRed, + ) + } + + Button( + onClick = { + scope.launch { sheetState.hide() }.invokeOnCompletion { + if (!sheetState.isVisible) { + showBottomSheet = false + } + } + }, + shape = RoundedCornerShape(5.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = SpruceBlue, + ), + modifier = Modifier + .fillMaxWidth() + ) { + Text( + text = "Cancel", + fontFamily = Inter, + fontWeight = FontWeight.Bold, + color = SpruceBlue, + ) + } + } + } + } + + @Composable + private fun listComponentTitle() { val achievementName = keyPathFinder(credential, mutableListOf("name")).toString() + + Text( + text = achievementName, + fontFamily = Inter, + fontWeight = FontWeight.Medium, + fontSize = 22.sp, + color = TextHeader, + modifier = Modifier.padding(bottom = 8.dp) + ) + } + + @Composable + private fun listComponentDescription() { val issuerName = keyPathFinder(credential, mutableListOf("issuer", "name")).toString() + Column { + Text( + text = issuerName, + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + color = TextBody + ) + Spacer(modifier = Modifier.height(16.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + painter = painterResource(id = R.drawable.valid), + contentDescription = stringResource(id = R.string.valid), + modifier = Modifier.width(15.dp) + ) + Text( + text = "Valid", + fontFamily = Inter, + fontWeight = FontWeight.Medium, + fontSize = 10.sp, + color = GreenValid + ) + } + } + } + + @Composable + fun listComponent() { + Row( Modifier.height(intrinsicSize = IntrinsicSize.Max) ) { // Leading icon Column { // Title - Text( - text = achievementName, - fontFamily = Inter, - fontWeight = FontWeight.Medium, - fontSize = 22.sp, - color = TextHeader, - modifier = Modifier.padding(bottom = 8.dp) - ) + listComponentTitle() // Description - Column { - Text( - text = issuerName, - fontFamily = Inter, - fontWeight = FontWeight.Normal, - fontSize = 14.sp, - color = TextBody - ) - Spacer(modifier = Modifier.height(16.dp)) - Row(verticalAlignment = Alignment.CenterVertically) { - Image( - painter = painterResource(id = R.drawable.valid), - contentDescription = stringResource(id = R.string.valid), - modifier = Modifier.width(15.dp) - ) - Text( - text = "Valid", - fontFamily = Inter, - fontWeight = FontWeight.Medium, - fontSize = 10.sp, - color = GreenValid - ) - } - } + listComponentDescription() + } + Spacer(modifier = Modifier.weight(1.0f)) + // Trailing action button + } + } + + @Composable + fun listComponentWithOptions() { + + Row( + Modifier.height(intrinsicSize = IntrinsicSize.Max) + ) { + // Leading icon + Column { + // Title + listComponentTitleWithOptions() + + // Description + listComponentDescription() } Spacer(modifier = Modifier.weight(1.0f)) // Trailing action button @@ -187,8 +320,7 @@ class AchievementCredentialItem { sheetOpen = true } ) { -// GenericCredentialListItem(credentialPack = credentialPack) - listComponent() + listComponentWithOptions() } } if (sheetOpen) { diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/wallet/WalletHomeView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/wallet/WalletHomeView.kt index 5246a5d..36574e4 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/wallet/WalletHomeView.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/wallet/WalletHomeView.kt @@ -18,6 +18,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -33,6 +34,7 @@ import com.spruceid.mobilesdkexample.ui.theme.Inter import com.spruceid.mobilesdkexample.ui.theme.TextHeader import com.spruceid.mobilesdkexample.ui.theme.Primary import com.spruceid.mobilesdkexample.viewmodels.IRawCredentialsViewModel +import kotlinx.coroutines.launch @Composable fun WalletHomeView( @@ -85,6 +87,8 @@ fun WalletHomeHeader(navController: NavController) { @Composable fun WalletHomeBody(rawCredentialsViewModel: IRawCredentialsViewModel) { + val scope = rememberCoroutineScope() + val rawCredentials by rawCredentialsViewModel.rawCredentials.collectAsState() if(rawCredentials.isNotEmpty()) { @@ -94,7 +98,14 @@ fun WalletHomeBody(rawCredentialsViewModel: IRawCredentialsViewModel) { .padding(top = 20.dp) ) { items(rawCredentials) { rawCredential -> - AchievementCredentialItem(rawCredential.rawCredential).component() + AchievementCredentialItem( + rawCredential.rawCredential, + onDelete = { + scope.launch { + rawCredentialsViewModel.deleteRawCredential(id = rawCredential.id) + } + } + ).component() } // item { // vcs.map { vc -> diff --git a/example/src/main/res/drawable/three_dots_horizontal.xml b/example/src/main/res/drawable/three_dots_horizontal.xml new file mode 100644 index 0000000..dcdc4e3 --- /dev/null +++ b/example/src/main/res/drawable/three_dots_horizontal.xml @@ -0,0 +1,10 @@ + + + diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml index e2a0c6e..a3db3b1 100644 --- a/example/src/main/res/values/strings.xml +++ b/example/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - SpruceKit Reference App + SpruceKit User profile Scan QR Code Valid @@ -13,4 +13,5 @@ Verification Activity Log Start action No credentials added yet + More options \ No newline at end of file