Skip to content

Commit

Permalink
Delete individual credentials and rename app (#33)
Browse files Browse the repository at this point in the history
This adds the feature to delete individual credentials and renames the app to SpruceKit.
  • Loading branch information
Juliano1612 authored Oct 1, 2024
1 parent 0090888 commit 18ccb4b
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ abstract class IRawCredentialsViewModel : ViewModel(){
abstract val rawCredentials: StateFlow<List<RawCredentials>>
abstract suspend fun saveRawCredential(rawCredential: RawCredentials)
abstract suspend fun deleteAllRawCredentials()
abstract suspend fun deleteRawCredential(id: Long)
abstract fun generateRawCredentialsCSV(): String
}

Expand All @@ -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 +
Expand All @@ -58,6 +64,8 @@ class RawCredentialsViewModelPreview(override val rawCredentials: StateFlow<List

override suspend fun deleteAllRawCredentials() {}

override suspend fun deleteRawCredential(id: Long) {}

override fun generateRawCredentialsCSV(): String {
return ""
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.spruceid.mobilesdkexample.wallet
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.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
Expand All @@ -16,17 +17,22 @@ 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.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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
Expand All @@ -39,70 +45,197 @@ import com.spruceid.mobilesdkexample.ui.theme.Bg
import com.spruceid.mobilesdkexample.ui.theme.CredentialBorder
import com.spruceid.mobilesdkexample.ui.theme.GreenValid
import com.spruceid.mobilesdkexample.ui.theme.Inter
import com.spruceid.mobilesdkexample.ui.theme.SecondaryButtonRed
import com.spruceid.mobilesdkexample.ui.theme.SpruceBlue
import com.spruceid.mobilesdkexample.ui.theme.TextBody
import com.spruceid.mobilesdkexample.ui.theme.TextHeader
import kotlinx.coroutines.launch
import org.json.JSONArray
import org.json.JSONObject
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter

class AchievementCredentialItem {
private var credential: JSONObject
private val onDelete: (() -> 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
Expand Down Expand Up @@ -187,8 +320,7 @@ class AchievementCredentialItem {
sheetOpen = true
}
) {
// GenericCredentialListItem(credentialPack = credentialPack)
listComponent()
listComponentWithOptions()
}
}
if (sheetOpen) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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()) {
Expand All @@ -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 ->
Expand Down
10 changes: 10 additions & 0 deletions example/src/main/res/drawable/three_dots_horizontal.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="10dp"
android:height="2dp"
android:viewportWidth="10"
android:viewportHeight="2">
<path
android:pathData="M2,1C2,1.552 1.552,2 1,2C0.448,2 0,1.552 0,1C0,0.448 0.448,0 1,0C1.552,0 2,0.448 2,1ZM10,1C10,1.552 9.552,2 9,2C8.448,2 8,1.552 8,1C8,0.448 8.448,0 9,0C9.552,0 10,0.448 10,1ZM5,2C5.552,2 6,1.552 6,1C6,0.448 5.552,0 5,0C4.448,0 4,0.448 4,1C4,1.552 4.448,2 5,2Z"
android:fillColor="#A8A29E"
android:fillType="evenOdd"/>
</vector>
3 changes: 2 additions & 1 deletion example/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<resources>
<string name="app_name">SpruceKit Reference App</string>
<string name="app_name">SpruceKit</string>
<string name="user">User profile</string>
<string name="scan_qr_code">Scan QR Code</string>
<string name="valid">Valid</string>
Expand All @@ -13,4 +13,5 @@
<string name="verification_activity_log">Verification Activity Log</string>
<string name="chevron">Start action</string>
<string name="empty_wallet">No credentials added yet</string>
<string name="three_dots">More options</string>
</resources>

0 comments on commit 18ccb4b

Please sign in to comment.