-
Notifications
You must be signed in to change notification settings - Fork 88
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
identity-appsupport: Add QR code generation and scanning composables.
This adds Compose Multiplatform support for QR code generation and scanning. A future change will start using this in the wallet app, currently it's only used in samples/testapp. This adds dependencies on https://github.com/alexzhirkevich/qrose and https://github.com/kalinjul/EasyQRScan which are under the MIT and Apache 2.0 license, respectively. These depencies are wholly hidden behind the `ScanQrCodeDialog` and `ShowQrCodeDialog` in case we want to swap these out in the future. For example, for the scanning part we likely want to just use MLKit directly. Test: New screens in samples/testapp for testing. Tested on Android and iOS. Signed-off-by: David Zeuthen <[email protected]>
- Loading branch information
Showing
12 changed files
with
281 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
...pport/src/commonMain/kotlin/com/android/identity/appsupport/ui/qrcode/ScanQrCodeDialog.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package com.android.identity.appsupport.ui.qrcode | ||
|
||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.material3.AlertDialog | ||
import androidx.compose.material3.Text | ||
import androidx.compose.material3.TextButton | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.unit.dp | ||
import org.publicvalue.multiplatform.qrcode.CodeType | ||
import org.publicvalue.multiplatform.qrcode.ScannerWithPermissions | ||
|
||
/** | ||
* Shows a dialog for scanning QR codes. | ||
* | ||
* If the application doesn't have the necessary permission, the user is prompted to grant it. | ||
* | ||
* @param title The title of the dialog. | ||
* @param description The description text to include in the dialog. | ||
* @param dismissButton The text for the dismiss button. | ||
* @param onCodeScanned called when a QR code is scanned, the parameter is the parsed data. Should | ||
* return `true` to stop scanning, `false` to continue scanning. | ||
* @param onDismiss called when the dismiss button is pressed. | ||
* @param modifier A [Modifier] or `null`. | ||
*/ | ||
@Composable | ||
fun ScanQrCodeDialog( | ||
title: String, | ||
description: String, | ||
dismissButton: String, | ||
onCodeScanned: (data: String) -> Boolean, | ||
onDismiss: () -> Unit, | ||
modifier: Modifier? = null | ||
) { | ||
AlertDialog( | ||
modifier = modifier ?: Modifier, | ||
title = { Text(text = title) }, | ||
text = { | ||
Column( | ||
verticalArrangement = Arrangement.spacedBy(16.dp), | ||
) { | ||
Text(text = description) | ||
|
||
ScannerWithPermissions( | ||
modifier = Modifier.height(300.dp), | ||
onScanned = { data -> | ||
onCodeScanned(data) | ||
}, | ||
types = listOf(CodeType.QR) | ||
) | ||
} | ||
}, | ||
onDismissRequest = onDismiss, | ||
confirmButton = {}, | ||
dismissButton = { | ||
TextButton(onClick = { onDismiss() }) { | ||
Text(dismissButton) | ||
} | ||
} | ||
) | ||
} |
87 changes: 87 additions & 0 deletions
87
...pport/src/commonMain/kotlin/com/android/identity/appsupport/ui/qrcode/ShowQrCodeDialog.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package com.android.identity.appsupport.ui.qrcode | ||
|
||
import androidx.compose.foundation.Image | ||
import androidx.compose.foundation.background | ||
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.layout.size | ||
import androidx.compose.foundation.shape.RoundedCornerShape | ||
import androidx.compose.material3.AlertDialog | ||
import androidx.compose.material3.Text | ||
import androidx.compose.material3.TextButton | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.draw.clip | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.unit.dp | ||
import identitycredential.identity_appsupport.generated.resources.Res | ||
import identitycredential.identity_appsupport.generated.resources.show_qr_code_dialog_qr_content_description | ||
import io.github.alexzhirkevich.qrose.rememberQrCodePainter | ||
import org.jetbrains.compose.resources.stringResource | ||
|
||
/** | ||
* Renders a QR code and shows it in a dialog. | ||
* | ||
* @param title The title of the dialog. | ||
* @param description The description text to include in the dialog. | ||
* @param dismissButton The text for the dismiss button. | ||
* @param data the QR code to show, e.g. mdoc:owBjMS4... or https://github.com/.... | ||
* @param onDismiss called when the dismiss button is pressed. | ||
* @param modifier A [Modifier] or `null`. | ||
*/ | ||
@Composable | ||
fun ShowQrCodeDialog( | ||
title: String, | ||
description: String, | ||
dismissButton: String, | ||
data: String, | ||
onDismiss: () -> Unit, | ||
modifier: Modifier? = null | ||
) { | ||
val painter = rememberQrCodePainter( | ||
data = data, | ||
) | ||
|
||
AlertDialog( | ||
modifier = modifier ?: Modifier, | ||
title = { Text(text = title) }, | ||
text = { | ||
Column( | ||
verticalArrangement = Arrangement.spacedBy(16.dp) | ||
) { | ||
Text(text = description) | ||
|
||
Row( | ||
modifier = Modifier.align(Alignment.CenterHorizontally) | ||
) { | ||
Column( | ||
horizontalAlignment = Alignment.CenterHorizontally, | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.clip(shape = RoundedCornerShape(16.dp)) | ||
.background(Color.White) | ||
) { | ||
Image( | ||
painter = painter, | ||
contentDescription = stringResource(Res.string.show_qr_code_dialog_qr_content_description), | ||
modifier = Modifier | ||
.size(300.dp) | ||
.padding(16.dp) | ||
) | ||
} | ||
} | ||
} | ||
}, | ||
onDismissRequest = onDismiss, | ||
confirmButton = {}, | ||
dismissButton = { | ||
TextButton(onClick = { onDismiss() }) { | ||
Text(dismissButton) | ||
} | ||
} | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
samples/testapp/src/commonMain/kotlin/com/android/identity/testapp/ui/QrCodesScreen.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package com.android.identity.testapp.ui | ||
|
||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.lazy.LazyColumn | ||
import androidx.compose.material3.Text | ||
import androidx.compose.material3.TextButton | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.unit.dp | ||
import com.android.identity.appsupport.ui.qrcode.ShowQrCodeDialog | ||
import com.android.identity.appsupport.ui.qrcode.ScanQrCodeDialog | ||
|
||
@Composable | ||
fun QrCodesScreen( | ||
showToast: (message: String) -> Unit | ||
) { | ||
val showMdocQrCodeDialog = remember { mutableStateOf(false) } | ||
val showUrlQrCodeDialog = remember { mutableStateOf(false) } | ||
val showQrScanDialog = remember { mutableStateOf(false) } | ||
|
||
if (showMdocQrCodeDialog.value) { | ||
ShowQrCodeDialog( | ||
title = "Scan code on reader", | ||
description = "Your personal information won't be shared yet. You don't need to hand your phone to anyone to share your ID.", | ||
dismissButton = "Close", | ||
// This is the DeviceEngagement test vector from ISO/IEC 18013-5:2021 Annex D encoded | ||
// as specified in clause 8.2.2.3. | ||
data = "mdoc:owBjMS4wAYIB2BhYS6QBAiABIVggWojRgrzl9C76WZQ/MzWdAuipaP8onZPl" + | ||
"+kRLYkNDFn/iJYILFujPhY3cdpBAe6YdTDOCNwqM/PPeaqZy/GClV6oy/GcCgYMCAaMA9AH1C1BF7+90KyxIN6kKOw4dBaaRBw==", | ||
onDismiss = { showMdocQrCodeDialog.value = false } | ||
) | ||
} | ||
|
||
if (showUrlQrCodeDialog.value) { | ||
ShowQrCodeDialog( | ||
title = "Scan code with phone", | ||
description = "This is a QR code for https://github.com/openwallet-foundation-labs/identity-credential", | ||
dismissButton = "Close", | ||
data = "https://github.com/openwallet-foundation-labs/identity-credential", | ||
onDismiss = { showUrlQrCodeDialog.value = false } | ||
) | ||
} | ||
|
||
if (showQrScanDialog.value) { | ||
ScanQrCodeDialog( | ||
title = "Scan code", | ||
description = "Ask the person you wish to request identity attributes from to present" + | ||
" a QR code. This is usually in their identity wallet.", | ||
dismissButton = "Close", | ||
onCodeScanned = { data -> | ||
if (data.startsWith("mdoc:")) { | ||
showToast("Scanned mdoc URI $data") | ||
showQrScanDialog.value = false | ||
true | ||
} else { | ||
false | ||
} | ||
}, | ||
onDismiss = { showQrScanDialog.value = false } | ||
) | ||
} | ||
|
||
LazyColumn( | ||
modifier = Modifier.padding(8.dp) | ||
) { | ||
item { | ||
TextButton( | ||
onClick = { showMdocQrCodeDialog.value = true }, | ||
content = { Text("Show mdoc QR code") } | ||
) | ||
} | ||
|
||
item { | ||
TextButton( | ||
onClick = { showUrlQrCodeDialog.value = true }, | ||
content = { Text("Show URL QR code") } | ||
) | ||
} | ||
|
||
item { | ||
TextButton( | ||
onClick = { showQrScanDialog.value = true }, | ||
content = { Text("Scan mdoc QR code") } | ||
) | ||
} | ||
} | ||
|
||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters