diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/MainActivity.kt b/example/src/main/java/com/spruceid/mobilesdkexample/MainActivity.kt index aff80c7..d7424db 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/MainActivity.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/MainActivity.kt @@ -23,6 +23,7 @@ import com.spruceid.mobilesdkexample.navigation.Screen import com.spruceid.mobilesdkexample.navigation.SetupNavGraph import com.spruceid.mobilesdkexample.ui.theme.ColorBase1 import com.spruceid.mobilesdkexample.ui.theme.MobileSdkTheme +import com.spruceid.mobilesdkexample.utils.Toast import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModel import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModelFactory import com.spruceid.mobilesdkexample.viewmodels.HelpersViewModel @@ -118,6 +119,8 @@ class MainActivity : ComponentActivity() { helpersViewModel = helpersViewModel ) } + // Global Toast Host + Toast.ToastHost() } } } diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/utils/Toast.kt b/example/src/main/java/com/spruceid/mobilesdkexample/utils/Toast.kt new file mode 100644 index 0000000..451ad75 --- /dev/null +++ b/example/src/main/java/com/spruceid/mobilesdkexample/utils/Toast.kt @@ -0,0 +1,114 @@ +package com.spruceid.mobilesdkexample.utils + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +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.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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 com.spruceid.mobilesdkexample.R +import com.spruceid.mobilesdkexample.ui.theme.ColorEmerald200 +import com.spruceid.mobilesdkexample.ui.theme.ColorEmerald50 +import com.spruceid.mobilesdkexample.ui.theme.ColorEmerald900 +import com.spruceid.mobilesdkexample.ui.theme.Inter +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +enum class ToastType { + SUCCESS +} + +object Toast { + private val toastMessage = mutableStateOf(null) + private val toastType = mutableStateOf(ToastType.SUCCESS) + + fun showSuccess(message: String) { + toastType.value = ToastType.SUCCESS + toastMessage.value = message + } + + @Composable + fun ToastHost( + duration: Long = 3000L, + onDismiss: () -> Unit = { toastMessage.value = null } + ) { + val scope = rememberCoroutineScope() + val currentMessage = rememberUpdatedState(toastMessage.value) + + currentMessage.value?.let { message -> + LaunchedEffect(message) { + scope.launch { + delay(duration) + onDismiss() + } + } + when (toastType.value) { + ToastType.SUCCESS -> SuccessToast(message = message) + } + } + } +} + +@Composable +fun SuccessToast( + message: String, +) { + Box( + modifier = Modifier + .wrapContentHeight() + .padding(all = 20.dp) + .padding(top = 20.dp) + .background( + color = ColorEmerald50, + shape = RoundedCornerShape(6.dp) + ) + .border( + width = 1.dp, + color = ColorEmerald200, + shape = RoundedCornerShape(6.dp) + ) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + ) { + Image( + painter = painterResource(id = R.drawable.success_toast_icon), + contentDescription = stringResource(id = R.string.success_toast_icon), + modifier = Modifier + .width(20.dp) + .height(20.dp) + ) + Text( + text = message, + color = ColorEmerald900, + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 15.sp, + modifier = Modifier.padding(start = 10.dp) + ) + } + } +} \ No newline at end of file diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/AddVerificationMethodView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/AddVerificationMethodView.kt index dd7ad09..57e10d7 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/AddVerificationMethodView.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/AddVerificationMethodView.kt @@ -44,11 +44,12 @@ fun AddVerificationMethodView( val jsonArray = JSONArray(content) for (i in 0 until jsonArray.length()) { val json = jsonArray.getJSONObject(i) + val credentialName = json.getString("credential_name") verificationMethodsViewModel.saveVerificationMethod( VerificationMethods( type = json.getString("type"), - name = json.getString("name"), - description = json.getString("description"), + name = credentialName, + description = "Verifies $credentialName Credentials", verifierName = json.getString("verifier_name"), url = json.getString("url") ) 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 ea2d890..c7b7fd5 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierHomeView.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/verifier/VerifierHomeView.kt @@ -176,7 +176,7 @@ fun VerifierHomeBody( } items(verificationMethods.value) { verificationMethod -> VerifierListItem( - title = verificationMethod.verifierName, + title = verificationMethod.name, description = verificationMethod.description, type = getBadgeType(verificationMethod.type), modifier = Modifier.clickable { diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/wallet/HandleOID4VPView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/wallet/HandleOID4VPView.kt index 3c1d695..292ec21 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/wallet/HandleOID4VPView.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/wallet/HandleOID4VPView.kt @@ -65,6 +65,7 @@ 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 com.spruceid.mobilesdkexample.utils.Toast import com.spruceid.mobilesdkexample.utils.getCredentialIdTitleAndIssuer import com.spruceid.mobilesdkexample.utils.getCurrentSqlDate import com.spruceid.mobilesdkexample.utils.trustedDids @@ -88,8 +89,8 @@ class Signer(keyId: String?) : PresentationSigner { override suspend fun sign(payload: ByteArray): ByteArray { val signature = - keyManager.signPayload(keyId, payload) - ?: throw IllegalStateException("Failed to sign payload") + keyManager.signPayload(keyId, payload) + ?: throw IllegalStateException("Failed to sign payload") return signature } @@ -124,10 +125,10 @@ class Signer(keyId: String?) : PresentationSigner { @Composable fun HandleOID4VPView( - navController: NavController, - url: String, - credentialPacksViewModel: CredentialPacksViewModel, - walletActivityLogsViewModel: WalletActivityLogsViewModel + navController: NavController, + url: String, + credentialPacksViewModel: CredentialPacksViewModel, + walletActivityLogsViewModel: WalletActivityLogsViewModel ) { val scope = rememberCoroutineScope() val credentialPacks = credentialPacksViewModel.credentialPacks @@ -152,12 +153,12 @@ fun HandleOID4VPView( withContext(Dispatchers.IO) { val signer = Signer("reference-app/default-signing") holder = - Holder.newWithCredentials( - credentials, - trustedDids, - signer, - getVCPlaygroundOID4VCIContext(ctx) - ) + Holder.newWithCredentials( + credentials, + trustedDids, + signer, + getVCPlaygroundOID4VCIContext(ctx) + ) val newurl = url.replace("authorize", "") val tempPermissionRequest = holder!!.authorizationRequest(newurl) val permissionRequestCredentials = tempPermissionRequest.credentials() @@ -183,9 +184,9 @@ fun HandleOID4VPView( if (err != null) { ErrorView( - errorTitle = "Error Presenting Credential", - errorDetails = err!!, - onClose = { navController.navigate(Screen.HomeScreen.route) { popUpTo(0) } } + errorTitle = "Error Presenting Credential", + errorDetails = err!!, + onClose = { navController.navigate(Screen.HomeScreen.route) { popUpTo(0) } } ) } else { if (permissionRequest == null) { @@ -193,69 +194,70 @@ fun HandleOID4VPView( } else if (permissionResponse == null) { if (permissionRequest!!.credentials().isNotEmpty()) { CredentialSelector( - credentials = permissionRequest!!.credentials(), - credentialClaims = credentialClaims, - getRequestedFields = { credential -> - permissionRequest!!.requestedFields(credential) - }, - onContinue = { selectedCredentials -> - scope.launch { - try { - // TODO: support multiple presentation - selectedCredential = selectedCredentials.first() - permissionResponse = - permissionRequest!!.createPermissionResponse( - selectedCredentials - ) - } catch (e: Exception) { - err = e.localizedMessage - } + credentials = permissionRequest!!.credentials(), + credentialClaims = credentialClaims, + getRequestedFields = { credential -> + permissionRequest!!.requestedFields(credential) + }, + onContinue = { selectedCredentials -> + scope.launch { + try { + // TODO: support multiple presentation + selectedCredential = selectedCredentials.first() + permissionResponse = + permissionRequest!!.createPermissionResponse( + selectedCredentials + ) + } catch (e: Exception) { + err = e.localizedMessage } - }, - onCancel = { onBack() } + } + }, + onCancel = { onBack() } ) } else { ErrorView( - errorTitle = "No matching credential(s)", - errorDetails = - "There are no credentials in your wallet that match the verification request you have scanned", - closeButtonLabel = "Cancel" + errorTitle = "No matching credential(s)", + errorDetails = + "There are no credentials in your wallet that match the verification request you have scanned", + closeButtonLabel = "Cancel" ) { onBack() } } } else { DataFieldSelector( - requestedFields = permissionRequest!!.requestedFields(selectedCredential!!), - onContinue = { - scope.launch { - try { - holder!!.submitPermissionResponse(permissionResponse!!) - val credentialPack = - credentialPacks.value.firstOrNull { credentialPack -> - credentialPack.getCredentialById( - selectedCredential!!.id() - ) != null - }!! - val credentialInfo = getCredentialIdTitleAndIssuer(credentialPack) - walletActivityLogsViewModel.saveWalletActivityLog( - walletActivityLogs = - WalletActivityLogs( - credentialPackId = - credentialPack.id().toString(), - credentialId = credentialInfo.first, - credentialTitle = credentialInfo.second, - issuer = credentialInfo.third, - action = "Verification", - dateTime = getCurrentSqlDate(), - additionalInformation = "" - ) + requestedFields = permissionRequest!!.requestedFields(selectedCredential!!), + onContinue = { + scope.launch { + try { + holder!!.submitPermissionResponse(permissionResponse!!) + val credentialPack = + credentialPacks.value.firstOrNull { credentialPack -> + credentialPack.getCredentialById( + selectedCredential!!.id() + ) != null + }!! + val credentialInfo = getCredentialIdTitleAndIssuer(credentialPack) + walletActivityLogsViewModel.saveWalletActivityLog( + walletActivityLogs = + WalletActivityLogs( + credentialPackId = + credentialPack.id().toString(), + credentialId = credentialInfo.first, + credentialTitle = credentialInfo.second, + issuer = credentialInfo.third, + action = "Verification", + dateTime = getCurrentSqlDate(), + additionalInformation = "" ) - onBack() - } catch (e: Exception) { - err = e.localizedMessage - } + ) + Toast.showSuccess("Shared successfully") + onBack() + } catch (e: Exception) { + err = e.localizedMessage } - }, - onCancel = { onBack() } + } + }, + onCancel = { onBack() } ) } } @@ -263,96 +265,107 @@ fun HandleOID4VPView( @Composable fun DataFieldSelector( - requestedFields: List, - onContinue: () -> Unit, - onCancel: () -> Unit + requestedFields: List, + onContinue: () -> Unit, + onCancel: () -> Unit ) { val bullet = "\u2022" val paragraphStyle = ParagraphStyle(textIndent = TextIndent(restLine = 12.sp)) val mockDataField = - requestedFields.map { field -> field.name()?.replaceFirstChar(Char::titlecase) ?: "" } + requestedFields.map { field -> field.name()?.replaceFirstChar(Char::titlecase) ?: "" } - Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp).padding(top = 48.dp)) { + Column(modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .padding(top = 48.dp)) { Text( - buildAnnotatedString { - withStyle(style = SpanStyle(color = Color.Blue)) { append("Verifier") } - append(" is requesting access to the following information") - }, - fontFamily = Inter, - fontWeight = FontWeight.Bold, - fontSize = 20.sp, - color = ColorStone950, - modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), - textAlign = TextAlign.Center + buildAnnotatedString { + withStyle(style = SpanStyle(color = Color.Blue)) { append("Verifier") } + append(" is requesting access to the following information") + }, + fontFamily = Inter, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + color = ColorStone950, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp), + textAlign = TextAlign.Center ) Column( - modifier = - Modifier.fillMaxSize() - .verticalScroll(rememberScrollState()) - .weight(weight = 1f, fill = false) + modifier = + Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .weight(weight = 1f, fill = false) ) { Text( - buildAnnotatedString { - mockDataField.forEach { - withStyle(style = paragraphStyle) { - append(bullet) - append("\t\t") - append(it) - } + buildAnnotatedString { + mockDataField.forEach { + withStyle(style = paragraphStyle) { + append(bullet) + append("\t\t") + append(it) } - }, + } + }, ) } Row( - modifier = - Modifier.fillMaxWidth().padding(vertical = 12.dp).navigationBarsPadding(), - horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 12.dp) + .navigationBarsPadding(), + horizontalArrangement = Arrangement.spacedBy(8.dp), ) { Button( - onClick = { onCancel() }, - shape = RoundedCornerShape(6.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = Color.Transparent, - contentColor = ColorStone950, - ), - modifier = - Modifier.fillMaxWidth() - .border( - width = 1.dp, - color = ColorStone300, - shape = RoundedCornerShape(6.dp) - ) - .weight(1f) + onClick = { onCancel() }, + shape = RoundedCornerShape(6.dp), + colors = + ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = ColorStone950, + ), + modifier = + Modifier + .fillMaxWidth() + .border( + width = 1.dp, + color = ColorStone300, + shape = RoundedCornerShape(6.dp) + ) + .weight(1f) ) { Text( - text = "Cancel", - fontFamily = Inter, - fontWeight = FontWeight.SemiBold, - color = ColorStone950, + text = "Cancel", + fontFamily = Inter, + fontWeight = FontWeight.SemiBold, + color = ColorStone950, ) } Button( - onClick = { onContinue() }, - shape = RoundedCornerShape(6.dp), - colors = ButtonDefaults.buttonColors(containerColor = ColorEmerald900), - modifier = - Modifier.fillMaxWidth() - .background( - color = ColorEmerald900, - shape = RoundedCornerShape(6.dp), - ) - .weight(1f) + onClick = { onContinue() }, + shape = RoundedCornerShape(6.dp), + colors = ButtonDefaults.buttonColors(containerColor = ColorEmerald900), + modifier = + Modifier + .fillMaxWidth() + .background( + color = ColorEmerald900, + shape = RoundedCornerShape(6.dp), + ) + .weight(1f) ) { Text( - text = "Approve", - fontFamily = Inter, - fontWeight = FontWeight.SemiBold, - color = ColorBase50, + text = "Approve", + fontFamily = Inter, + fontWeight = FontWeight.SemiBold, + color = ColorBase50, ) } } @@ -361,12 +374,12 @@ fun DataFieldSelector( @Composable fun CredentialSelector( - credentials: List, - credentialClaims: Map, - getRequestedFields: (ParsedCredential) -> List, - onContinue: (List) -> Unit, - onCancel: () -> Unit, - allowMultiple: Boolean = false + credentials: List, + credentialClaims: Map, + getRequestedFields: (ParsedCredential) -> List, + onContinue: (List) -> Unit, + onCancel: () -> Unit, + allowMultiple: Boolean = false ) { val selectedCredentials = remember { mutableStateListOf() } @@ -388,7 +401,8 @@ fun CredentialSelector( credentialClaims[credential.id()]?.getString("name").let { return it.toString() } - } catch (_: Exception) {} + } catch (_: Exception) { + } try { credentialClaims[credential.id()]?.getJSONArray("type").let { @@ -399,117 +413,129 @@ fun CredentialSelector( } return "" } - } catch (_: Exception) {} + } catch (_: Exception) { + } return "" } - Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp).padding(top = 48.dp)) { + Column(modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .padding(top = 48.dp)) { Text( - text = "Select the credential${if (allowMultiple) "(s)" else ""} to share", - fontFamily = Inter, - fontWeight = FontWeight.Bold, - fontSize = 20.sp, - color = ColorStone950, - modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), - textAlign = TextAlign.Center + text = "Select the credential${if (allowMultiple) "(s)" else ""} to share", + fontFamily = Inter, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + color = ColorStone950, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp), + textAlign = TextAlign.Center ) if (allowMultiple) { Text( - text = "Select All", - fontFamily = Inter, - fontWeight = FontWeight.Normal, - fontSize = 15.sp, - color = ColorBlue600, - modifier = - Modifier.clickable { - // TODO: implement select all - } + text = "Select All", + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 15.sp, + color = ColorBlue600, + modifier = + Modifier.clickable { + // TODO: implement select all + } ) } Column( - modifier = - Modifier.fillMaxSize() - .verticalScroll(rememberScrollState()) - .weight(weight = 1f, fill = false) + modifier = + Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .weight(weight = 1f, fill = false) ) { credentials.forEach { credential -> CredentialSelectorItem( - credential = credential, - requestedFields = getRequestedFields(credential), - getCredentialTitle = { cred -> getCredentialTitle(cred) }, - isChecked = credential in selectedCredentials, - selectCredential = { cred -> selectCredential(cred) }, - removeCredential = { cred -> removeCredential(cred) }, + credential = credential, + requestedFields = getRequestedFields(credential), + getCredentialTitle = { cred -> getCredentialTitle(cred) }, + isChecked = credential in selectedCredentials, + selectCredential = { cred -> selectCredential(cred) }, + removeCredential = { cred -> removeCredential(cred) }, ) } } Row( - modifier = - Modifier.fillMaxWidth().padding(vertical = 12.dp).navigationBarsPadding(), - horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 12.dp) + .navigationBarsPadding(), + horizontalArrangement = Arrangement.spacedBy(8.dp), ) { Button( - onClick = { onCancel() }, - shape = RoundedCornerShape(6.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = Color.Transparent, - contentColor = ColorStone950, - ), - modifier = - Modifier.fillMaxWidth() - .border( - width = 1.dp, - color = ColorStone300, - shape = RoundedCornerShape(6.dp) - ) - .weight(1f) + onClick = { onCancel() }, + shape = RoundedCornerShape(6.dp), + colors = + ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = ColorStone950, + ), + modifier = + Modifier + .fillMaxWidth() + .border( + width = 1.dp, + color = ColorStone300, + shape = RoundedCornerShape(6.dp) + ) + .weight(1f) ) { Text( - text = "Cancel", - fontFamily = Inter, - fontWeight = FontWeight.SemiBold, - color = ColorStone950, + text = "Cancel", + fontFamily = Inter, + fontWeight = FontWeight.SemiBold, + color = ColorStone950, ) } Button( - onClick = { + onClick = { + if (selectedCredentials.isNotEmpty()) { + onContinue(selectedCredentials) + } + }, + shape = RoundedCornerShape(6.dp), + colors = + ButtonDefaults.buttonColors( + containerColor = + if (selectedCredentials.isNotEmpty()) { + ColorStone600 + } else { + Color.Gray + } + ), + modifier = + Modifier + .fillMaxWidth() + .background( + color = if (selectedCredentials.isNotEmpty()) { - onContinue(selectedCredentials) - } - }, - shape = RoundedCornerShape(6.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = - if (selectedCredentials.isNotEmpty()) { - ColorStone600 - } else { - Color.Gray - } - ), - modifier = - Modifier.fillMaxWidth() - .background( - color = - if (selectedCredentials.isNotEmpty()) { - ColorStone600 - } else { - Color.Gray - }, - shape = RoundedCornerShape(6.dp), - ) - .weight(1f) + ColorStone600 + } else { + Color.Gray + }, + shape = RoundedCornerShape(6.dp), + ) + .weight(1f) ) { Text( - text = "Continue", - fontFamily = Inter, - fontWeight = FontWeight.SemiBold, - color = ColorBase50, + text = "Continue", + fontFamily = Inter, + fontWeight = FontWeight.SemiBold, + color = ColorBase50, ) } } @@ -518,84 +544,88 @@ fun CredentialSelector( @Composable fun CredentialSelectorItem( - credential: ParsedCredential, - requestedFields: List, - getCredentialTitle: (ParsedCredential) -> String, - isChecked: Boolean, - selectCredential: (ParsedCredential) -> Unit, - removeCredential: (ParsedCredential) -> Unit + credential: ParsedCredential, + requestedFields: List, + getCredentialTitle: (ParsedCredential) -> String, + isChecked: Boolean, + selectCredential: (ParsedCredential) -> Unit, + removeCredential: (ParsedCredential) -> Unit ) { var expanded by remember { mutableStateOf(false) } val bullet = "\u2022" val paragraphStyle = ParagraphStyle(textIndent = TextIndent(restLine = 12.sp)) val mockDataField = - requestedFields.map { field -> field.name()?.replaceFirstChar(Char::titlecase) ?: "" } + requestedFields.map { field -> field.name()?.replaceFirstChar(Char::titlecase) ?: "" } Column( - modifier = - Modifier.fillMaxWidth() - .padding(vertical = 8.dp) - .border( - width = 1.dp, - color = ColorBase300, - shape = RoundedCornerShape(8.dp) - ) + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + .border( + width = 1.dp, + color = ColorBase300, + shape = RoundedCornerShape(8.dp) + ) ) { Row( - modifier = Modifier.fillMaxWidth().padding(end = 8.dp).padding(vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically + modifier = Modifier + .fillMaxWidth() + .padding(end = 8.dp) + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically ) { Checkbox( - checked = isChecked, - onCheckedChange = { isChecked -> - if (isChecked) { - selectCredential(credential) - } else { - removeCredential(credential) - } - }, - colors = - CheckboxDefaults.colors( - checkedColor = ColorBlue600, - uncheckedColor = ColorStone300 - ) + checked = isChecked, + onCheckedChange = { isChecked -> + if (isChecked) { + selectCredential(credential) + } else { + removeCredential(credential) + } + }, + colors = + CheckboxDefaults.colors( + checkedColor = ColorBlue600, + uncheckedColor = ColorStone300 + ) ) Text( - text = getCredentialTitle(credential), - fontFamily = Inter, - fontWeight = FontWeight.SemiBold, - fontSize = 18.sp, - color = ColorStone950, - modifier = Modifier.weight(1f) + text = getCredentialTitle(credential), + fontFamily = Inter, + fontWeight = FontWeight.SemiBold, + fontSize = 18.sp, + color = ColorStone950, + modifier = Modifier.weight(1f) ) if (expanded) { Image( - painter = painterResource(id = R.drawable.collapse), - contentDescription = stringResource(id = R.string.collapse), - modifier = Modifier.clickable { expanded = false } + painter = painterResource(id = R.drawable.collapse), + contentDescription = stringResource(id = R.string.collapse), + modifier = Modifier.clickable { expanded = false } ) } else { Image( - painter = painterResource(id = R.drawable.expand), - contentDescription = stringResource(id = R.string.expand), - modifier = Modifier.clickable { expanded = true } + painter = painterResource(id = R.drawable.expand), + contentDescription = stringResource(id = R.string.expand), + modifier = Modifier.clickable { expanded = true } ) } } if (expanded) { Text( - buildAnnotatedString { - mockDataField.forEach { - withStyle(style = paragraphStyle) { - append(bullet) - append("\t\t") - append(it) - } + buildAnnotatedString { + mockDataField.forEach { + withStyle(style = paragraphStyle) { + append(bullet) + append("\t\t") + append(it) } - }, - modifier = Modifier.padding(16.dp) + } + }, + modifier = Modifier.padding(16.dp) ) } } 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 fc50af6..f64074a 100644 --- a/example/src/main/java/com/spruceid/mobilesdkexample/wallet/WalletHomeView.kt +++ b/example/src/main/java/com/spruceid/mobilesdkexample/wallet/WalletHomeView.kt @@ -50,9 +50,8 @@ import com.spruceid.mobilesdkexample.utils.getCredentialIdTitleAndIssuer import com.spruceid.mobilesdkexample.utils.getCurrentSqlDate import com.spruceid.mobilesdkexample.utils.getFileContent import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModel -import com.spruceid.mobilesdkexample.viewmodels.StatusListViewModel -import kotlinx.coroutines.launch import com.spruceid.mobilesdkexample.viewmodels.HelpersViewModel +import com.spruceid.mobilesdkexample.viewmodels.StatusListViewModel import com.spruceid.mobilesdkexample.viewmodels.WalletActivityLogsViewModel import kotlinx.coroutines.launch diff --git a/example/src/main/res/drawable/success_toast_icon.xml b/example/src/main/res/drawable/success_toast_icon.xml new file mode 100644 index 0000000..6160fa9 --- /dev/null +++ b/example/src/main/res/drawable/success_toast_icon.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml index eb9bfff..35fe815 100644 --- a/example/src/main/res/values/strings.xml +++ b/example/src/main/res/values/strings.xml @@ -27,4 +27,5 @@ Settings Click to filter Export + Success