Skip to content

Commit

Permalink
[Android][iOS] Holder can export the credentials in a machine+human r…
Browse files Browse the repository at this point in the history
…eadable format (#59)

This implements a way for a credential holder to export their credentials from their wallet as `.json`.
  • Loading branch information
Juliano1612 authored Dec 12, 2024
1 parent 8cdc376 commit f114d3e
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import com.spruceid.mobilesdkexample.ui.theme.ColorBlue900
import com.spruceid.mobilesdkexample.ui.theme.Switzer
import com.spruceid.mobilesdkexample.verifier.VerifierHomeView
import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModel
import com.spruceid.mobilesdkexample.viewmodels.HelpersViewModel
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModel
import com.spruceid.mobilesdkexample.wallet.WalletHomeView

Expand All @@ -53,7 +54,8 @@ fun HomeView(
navController: NavController,
initialTab: String,
verificationMethodsViewModel: VerificationMethodsViewModel,
credentialPacksViewModel: CredentialPacksViewModel
credentialPacksViewModel: CredentialPacksViewModel,
helpersViewModel: HelpersViewModel
) {
var tab by remember {
if (initialTab == "verifier") {
Expand All @@ -76,7 +78,8 @@ fun HomeView(
if (tab == HomeTabs.WALLET) {
WalletHomeView(
navController,
credentialPacksViewModel = credentialPacksViewModel
credentialPacksViewModel = credentialPacksViewModel,
helpersViewModel = helpersViewModel
)
} else {
VerifierHomeView(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ fun AddToWalletView(
val scope = rememberCoroutineScope()

LaunchedEffect(Unit) {
credentialItem = credentialDisplaySelector(rawCredential, null)
credentialItem = credentialDisplaySelector(rawCredential, null, null)
}

fun back() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.spruceid.mobilesdkexample.credentials

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
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.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.ui.theme.ColorBlue600
import com.spruceid.mobilesdkexample.ui.theme.ColorRose600
import com.spruceid.mobilesdkexample.ui.theme.ColorStone950
import com.spruceid.mobilesdkexample.ui.theme.Inter
import kotlinx.coroutines.launch

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CredentialOptionsDialogActions(
setShowBottomSheet: (Boolean) -> Unit,
onExport: (() -> Unit)?,
onDelete: (() -> Unit)?
) {
val scope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState()

ModalBottomSheet(
onDismissRequest = {
setShowBottomSheet(false)
},
sheetState = sheetState,
modifier = Modifier.navigationBarsPadding()
) {
Text(
text = "Credential Options",
textAlign = TextAlign.Center,
fontFamily = Inter,
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
color = ColorStone950,
modifier = Modifier
.fillMaxWidth()
)
if (onExport != null) {
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
Button(
onClick = {
setShowBottomSheet(false)
onExport()
},
shape = RoundedCornerShape(5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = ColorBlue600,
),
modifier = Modifier
.fillMaxWidth()
) {
Text(
text = "Export",
fontFamily = Inter,
fontWeight = FontWeight.Normal,
color = ColorBlue600,
)
}
}
if (onDelete != null) {
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
Button(
onClick = {
setShowBottomSheet(false)
onDelete()
},
shape = RoundedCornerShape(5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = ColorRose600,
),
modifier = Modifier
.fillMaxWidth()
) {
Text(
text = "Delete",
fontFamily = Inter,
fontWeight = FontWeight.Normal,
color = ColorRose600,
)
}
}

Button(
onClick = {
scope.launch { sheetState.hide() }.invokeOnCompletion {
if (!sheetState.isVisible) {
setShowBottomSheet(false)
}
}
},
shape = RoundedCornerShape(5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = ColorBlue600,
),
modifier = Modifier
.fillMaxWidth()
) {
Text(
text = "Cancel",
fontFamily = Inter,
fontWeight = FontWeight.Bold,
color = ColorBlue600,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,20 @@ import androidx.compose.foundation.layout.fillMaxHeight
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.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
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.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.painterResource
Expand All @@ -49,28 +43,36 @@ import com.spruceid.mobile.sdk.ui.toCardRendering
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.ColorBlue600
import com.spruceid.mobilesdkexample.ui.theme.ColorRose600
import com.spruceid.mobilesdkexample.ui.theme.ColorStone600
import com.spruceid.mobilesdkexample.ui.theme.ColorStone950
import com.spruceid.mobilesdkexample.ui.theme.Inter
import com.spruceid.mobilesdkexample.utils.addCredential
import com.spruceid.mobilesdkexample.utils.splitCamelCase
import kotlinx.coroutines.launch
import org.json.JSONObject

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

constructor(credentialPack: CredentialPack, onDelete: (() -> Unit)? = null) {
constructor(
credentialPack: CredentialPack,
onDelete: (() -> Unit)? = null,
onExport: ((String) -> Unit)? = null
) {
this.credentialPack = credentialPack
this.onDelete = onDelete
this.onExport = onExport
}

constructor(rawCredential: String, onDelete: (() -> Unit)? = null) {
constructor(
rawCredential: String,
onDelete: (() -> Unit)? = null,
onExport: ((String) -> Unit)? = null
) {
this.credentialPack = addCredential(CredentialPack(), rawCredential)
this.onDelete = onDelete
this.onExport = onExport
}

@Composable
Expand Down Expand Up @@ -236,11 +238,8 @@ class GenericCredentialItem : ICredentialView {
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun listItemWithOptions() {
val sheetState = rememberModalBottomSheetState()
val scope = rememberCoroutineScope()
var showBottomSheet by remember { mutableStateOf(false) }

val listRendering = CardRenderingListView(
Expand Down Expand Up @@ -303,6 +302,17 @@ class GenericCredentialItem : ICredentialView {
color = ColorStone950,
modifier = Modifier.padding(bottom = 8.dp)
)
if (showBottomSheet) {
CredentialOptionsDialogActions(
setShowBottomSheet = { show ->
showBottomSheet = show
},
onDelete = onDelete,
onExport = {
onExport?.let { it(title) }
}
)
}
}
},
descriptionKeys = listOf("description", "issuer"),
Expand All @@ -319,72 +329,6 @@ class GenericCredentialItem : ICredentialView {
credentialPack = credentialPack,
rendering = listRendering.toCardRendering()
)

if (showBottomSheet) {
ModalBottomSheet(
onDismissRequest = {
showBottomSheet = false
},
sheetState = sheetState,
modifier = Modifier.navigationBarsPadding()
) {
Text(
text = "Credential Options",
textAlign = TextAlign.Center,
fontFamily = Inter,
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
color = ColorStone950,
modifier = Modifier
.fillMaxWidth()
)
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
Button(
onClick = {
showBottomSheet = false
onDelete?.invoke()
},
shape = RoundedCornerShape(5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = ColorRose600,
),
modifier = Modifier
.fillMaxWidth()
) {
Text(
text = "Delete",
fontFamily = Inter,
fontWeight = FontWeight.Normal,
color = ColorRose600,
)
}

Button(
onClick = {
scope.launch { sheetState.hide() }.invokeOnCompletion {
if (!sheetState.isVisible) {
showBottomSheet = false
}
}
},
shape = RoundedCornerShape(5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = ColorBlue600,
),
modifier = Modifier
.fillMaxWidth()
) {
Text(
text = "Cancel",
fontFamily = Inter,
fontWeight = FontWeight.Bold,
color = ColorBlue600,
)
}
}
}
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ fun SetupNavGraph(
navController,
initialTab = tab,
verificationMethodsViewModel = verificationMethodsViewModel,
credentialPacksViewModel = credentialPacksViewModel
credentialPacksViewModel = credentialPacksViewModel,
helpersViewModel = helpersViewModel
)
}
composable(
Expand Down
Loading

0 comments on commit f114d3e

Please sign in to comment.