view alignment
This commit is contained in:
@@ -48,6 +48,12 @@ fun UseCaseView.capabilities(): UseCaseCapabilities {
|
|||||||
allowCopy = true,
|
allowCopy = true,
|
||||||
allowShare = true,
|
allowShare = true,
|
||||||
allowOpenUrl = true,
|
allowOpenUrl = true,
|
||||||
|
allowAddContact = true,
|
||||||
|
allowDialPhone = true,
|
||||||
|
allowSendSms = true,
|
||||||
|
allowSendEmail = true,
|
||||||
|
allowOpenWifiSettings = true,
|
||||||
|
allowAddCalendarEvent = true,
|
||||||
allowHistoryExport = true
|
allowHistoryExport = true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,23 @@ import android.os.Build
|
|||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -46,6 +53,7 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
@@ -54,6 +62,7 @@ import de.softwareapp_hb.privateqrscanner.R
|
|||||||
import de.softwareapp_hb.privateqrscanner.data.scanner.DetectionBox
|
import de.softwareapp_hb.privateqrscanner.data.scanner.DetectionBox
|
||||||
import de.softwareapp_hb.privateqrscanner.data.scanner.DetectionPoint
|
import de.softwareapp_hb.privateqrscanner.data.scanner.DetectionPoint
|
||||||
import de.softwareapp_hb.privateqrscanner.domain.ScanResult
|
import de.softwareapp_hb.privateqrscanner.domain.ScanResult
|
||||||
|
import de.softwareapp_hb.privateqrscanner.ui.theme.PrivateQrColors
|
||||||
import de.softwareapp_hb.privateqrscanner.util.readableBarcodePayload
|
import de.softwareapp_hb.privateqrscanner.util.readableBarcodePayload
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScanner
|
import com.google.mlkit.vision.barcode.BarcodeScanner
|
||||||
@@ -104,6 +113,7 @@ internal fun GalleryScanPreviewDialog(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val bitmap = remember(imageUri) { imageUri?.let { loadBitmapFromUri(context, it) } }
|
val bitmap = remember(imageUri) { imageUri?.let { loadBitmapFromUri(context, it) } }
|
||||||
var liveCandidates by remember(imageUri, candidates) { mutableStateOf(candidates) }
|
var liveCandidates by remember(imageUri, candidates) { mutableStateOf(candidates) }
|
||||||
|
var selectedIndex by remember(imageUri) { mutableIntStateOf(0) }
|
||||||
var zoom by remember(imageUri) { mutableFloatStateOf(1f) }
|
var zoom by remember(imageUri) { mutableFloatStateOf(1f) }
|
||||||
var pan by remember(imageUri) { mutableStateOf(Offset.Zero) }
|
var pan by remember(imageUri) { mutableStateOf(Offset.Zero) }
|
||||||
var viewportSize by remember { mutableStateOf(IntSize.Zero) }
|
var viewportSize by remember { mutableStateOf(IntSize.Zero) }
|
||||||
@@ -130,6 +140,14 @@ internal fun GalleryScanPreviewDialog(
|
|||||||
onDispose { scanner.close() }
|
onDispose { scanner.close() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(liveCandidates.size) {
|
||||||
|
selectedIndex = if (liveCandidates.isEmpty()) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
selectedIndex.coerceIn(0, liveCandidates.lastIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(bitmap, viewportSize, zoom, pan, scanTick) {
|
LaunchedEffect(bitmap, viewportSize, zoom, pan, scanTick) {
|
||||||
val bmp = bitmap ?: return@LaunchedEffect
|
val bmp = bitmap ?: return@LaunchedEffect
|
||||||
val vw = viewportSize.width.toFloat()
|
val vw = viewportSize.width.toFloat()
|
||||||
@@ -204,14 +222,33 @@ internal fun GalleryScanPreviewDialog(
|
|||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = { Text(stringResource(R.string.image_scan_pick_title, liveCandidates.size)) },
|
containerColor = PrivateQrColors.Surface,
|
||||||
|
shape = RoundedCornerShape(30.dp),
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.image_scan_pick_title, liveCandidates.size),
|
||||||
|
color = PrivateQrColors.TextPrimary,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
fontWeight = FontWeight.ExtraBold
|
||||||
|
)
|
||||||
|
},
|
||||||
text = {
|
text = {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (liveCandidates.isEmpty()) {
|
||||||
|
stringResource(R.string.no_code_found_in_image)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.image_scan_pick_subtitle)
|
||||||
|
},
|
||||||
|
color = PrivateQrColors.TextSecondary,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -228,7 +265,7 @@ internal fun GalleryScanPreviewDialog(
|
|||||||
scanTick++
|
scanTick++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color.Black.copy(alpha = 0.32f), RoundedCornerShape(12.dp)),
|
.background(PrivateQrColors.Navy, RoundedCornerShape(24.dp)),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
@@ -267,7 +304,7 @@ internal fun GalleryScanPreviewDialog(
|
|||||||
|
|
||||||
liveCandidates.forEachIndexed { index, candidate ->
|
liveCandidates.forEachIndexed { index, candidate ->
|
||||||
val box = candidate.box ?: return@forEachIndexed
|
val box = candidate.box ?: return@forEachIndexed
|
||||||
val color = Color(0xFF4AE3A3).copy(alpha = 0.96f)
|
val color = PrivateQrColors.Teal300.copy(alpha = 0.96f)
|
||||||
val points = box.corners.map { p ->
|
val points = box.corners.map { p ->
|
||||||
imageToScreen(p.x * imageW, p.y * imageH)
|
imageToScreen(p.x * imageW, p.y * imageH)
|
||||||
}
|
}
|
||||||
@@ -322,20 +359,45 @@ internal fun GalleryScanPreviewDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (liveCandidates.isEmpty()) {
|
if (liveCandidates.isNotEmpty()) {
|
||||||
Text(text = stringResource(R.string.no_code_found_in_image))
|
|
||||||
} else {
|
|
||||||
Text(text = stringResource(R.string.image_scan_pick_subtitle))
|
|
||||||
liveCandidates.forEachIndexed { index, candidate ->
|
liveCandidates.forEachIndexed { index, candidate ->
|
||||||
TextButton(
|
val selected = index == selectedIndex
|
||||||
onClick = { onPick(candidate) },
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
color = if (selected) Color(0xFFECFDF5) else PrivateQrColors.AppBackground,
|
||||||
|
shape = RoundedCornerShape(24.dp)
|
||||||
|
)
|
||||||
|
.border(
|
||||||
|
width = 2.dp,
|
||||||
|
color = if (selected) PrivateQrColors.Teal300 else Color.Transparent,
|
||||||
|
shape = RoundedCornerShape(24.dp)
|
||||||
|
)
|
||||||
|
.clickable { selectedIndex = index }
|
||||||
|
.padding(18.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(54.dp)
|
||||||
|
.background(PrivateQrColors.Mint, RoundedCornerShape(16.dp)),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "${index + 1}. ${candidate.result.displayType}",
|
text = "${index + 1}",
|
||||||
textAlign = TextAlign.Start,
|
color = PrivateQrColors.Teal700,
|
||||||
modifier = Modifier.fillMaxWidth()
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.ExtraBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = candidate.result.displayType,
|
||||||
|
color = PrivateQrColors.TextPrimary,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.ExtraBold
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = if (candidate.result.isBase64Encoded) {
|
text = if (candidate.result.isBase64Encoded) {
|
||||||
@@ -343,10 +405,11 @@ internal fun GalleryScanPreviewDialog(
|
|||||||
} else {
|
} else {
|
||||||
candidate.result.content
|
candidate.result.content
|
||||||
},
|
},
|
||||||
|
color = PrivateQrColors.TextSecondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,9 +417,29 @@ internal fun GalleryScanPreviewDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {},
|
confirmButton = {
|
||||||
|
val selected = liveCandidates.getOrNull(selectedIndex)
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
if (selected != null) onPick(selected)
|
||||||
|
},
|
||||||
|
enabled = selected != null,
|
||||||
|
colors = ButtonDefaults.textButtonColors(
|
||||||
|
containerColor = PrivateQrColors.Teal700,
|
||||||
|
contentColor = PrivateQrColors.Surface,
|
||||||
|
disabledContainerColor = PrivateQrColors.TextSecondary.copy(alpha = 0.18f),
|
||||||
|
disabledContentColor = PrivateQrColors.TextSecondary
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(18.dp)
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.image_scan_use_selected))
|
||||||
|
}
|
||||||
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismiss) {
|
TextButton(
|
||||||
|
onClick = onDismiss,
|
||||||
|
colors = ButtonDefaults.textButtonColors(contentColor = PrivateQrColors.Teal700)
|
||||||
|
) {
|
||||||
Text(stringResource(R.string.cancel))
|
Text(stringResource(R.string.cancel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -801,15 +801,44 @@ fun ScannerScreen(
|
|||||||
if (showRiskWarning && pendingOpenUrl != null) {
|
if (showRiskWarning && pendingOpenUrl != null) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showRiskWarning = false },
|
onDismissRequest = { showRiskWarning = false },
|
||||||
text = { Text(stringResource(R.string.risk_warning)) },
|
containerColor = PrivateQrColors.Surface,
|
||||||
|
shape = RoundedCornerShape(28.dp),
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.risk_warning_title),
|
||||||
|
color = PrivateQrColors.TextPrimary,
|
||||||
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.risk_warning),
|
||||||
|
color = PrivateQrColors.TextSecondary,
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = {
|
TextButton(
|
||||||
Intents.openUrl(context, pendingOpenUrl!!)
|
onClick = {
|
||||||
showRiskWarning = false
|
Intents.openUrl(context, pendingOpenUrl!!)
|
||||||
}) { Text(stringResource(R.string.open_anyway)) }
|
showRiskWarning = false
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.textButtonColors(
|
||||||
|
containerColor = PrivateQrColors.Teal700,
|
||||||
|
contentColor = PrivateQrColors.Surface
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(18.dp)
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.open_anyway))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { showRiskWarning = false }) {
|
TextButton(
|
||||||
|
onClick = { showRiskWarning = false },
|
||||||
|
colors = ButtonDefaults.textButtonColors(
|
||||||
|
contentColor = PrivateQrColors.Teal700
|
||||||
|
)
|
||||||
|
) {
|
||||||
Text(stringResource(R.string.cancel))
|
Text(stringResource(R.string.cancel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package de.softwareapp_hb.privateqrscanner.ui.screens
|
|||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -80,19 +83,27 @@ fun SettingsScreen(
|
|||||||
if (showUseCasePicker.value) {
|
if (showUseCasePicker.value) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showUseCasePicker.value = false },
|
onDismissRequest = { showUseCasePicker.value = false },
|
||||||
title = { Text(stringResource(R.string.select_use_case_view)) },
|
containerColor = PrivateQrColors.Surface,
|
||||||
|
shape = RoundedCornerShape(30.dp),
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.select_use_case_view),
|
||||||
|
color = PrivateQrColors.TextPrimary,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
fontWeight = FontWeight.ExtraBold
|
||||||
|
)
|
||||||
|
},
|
||||||
text = {
|
text = {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
UseCaseView.entries.forEach { candidate ->
|
UseCaseView.entries.forEach { candidate ->
|
||||||
TextButton(
|
UseCasePickerOption(
|
||||||
|
candidate = candidate,
|
||||||
|
selected = candidate == selectedUseCaseView,
|
||||||
onClick = {
|
onClick = {
|
||||||
onUseCaseViewSelected(candidate)
|
onUseCaseViewSelected(candidate)
|
||||||
showUseCasePicker.value = false
|
showUseCasePicker.value = false
|
||||||
},
|
}
|
||||||
modifier = Modifier.fillMaxWidth()
|
)
|
||||||
) {
|
|
||||||
Text(text = stringResource(candidate.titleRes))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -223,6 +234,61 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun UseCasePickerOption(
|
||||||
|
candidate: UseCaseView,
|
||||||
|
selected: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val borderColor = if (selected) PrivateQrColors.Teal300 else Color.Transparent
|
||||||
|
val backgroundColor = if (selected) Color(0xFFECFDF5) else PrivateQrColors.AppBackground
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(backgroundColor, RoundedCornerShape(24.dp))
|
||||||
|
.border(2.dp, borderColor, RoundedCornerShape(24.dp))
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(18.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(58.dp)
|
||||||
|
.background(PrivateQrColors.Mint, RoundedCornerShape(16.dp)),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (selected) "✓" else "◎",
|
||||||
|
color = PrivateQrColors.Teal700,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.ExtraBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(candidate.titleRes),
|
||||||
|
color = PrivateQrColors.TextPrimary,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.ExtraBold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(candidate.descriptionRes()),
|
||||||
|
color = PrivateQrColors.TextSecondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun UseCaseView.descriptionRes(): Int {
|
||||||
|
return when (this) {
|
||||||
|
UseCaseView.EverydayPersonal -> R.string.use_case_everyday_description
|
||||||
|
UseCaseView.EventTicketing -> R.string.use_case_event_ticketing_description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SettingsHeader() {
|
private fun SettingsHeader() {
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
<string name="open">Öffnen</string>
|
<string name="open">Öffnen</string>
|
||||||
<string name="cancel">Abbrechen</string>
|
<string name="cancel">Abbrechen</string>
|
||||||
<string name="open_anyway">Trotzdem öffnen</string>
|
<string name="open_anyway">Trotzdem öffnen</string>
|
||||||
<string name="risk_warning">Diese URL wirkt ungewöhnlich. Prüfe sie, bevor du öffnest.</string>
|
<string name="risk_warning_title">Diese URL wirkt ungewöhnlich</string>
|
||||||
|
<string name="risk_warning">Prüfe den Link vor dem Öffnen. Die Warnung wird auf deinem Gerät berechnet, ohne den Scan irgendwohin zu senden.</string>
|
||||||
<string name="delete_all">Alles löschen</string>
|
<string name="delete_all">Alles löschen</string>
|
||||||
<string name="confirm_delete_all">Alle Historie-Einträge löschen?</string>
|
<string name="confirm_delete_all">Alle Historie-Einträge löschen?</string>
|
||||||
<string name="confirm">Bestätigen</string>
|
<string name="confirm">Bestätigen</string>
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
<string name="no_code_found_in_image">Im gewählten Bild wurde kein QR- oder Barcode gefunden.</string>
|
<string name="no_code_found_in_image">Im gewählten Bild wurde kein QR- oder Barcode gefunden.</string>
|
||||||
<string name="image_scan_pick_title">%1$d Codes im Bild gefunden</string>
|
<string name="image_scan_pick_title">%1$d Codes im Bild gefunden</string>
|
||||||
<string name="image_scan_pick_subtitle">Wähle ein Ergebnis aus:</string>
|
<string name="image_scan_pick_subtitle">Wähle ein Ergebnis aus:</string>
|
||||||
|
<string name="image_scan_use_selected">Ausgewähltes verwenden</string>
|
||||||
<string name="image_scan_failed">Dieses Bild konnte nicht gelesen werden. Bitte anderes Bild versuchen.</string>
|
<string name="image_scan_failed">Dieses Bild konnte nicht gelesen werden. Bitte anderes Bild versuchen.</string>
|
||||||
<string name="already_scanned">Bereits gescannt</string>
|
<string name="already_scanned">Bereits gescannt</string>
|
||||||
<string name="duplicate_ticket_alert_title">Doppeltes Ticket erkannt</string>
|
<string name="duplicate_ticket_alert_title">Doppeltes Ticket erkannt</string>
|
||||||
@@ -76,4 +78,6 @@
|
|||||||
<string name="select_use_case_view">Use-Case-Ansicht wählen</string>
|
<string name="select_use_case_view">Use-Case-Ansicht wählen</string>
|
||||||
<string name="use_case_everyday_personal">Alltägliche private Nutzung</string>
|
<string name="use_case_everyday_personal">Alltägliche private Nutzung</string>
|
||||||
<string name="use_case_event_ticketing">Events & Ticketing</string>
|
<string name="use_case_event_ticketing">Events & Ticketing</string>
|
||||||
|
<string name="use_case_everyday_description">Vollständiger privater Scanner mit lokalem Verlauf und üblichen Ergebnisaktionen.</string>
|
||||||
|
<string name="use_case_event_ticketing_description">Batch-Scanning, Duplikaterkennung, Whitelist-Import und Batch-Teilen.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
<string name="open">Open</string>
|
<string name="open">Open</string>
|
||||||
<string name="cancel">Cancel</string>
|
<string name="cancel">Cancel</string>
|
||||||
<string name="open_anyway">Open anyway</string>
|
<string name="open_anyway">Open anyway</string>
|
||||||
<string name="risk_warning">This URL looks unusual. Check it before opening.</string>
|
<string name="risk_warning_title">This URL looks unusual</string>
|
||||||
|
<string name="risk_warning">Check the link before opening. The warning is calculated on your device, without sending the scan anywhere.</string>
|
||||||
<string name="delete_all">Delete all</string>
|
<string name="delete_all">Delete all</string>
|
||||||
<string name="confirm_delete_all">Delete all history entries?</string>
|
<string name="confirm_delete_all">Delete all history entries?</string>
|
||||||
<string name="confirm">Confirm</string>
|
<string name="confirm">Confirm</string>
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
<string name="no_code_found_in_image">No QR or barcode found in the selected image.</string>
|
<string name="no_code_found_in_image">No QR or barcode found in the selected image.</string>
|
||||||
<string name="image_scan_pick_title">Found %1$d codes in image</string>
|
<string name="image_scan_pick_title">Found %1$d codes in image</string>
|
||||||
<string name="image_scan_pick_subtitle">Choose a result to use:</string>
|
<string name="image_scan_pick_subtitle">Choose a result to use:</string>
|
||||||
|
<string name="image_scan_use_selected">Use selected</string>
|
||||||
<string name="image_scan_failed">Could not read this image. Try another one.</string>
|
<string name="image_scan_failed">Could not read this image. Try another one.</string>
|
||||||
<string name="already_scanned">Already scanned</string>
|
<string name="already_scanned">Already scanned</string>
|
||||||
<string name="duplicate_ticket_alert_title">Duplicate ticket detected</string>
|
<string name="duplicate_ticket_alert_title">Duplicate ticket detected</string>
|
||||||
@@ -76,4 +78,6 @@
|
|||||||
<string name="select_use_case_view">Select use-case view</string>
|
<string name="select_use_case_view">Select use-case view</string>
|
||||||
<string name="use_case_everyday_personal">Everyday personal use</string>
|
<string name="use_case_everyday_personal">Everyday personal use</string>
|
||||||
<string name="use_case_event_ticketing">Event & ticketing</string>
|
<string name="use_case_event_ticketing">Event & ticketing</string>
|
||||||
|
<string name="use_case_everyday_description">Full personal scanner with local history and common result actions.</string>
|
||||||
|
<string name="use_case_event_ticketing_description">Batch scanning, duplicate detection, whitelist import, and batch sharing.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user