feat(qr): Enhance QR code scanner UI with instructions and warning

This commit introduces several UI improvements to the QR code scanner:

- Adds an instruction text to guide users on aligning the QR code.
- Includes a warning message card about potential risks of scanning QR codes (phishing, malware).
- Implements visual corner guides on the scanner view.
- Adds a new "Warning" icon to `MifosIcons`.
- Adds corresponding string resources for the new UI elements.
This commit is contained in:
Nagarjuna 2025-09-15 10:19:12 +05:30
parent c0a14316f6
commit 298c1933e2
3 changed files with 180 additions and 17 deletions

View File

@ -45,6 +45,7 @@ import androidx.compose.material.icons.filled.RadioButtonUnchecked
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material.icons.outlined.AccountCircle
import androidx.compose.material.icons.outlined.Cancel
import androidx.compose.material.icons.outlined.DeleteOutline
@ -140,4 +141,5 @@ object MifosIcons {
val History = Icons.Default.History
val Filter = Icons.Default.FilterList
val OpenInNew = Icons.AutoMirrored.Filled.OpenInNew
val Warning = Icons.Default.Warning
}

View File

@ -17,5 +17,7 @@
<string name="feature_qr_loading">Loading</string>
<string name="feature_qr_oops">Oops</string>
<string name="feature_qr_unexpected_error_subtitle">An unexpected error occurred. Please try again.</string>
<string name="feature_qr_instruction">Please, align QR Code within the frame to make scanning easily detectable.</string>
<string name="feature_qr_warning_title">Potential Warning</string>
<string name="feature_qr_warning_message">If you scan the QR code, it could take you to a phishing website that steals your personal information, like credit card numbers or usernames and passwords. It could also download malware onto your phone and give hackers access to your device.</string>
</resources>

View File

@ -9,15 +9,38 @@
*/
package org.mifospay.feature.qr
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import mobile_wallet.feature.qr.generated.resources.Res
import mobile_wallet.feature.qr.generated.resources.feature_qr_instruction
import mobile_wallet.feature.qr.generated.resources.feature_qr_warning_message
import mobile_wallet.feature.qr.generated.resources.feature_qr_warning_title
import org.jetbrains.compose.resources.stringResource
import org.mifospay.core.designsystem.component.MifosCard
import org.mifospay.core.designsystem.icon.MifosIcons
import template.core.base.designsystem.theme.KptTheme
@Composable
@ -35,27 +58,94 @@ fun QrScannerWithPermissions(
openSettingsLabel: String = "Open Settings",
onScanned: (String) -> Boolean,
) {
QrScannerWithPermissions(
types = types,
modifier = modifier.clipToBounds(),
onScanned = onScanned,
permissionDeniedContent = { permissionState ->
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
Box(
modifier = Modifier
.fillMaxSize()
.padding(KptTheme.spacing.lg)
.padding(top = KptTheme.spacing.lg),
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 56.dp + 24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResource(Res.string.feature_qr_instruction),
textAlign = TextAlign.Center,
color = Color.Black,
)
Spacer(modifier = Modifier.height(KptTheme.spacing.md))
Box(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.clip(KptTheme.shapes.medium)
.drawQrCorners()
.border(1.dp, Color.Transparent, KptTheme.shapes.medium),
contentAlignment = Alignment.Center,
) {
Text(
modifier = Modifier.padding(KptTheme.spacing.sm),
text = permissionText,
QrScannerWithPermissions(
types = types,
modifier = modifier.clipToBounds(),
onScanned = onScanned,
permissionDeniedContent = { permissionState ->
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
modifier = Modifier.padding(KptTheme.spacing.sm),
text = permissionText,
)
Button(
onClick = permissionState::goToSettings,
) {
Text(openSettingsLabel)
}
}
},
)
Button(
onClick = permissionState::goToSettings,
MifosCard(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.padding(KptTheme.spacing.sm),
shape = KptTheme.shapes.medium,
) {
Text(openSettingsLabel)
Column(
modifier = Modifier.padding(KptTheme.spacing.lg),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.sm),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(KptTheme.spacing.sm),
) {
Icon(
modifier = Modifier.size(20.dp),
imageVector = MifosIcons.Warning,
contentDescription = "Warning",
tint = Color.Unspecified,
)
Text(
text = stringResource(Res.string.feature_qr_warning_title),
color = Color.Black,
)
}
Text(
text = stringResource(Res.string.feature_qr_warning_message),
color = Color.Black,
textAlign = TextAlign.Center,
)
}
}
}
},
)
}
}
}
@Composable
@ -79,3 +169,72 @@ fun QrScannerWithPermissions(
permissionDeniedContent(permissionState)
}
}
private fun Modifier.drawQrCorners(): Modifier = drawWithContent {
drawContent()
val strokeWidth = 5.dp.toPx()
val lineLength = 40.dp.toPx()
val horizontalPadding = 50.dp.toPx() // for left & right
val verticalPaddingTop = 50.dp.toPx() // for top corners
val verticalPaddingBottom = 300.dp.toPx() // more padding for bottom corners
val color = Color.White
// Top-left
drawLine(
color,
Offset(horizontalPadding, verticalPaddingTop),
Offset(horizontalPadding + lineLength, verticalPaddingTop),
strokeWidth,
)
drawLine(
color,
Offset(horizontalPadding, verticalPaddingTop),
Offset(horizontalPadding, verticalPaddingTop + lineLength),
strokeWidth,
)
// Top-right
drawLine(
color,
Offset(size.width - horizontalPadding, verticalPaddingTop),
Offset(size.width - horizontalPadding - lineLength, verticalPaddingTop),
strokeWidth,
)
drawLine(
color,
Offset(size.width - horizontalPadding, verticalPaddingTop),
Offset(size.width - horizontalPadding, verticalPaddingTop + lineLength),
strokeWidth,
)
// Bottom-left
drawLine(
color,
Offset(horizontalPadding, size.height - verticalPaddingBottom),
Offset(horizontalPadding + lineLength, size.height - verticalPaddingBottom),
strokeWidth,
)
drawLine(
color,
Offset(horizontalPadding, size.height - verticalPaddingBottom),
Offset(horizontalPadding, size.height - verticalPaddingBottom - lineLength),
strokeWidth,
)
// Bottom-right
drawLine(
color,
Offset(size.width - horizontalPadding, size.height - verticalPaddingBottom),
Offset(size.width - horizontalPadding - lineLength, size.height - verticalPaddingBottom),
strokeWidth,
)
drawLine(
color,
Offset(size.width - horizontalPadding, size.height - verticalPaddingBottom),
Offset(size.width - horizontalPadding, size.height - verticalPaddingBottom - lineLength),
strokeWidth,
)
}