removed batchmode from personal usecase
update ticketing and events view
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# Clean Scanner Use Cases
|
||||
|
||||
## Use-Case Views
|
||||
- [Done] Each use case has an individual view profile that shows only relevant functions.
|
||||
- [Done] Default profile is **Everyday Personal Use**.
|
||||
- [Done] Other profiles can be selected in **Settings**.
|
||||
|
||||
## 1. Everyday Personal Use
|
||||
- [Done] Scan restaurant menus, product QR labels, and website links quickly.
|
||||
- [Done] Copy/share scanned values to chat apps or notes.
|
||||
@@ -7,6 +12,7 @@
|
||||
|
||||
## 2. Event & Ticketing
|
||||
- [Done] Scan tickets at venues and quickly validate repeated entries.
|
||||
- [Done] Enable **Stapelmodus (Batch Mode)** by default in this view for fast check-in flow.
|
||||
- [Done] Use batch mode to process multiple attendees without leaving the camera.
|
||||
- [Done] Share batch captures to organizers for quick reconciliation.
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@ package com.clean.scanner.settings
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.clean.scanner.ui.UseCaseView
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@@ -14,6 +16,7 @@ class SettingsRepository(private val context: Context) {
|
||||
val historyEnabled = booleanPreferencesKey("history_enabled")
|
||||
val warningsEnabled = booleanPreferencesKey("warnings_enabled")
|
||||
val scanFeedbackEnabled = booleanPreferencesKey("scan_feedback_enabled")
|
||||
val useCaseView = stringPreferencesKey("use_case_view")
|
||||
}
|
||||
|
||||
val historyEnabled: Flow<Boolean> = context.dataStore.data.map { prefs ->
|
||||
@@ -28,6 +31,10 @@ class SettingsRepository(private val context: Context) {
|
||||
prefs[Keys.scanFeedbackEnabled] ?: true
|
||||
}
|
||||
|
||||
val useCaseView: Flow<UseCaseView> = context.dataStore.data.map { prefs ->
|
||||
UseCaseView.fromStorageKey(prefs[Keys.useCaseView])
|
||||
}
|
||||
|
||||
suspend fun setHistoryEnabled(enabled: Boolean) {
|
||||
context.dataStore.edit { it[Keys.historyEnabled] = enabled }
|
||||
}
|
||||
@@ -39,4 +46,8 @@ class SettingsRepository(private val context: Context) {
|
||||
suspend fun setScanFeedbackEnabled(enabled: Boolean) {
|
||||
context.dataStore.edit { it[Keys.scanFeedbackEnabled] = enabled }
|
||||
}
|
||||
|
||||
suspend fun setUseCaseView(useCaseView: UseCaseView) {
|
||||
context.dataStore.edit { it[Keys.useCaseView] = useCaseView.storageKey }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ data class AppUiState(
|
||||
val historyEnabled: Boolean = false,
|
||||
val warningsEnabled: Boolean = true,
|
||||
val scanFeedbackEnabled: Boolean = true,
|
||||
val useCaseView: UseCaseView = UseCaseView.default,
|
||||
val history: List<ScanRecord> = emptyList(),
|
||||
val searchQuery: String = ""
|
||||
)
|
||||
@@ -24,19 +25,39 @@ class AppViewModel(
|
||||
private val container: AppContainer
|
||||
) : ViewModel() {
|
||||
|
||||
private data class SettingsState(
|
||||
val historyEnabled: Boolean,
|
||||
val warningsEnabled: Boolean,
|
||||
val scanFeedbackEnabled: Boolean,
|
||||
val useCaseView: UseCaseView
|
||||
)
|
||||
|
||||
private val query = MutableStateFlow("")
|
||||
|
||||
val uiState: StateFlow<AppUiState> = combine(
|
||||
private val settingsState = combine(
|
||||
container.settingsRepository.historyEnabled,
|
||||
container.settingsRepository.warningsEnabled,
|
||||
container.settingsRepository.scanFeedbackEnabled,
|
||||
container.scanRepository.observeHistory(),
|
||||
query
|
||||
) { historyEnabled, warningsEnabled, scanFeedbackEnabled, history, q ->
|
||||
AppUiState(
|
||||
container.settingsRepository.useCaseView
|
||||
) { historyEnabled, warningsEnabled, scanFeedbackEnabled, useCaseView ->
|
||||
SettingsState(
|
||||
historyEnabled = historyEnabled,
|
||||
warningsEnabled = warningsEnabled,
|
||||
scanFeedbackEnabled = scanFeedbackEnabled,
|
||||
useCaseView = useCaseView
|
||||
)
|
||||
}
|
||||
|
||||
val uiState: StateFlow<AppUiState> = combine(
|
||||
settingsState,
|
||||
container.scanRepository.observeHistory(),
|
||||
query
|
||||
) { settings, history, q ->
|
||||
AppUiState(
|
||||
historyEnabled = settings.historyEnabled,
|
||||
warningsEnabled = settings.warningsEnabled,
|
||||
scanFeedbackEnabled = settings.scanFeedbackEnabled,
|
||||
useCaseView = settings.useCaseView,
|
||||
history = if (q.isBlank()) history else history.filter {
|
||||
it.content.contains(q, ignoreCase = true) || it.type.contains(q, ignoreCase = true)
|
||||
},
|
||||
@@ -69,6 +90,12 @@ class AppViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun setUseCaseView(useCaseView: UseCaseView) {
|
||||
viewModelScope.launch {
|
||||
container.settingsRepository.setUseCaseView(useCaseView)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteHistoryItem(id: Long) {
|
||||
viewModelScope.launch {
|
||||
container.scanRepository.deleteById(id)
|
||||
|
||||
@@ -74,6 +74,7 @@ fun CleanScannerAppRoot(container: AppContainer) {
|
||||
scanFeedbackNonce = scannerState.scanFeedbackNonce,
|
||||
warningsEnabled = appState.warningsEnabled,
|
||||
scanFeedbackEnabled = appState.scanFeedbackEnabled,
|
||||
useCaseView = appState.useCaseView,
|
||||
onScan = scannerViewModel::onScan,
|
||||
onScanAgain = scannerViewModel::resumeScanning,
|
||||
onBatchModeChange = scannerViewModel::setBatchMode,
|
||||
@@ -84,6 +85,7 @@ fun CleanScannerAppRoot(container: AppContainer) {
|
||||
RootTab.History -> HistoryScreen(
|
||||
query = appState.searchQuery,
|
||||
history = appState.history,
|
||||
useCaseView = appState.useCaseView,
|
||||
onQueryChange = appViewModel::setQuery,
|
||||
onDelete = appViewModel::deleteHistoryItem,
|
||||
onClearAll = appViewModel::clearHistory
|
||||
@@ -93,9 +95,11 @@ fun CleanScannerAppRoot(container: AppContainer) {
|
||||
historyEnabled = appState.historyEnabled,
|
||||
warningsEnabled = appState.warningsEnabled,
|
||||
scanFeedbackEnabled = appState.scanFeedbackEnabled,
|
||||
selectedUseCaseView = appState.useCaseView,
|
||||
onHistoryToggle = appViewModel::setHistoryEnabled,
|
||||
onWarningsToggle = appViewModel::setWarningsEnabled,
|
||||
onScanFeedbackToggle = appViewModel::setScanFeedbackEnabled
|
||||
onScanFeedbackToggle = appViewModel::setScanFeedbackEnabled,
|
||||
onUseCaseViewSelected = appViewModel::setUseCaseView
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.clean.scanner.ui
|
||||
|
||||
import com.clean.scanner.R
|
||||
|
||||
enum class UseCaseView(
|
||||
val storageKey: String,
|
||||
val titleRes: Int
|
||||
) {
|
||||
EverydayPersonal(
|
||||
storageKey = "everyday_personal",
|
||||
titleRes = R.string.use_case_everyday_personal
|
||||
),
|
||||
EventTicketing(
|
||||
storageKey = "event_ticketing",
|
||||
titleRes = R.string.use_case_event_ticketing
|
||||
),
|
||||
InventoryOperations(
|
||||
storageKey = "inventory_operations",
|
||||
titleRes = R.string.use_case_inventory_operations
|
||||
),
|
||||
FieldWorkServiceTeams(
|
||||
storageKey = "field_work_service_teams",
|
||||
titleRes = R.string.use_case_field_work
|
||||
),
|
||||
OfficeAdmin(
|
||||
storageKey = "office_admin",
|
||||
titleRes = R.string.use_case_office_admin
|
||||
),
|
||||
CommunicationShortcuts(
|
||||
storageKey = "communication_shortcuts",
|
||||
titleRes = R.string.use_case_communication_shortcuts
|
||||
),
|
||||
SecurityBrowsing(
|
||||
storageKey = "security_browsing",
|
||||
titleRes = R.string.use_case_security_browsing
|
||||
),
|
||||
OfflineLowConnectivity(
|
||||
storageKey = "offline_low_connectivity",
|
||||
titleRes = R.string.use_case_offline_low_connectivity
|
||||
),
|
||||
AccessibilitySpeed(
|
||||
storageKey = "accessibility_speed",
|
||||
titleRes = R.string.use_case_accessibility_speed
|
||||
),
|
||||
TeamHandoverTransfer(
|
||||
storageKey = "team_handover_transfer",
|
||||
titleRes = R.string.use_case_team_handover_transfer
|
||||
);
|
||||
|
||||
companion object {
|
||||
val default = EverydayPersonal
|
||||
|
||||
fun fromStorageKey(value: String?): UseCaseView {
|
||||
return entries.firstOrNull { it.storageKey == value } ?: default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class UseCaseCapabilities(
|
||||
val allowScanFromImage: Boolean = true,
|
||||
val allowBatchMode: Boolean = false,
|
||||
val allowCopy: Boolean = true,
|
||||
val allowShare: Boolean = true,
|
||||
val allowOpenUrl: Boolean = true,
|
||||
val allowAddContact: Boolean = false,
|
||||
val allowDialPhone: Boolean = false,
|
||||
val allowSendSms: Boolean = false,
|
||||
val allowSendEmail: Boolean = false,
|
||||
val allowOpenWifiSettings: Boolean = false,
|
||||
val allowAddCalendarEvent: Boolean = false,
|
||||
val allowHistoryExport: Boolean = false,
|
||||
val allowBatchShare: Boolean = true
|
||||
)
|
||||
|
||||
fun UseCaseView.capabilities(): UseCaseCapabilities {
|
||||
return when (this) {
|
||||
UseCaseView.EverydayPersonal -> UseCaseCapabilities(
|
||||
allowScanFromImage = true,
|
||||
allowBatchMode = false,
|
||||
allowCopy = true,
|
||||
allowShare = true,
|
||||
allowOpenUrl = true
|
||||
)
|
||||
|
||||
UseCaseView.EventTicketing -> UseCaseCapabilities(
|
||||
allowScanFromImage = false,
|
||||
allowBatchMode = true,
|
||||
allowCopy = true,
|
||||
allowShare = true,
|
||||
allowOpenUrl = false,
|
||||
allowBatchShare = true
|
||||
)
|
||||
|
||||
UseCaseView.InventoryOperations -> UseCaseCapabilities(
|
||||
allowScanFromImage = true,
|
||||
allowBatchMode = true,
|
||||
allowCopy = true,
|
||||
allowShare = true,
|
||||
allowOpenUrl = false,
|
||||
allowHistoryExport = true,
|
||||
allowBatchShare = true
|
||||
)
|
||||
|
||||
UseCaseView.FieldWorkServiceTeams -> UseCaseCapabilities(
|
||||
allowScanFromImage = true,
|
||||
allowBatchMode = true,
|
||||
allowCopy = true,
|
||||
allowShare = true,
|
||||
allowOpenUrl = true,
|
||||
allowHistoryExport = true,
|
||||
allowBatchShare = true
|
||||
)
|
||||
|
||||
UseCaseView.OfficeAdmin -> UseCaseCapabilities(
|
||||
allowScanFromImage = true,
|
||||
allowBatchMode = false,
|
||||
allowCopy = true,
|
||||
allowShare = true,
|
||||
allowOpenUrl = true,
|
||||
allowAddContact = true,
|
||||
allowDialPhone = true,
|
||||
allowSendSms = true,
|
||||
allowSendEmail = true,
|
||||
allowOpenWifiSettings = true,
|
||||
allowAddCalendarEvent = true
|
||||
)
|
||||
|
||||
UseCaseView.CommunicationShortcuts -> UseCaseCapabilities(
|
||||
allowScanFromImage = true,
|
||||
allowBatchMode = false,
|
||||
allowCopy = true,
|
||||
allowShare = true,
|
||||
allowOpenUrl = false,
|
||||
allowDialPhone = true,
|
||||
allowSendSms = true,
|
||||
allowSendEmail = true
|
||||
)
|
||||
|
||||
UseCaseView.SecurityBrowsing -> UseCaseCapabilities(
|
||||
allowScanFromImage = true,
|
||||
allowBatchMode = false,
|
||||
allowCopy = true,
|
||||
allowShare = true,
|
||||
allowOpenUrl = true
|
||||
)
|
||||
|
||||
UseCaseView.OfflineLowConnectivity -> UseCaseCapabilities(
|
||||
allowScanFromImage = true,
|
||||
allowBatchMode = true,
|
||||
allowCopy = true,
|
||||
allowShare = true,
|
||||
allowOpenUrl = false,
|
||||
allowBatchShare = true
|
||||
)
|
||||
|
||||
UseCaseView.AccessibilitySpeed -> UseCaseCapabilities(
|
||||
allowScanFromImage = true,
|
||||
allowBatchMode = false,
|
||||
allowCopy = true,
|
||||
allowShare = true,
|
||||
allowOpenUrl = true
|
||||
)
|
||||
|
||||
UseCaseView.TeamHandoverTransfer -> UseCaseCapabilities(
|
||||
allowScanFromImage = true,
|
||||
allowBatchMode = true,
|
||||
allowCopy = true,
|
||||
allowShare = true,
|
||||
allowOpenUrl = false,
|
||||
allowHistoryExport = true,
|
||||
allowBatchShare = true
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.clean.scanner.R
|
||||
import com.clean.scanner.domain.ScanRecord
|
||||
import com.clean.scanner.ui.UseCaseView
|
||||
import com.clean.scanner.ui.capabilities
|
||||
import com.clean.scanner.util.HistoryExportFormatter
|
||||
import com.clean.scanner.util.Intents
|
||||
import java.text.DateFormat
|
||||
@@ -35,11 +37,13 @@ import java.util.Date
|
||||
fun HistoryScreen(
|
||||
query: String,
|
||||
history: List<ScanRecord>,
|
||||
useCaseView: UseCaseView,
|
||||
onQueryChange: (String) -> Unit,
|
||||
onDelete: (Long) -> Unit,
|
||||
onClearAll: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val capabilities = useCaseView.capabilities()
|
||||
val showDeleteAll = remember { mutableStateOf(false) }
|
||||
val selectedItem = remember { mutableStateOf<ScanRecord?>(null) }
|
||||
|
||||
@@ -90,6 +94,7 @@ fun HistoryScreen(
|
||||
)
|
||||
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
if (capabilities.allowHistoryExport) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
val exportText = HistoryExportFormatter.formatText(history)
|
||||
@@ -117,6 +122,7 @@ fun HistoryScreen(
|
||||
) {
|
||||
Text(stringResource(R.string.share_json))
|
||||
}
|
||||
}
|
||||
TextButton(onClick = { showDeleteAll.value = true }) {
|
||||
Text(stringResource(R.string.delete_all))
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ internal fun OverlayIconToggle(
|
||||
@Composable
|
||||
internal fun BatchResultsPanel(
|
||||
results: List<BatchScanRecord>,
|
||||
onClear: () -> Unit
|
||||
onClear: () -> Unit,
|
||||
allowShare: Boolean
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val timeFormat = remember { DateFormat.getTimeInstance(DateFormat.SHORT) }
|
||||
@@ -125,6 +126,7 @@ internal fun BatchResultsPanel(
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
if (allowShare) {
|
||||
IconButton(onClick = { Intents.shareText(context, item.result.content) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Share,
|
||||
@@ -135,10 +137,12 @@ internal fun BatchResultsPanel(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
TextButton(onClick = onClear, enabled = results.isNotEmpty()) {
|
||||
Text(stringResource(R.string.clear_batch))
|
||||
}
|
||||
if (allowShare) {
|
||||
TextButton(
|
||||
onClick = { Intents.shareText(context, buildBatchExport(results)) },
|
||||
enabled = results.isNotEmpty()
|
||||
@@ -148,6 +152,7 @@ internal fun BatchResultsPanel(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildBatchExport(results: List<BatchScanRecord>): String {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.clean.scanner.ui.screens
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -21,6 +22,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.clean.scanner.domain.ScanResult
|
||||
@@ -38,6 +40,7 @@ private data class ResultField(
|
||||
@Composable
|
||||
internal fun ResultVisualCard(
|
||||
result: ScanResult,
|
||||
onOpenUrl: ((String) -> Unit)? = null,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val contact = remember(result.content) { ScanContentParsers.parseContact(result.content) }
|
||||
@@ -94,9 +97,19 @@ internal fun ResultVisualCard(
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = Color(0xFF4F6277)
|
||||
)
|
||||
val isClickableUrl = result.type == "URL" &&
|
||||
field.label == "Link" &&
|
||||
onOpenUrl != null
|
||||
Text(
|
||||
text = field.value,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (isClickableUrl) Color(0xFF1D4ED8) else Color.Unspecified,
|
||||
textDecoration = if (isClickableUrl) TextDecoration.Underline else null,
|
||||
modifier = if (isClickableUrl) {
|
||||
Modifier.clickable { onOpenUrl(field.value) }
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
@@ -75,7 +75,9 @@ import com.clean.scanner.data.scanner.DetectionBox
|
||||
import com.clean.scanner.data.scanner.DetectionPoint
|
||||
import com.clean.scanner.domain.ScanResult
|
||||
import com.clean.scanner.ui.BatchScanRecord
|
||||
import com.clean.scanner.ui.UseCaseView
|
||||
import com.clean.scanner.ui.components.CameraPreview
|
||||
import com.clean.scanner.ui.capabilities
|
||||
import com.clean.scanner.util.ClipboardUtil
|
||||
import com.clean.scanner.util.Intents
|
||||
import com.clean.scanner.util.ScanContentParsers
|
||||
@@ -103,6 +105,7 @@ fun ScannerScreen(
|
||||
scanFeedbackNonce: Int,
|
||||
warningsEnabled: Boolean,
|
||||
scanFeedbackEnabled: Boolean,
|
||||
useCaseView: UseCaseView,
|
||||
onScan: (ScanResult) -> Unit,
|
||||
onScanAgain: () -> Unit,
|
||||
onBatchModeChange: (Boolean) -> Unit,
|
||||
@@ -111,9 +114,26 @@ fun ScannerScreen(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val capabilities = remember(useCaseView) { useCaseView.capabilities() }
|
||||
val showBatchModeToggle = remember(useCaseView) {
|
||||
when (useCaseView) {
|
||||
UseCaseView.EventTicketing,
|
||||
UseCaseView.InventoryOperations,
|
||||
UseCaseView.FieldWorkServiceTeams,
|
||||
UseCaseView.OfflineLowConnectivity,
|
||||
UseCaseView.TeamHandoverTransfer -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
val duplicateSnackbarHostState = remember { SnackbarHostState() }
|
||||
val toneGenerator = remember { ToneGenerator(AudioManager.STREAM_NOTIFICATION, 70) }
|
||||
|
||||
LaunchedEffect(showBatchModeToggle, batchMode) {
|
||||
if (!showBatchModeToggle && batchMode) {
|
||||
onBatchModeChange(false)
|
||||
}
|
||||
}
|
||||
|
||||
var cameraGranted by remember {
|
||||
mutableStateOf(
|
||||
ContextCompat.checkSelfPermission(
|
||||
@@ -382,6 +402,7 @@ fun ScannerScreen(
|
||||
.padding(horizontal = 14.dp, vertical = 8.dp)
|
||||
)
|
||||
|
||||
if (capabilities.allowScanFromImage) {
|
||||
IconButton(
|
||||
onClick = { imagePicker.launch("image/*") },
|
||||
modifier = Modifier
|
||||
@@ -398,6 +419,21 @@ fun ScannerScreen(
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(useCaseView.titleRes),
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.padding(top = 16.dp, start = 64.dp, end = 64.dp)
|
||||
.background(
|
||||
color = Color.Black.copy(alpha = 0.4f),
|
||||
shape = RoundedCornerShape(14.dp)
|
||||
)
|
||||
.padding(horizontal = 12.dp, vertical = 6.dp)
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -415,6 +451,7 @@ fun ScannerScreen(
|
||||
showLabel = false
|
||||
)
|
||||
}
|
||||
if (showBatchModeToggle) {
|
||||
OverlayIconToggle(
|
||||
checked = batchMode,
|
||||
onCheckedChange = onBatchModeChange,
|
||||
@@ -423,12 +460,14 @@ fun ScannerScreen(
|
||||
uncheckedImageVector = Icons.AutoMirrored.Filled.ViewList
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (batchMode) {
|
||||
if (batchMode && showBatchModeToggle) {
|
||||
Box(modifier = Modifier.align(Alignment.BottomCenter)) {
|
||||
BatchResultsPanel(
|
||||
results = batchResults,
|
||||
onClear = onClearBatchResults
|
||||
onClear = onClearBatchResults,
|
||||
allowShare = capabilities.allowBatchShare
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -470,17 +509,31 @@ fun ScannerScreen(
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
if (parsedContact == null && lastResult.type != "WiFi") {
|
||||
Text(text = "${stringResource(R.string.content_type)}: ${lastResult.type}")
|
||||
ResultVisualCard(
|
||||
result = lastResult,
|
||||
onOpenUrl = { url ->
|
||||
val risk = UrlRiskScorer.score(url)
|
||||
val risky = warningsEnabled && risk.score >= 3
|
||||
if (risky) {
|
||||
pendingOpenUrl = url
|
||||
showRiskWarning = true
|
||||
} else {
|
||||
Intents.openUrl(context, url)
|
||||
}
|
||||
ResultVisualCard(result = lastResult)
|
||||
}
|
||||
)
|
||||
val hasQuickActions = capabilities.allowCopy ||
|
||||
capabilities.allowShare ||
|
||||
(capabilities.allowAddContact && parsedContact != null)
|
||||
|
||||
if (hasQuickActions) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState()),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
if (parsedContact != null) {
|
||||
if (capabilities.allowAddContact && parsedContact != null) {
|
||||
IconButton(onClick = {
|
||||
Intents.addContact(context, parsedContact, lastResult.content)
|
||||
}) {
|
||||
@@ -490,26 +543,15 @@ fun ScannerScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
if (capabilities.allowCopy) {
|
||||
IconButton(onClick = { ClipboardUtil.copy(context, lastResult.content) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ContentCopy,
|
||||
contentDescription = stringResource(R.string.copy)
|
||||
)
|
||||
}
|
||||
if (lastResult.type == "URL") {
|
||||
Button(onClick = {
|
||||
val risk = UrlRiskScorer.score(lastResult.content)
|
||||
val risky = warningsEnabled && risk.score >= 3
|
||||
if (risky) {
|
||||
pendingOpenUrl = lastResult.content
|
||||
showRiskWarning = true
|
||||
} else {
|
||||
Intents.openUrl(context, lastResult.content)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.open))
|
||||
}
|
||||
}
|
||||
if (capabilities.allowShare) {
|
||||
IconButton(onClick = { Intents.shareText(context, lastResult.content) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Share,
|
||||
@@ -517,17 +559,22 @@ fun ScannerScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (lastResult.type) {
|
||||
"Phone" -> {
|
||||
if (capabilities.allowDialPhone) {
|
||||
Button(onClick = {
|
||||
Intents.dialPhone(context, ScanContentParsers.extractPhoneNumber(lastResult.content))
|
||||
}) {
|
||||
Text(stringResource(R.string.call_number))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"SMS" -> {
|
||||
if (capabilities.allowSendSms) {
|
||||
Button(onClick = {
|
||||
val smsData = ScanContentParsers.parseSms(lastResult.content)
|
||||
Intents.sendSms(context, smsData.first, smsData.second)
|
||||
@@ -535,22 +582,28 @@ fun ScannerScreen(
|
||||
Text(stringResource(R.string.send_sms))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"Email" -> {
|
||||
if (capabilities.allowSendEmail) {
|
||||
Button(onClick = {
|
||||
Intents.sendEmail(context, ScanContentParsers.extractEmail(lastResult.content), null)
|
||||
}) {
|
||||
Text(stringResource(R.string.send_email))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"WiFi" -> {
|
||||
if (capabilities.allowOpenWifiSettings) {
|
||||
Button(onClick = { Intents.openWifiSettings(context) }) {
|
||||
Text(stringResource(R.string.open_wifi_settings))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"Calendar" -> {
|
||||
if (capabilities.allowAddCalendarEvent) {
|
||||
Button(onClick = {
|
||||
Intents.addCalendarEvent(context, parsedEvent, lastResult.content)
|
||||
}) {
|
||||
@@ -561,6 +614,7 @@ fun ScannerScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showRiskWarning && pendingOpenUrl != null) {
|
||||
AlertDialog(
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.material3.AlertDialog
|
||||
@@ -20,6 +21,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.clean.scanner.R
|
||||
import com.clean.scanner.ui.UseCaseView
|
||||
import com.clean.scanner.util.Intents
|
||||
|
||||
@Composable
|
||||
@@ -27,13 +29,16 @@ fun SettingsScreen(
|
||||
historyEnabled: Boolean,
|
||||
warningsEnabled: Boolean,
|
||||
scanFeedbackEnabled: Boolean,
|
||||
selectedUseCaseView: UseCaseView,
|
||||
onHistoryToggle: (Boolean, Boolean) -> Unit,
|
||||
onWarningsToggle: (Boolean) -> Unit,
|
||||
onScanFeedbackToggle: (Boolean) -> Unit
|
||||
onScanFeedbackToggle: (Boolean) -> Unit,
|
||||
onUseCaseViewSelected: (UseCaseView) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val showDeleteConfirm = remember { mutableStateOf(false) }
|
||||
val showFeatureRequestForm = remember { mutableStateOf(false) }
|
||||
val showUseCasePicker = remember { mutableStateOf(false) }
|
||||
val requesterNeed = remember { mutableStateOf("") }
|
||||
|
||||
if (showDeleteConfirm.value) {
|
||||
@@ -101,6 +106,34 @@ fun SettingsScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (showUseCasePicker.value) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showUseCasePicker.value = false },
|
||||
title = { Text(stringResource(R.string.select_use_case_view)) },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
UseCaseView.entries.forEach { candidate ->
|
||||
TextButton(
|
||||
onClick = {
|
||||
onUseCaseViewSelected(candidate)
|
||||
showUseCasePicker.value = false
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(text = stringResource(candidate.titleRes))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showUseCasePicker.value = false }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -129,6 +162,14 @@ fun SettingsScreen(
|
||||
Text(text = stringResource(R.string.scan_feedback))
|
||||
Switch(checked = scanFeedbackEnabled, onCheckedChange = onScanFeedbackToggle)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(text = stringResource(R.string.active_use_case_view))
|
||||
Text(text = stringResource(selectedUseCaseView.titleRes))
|
||||
TextButton(onClick = { showUseCasePicker.value = true }) {
|
||||
Text(stringResource(R.string.select_use_case_view))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Text(text = stringResource(R.string.about))
|
||||
Text(text = stringResource(R.string.version))
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<string name="delete_history_on_disable">Vorhandene Historie beim Deaktivieren löschen?</string>
|
||||
<string name="version">Version 1.0.0</string>
|
||||
<string name="licenses">Open-Source-Lizenzen</string>
|
||||
<string name="contact">Kontakt: support@example.com</string>
|
||||
<string name="contact">Kontakt: softwareapp.hb@gmail.com</string>
|
||||
<string name="content_type">Typ</string>
|
||||
<string name="content_value">Inhalt</string>
|
||||
<string name="request_camera">Kamera erlauben</string>
|
||||
@@ -57,7 +57,7 @@
|
||||
<string name="open_wifi_settings">WLAN-Einstellungen öffnen</string>
|
||||
<string name="add_contact">Kontakt hinzufügen</string>
|
||||
<string name="add_calendar_event">Kalendereintrag hinzufügen</string>
|
||||
<string name="support_email">support@example.com</string>
|
||||
<string name="support_email">softwareapp.hb@gmail.com</string>
|
||||
<string name="feature_request">Feature-Request-Formular</string>
|
||||
<string name="feature_request_title">Feature-Request</string>
|
||||
<string name="feature_request_name">Dein Name</string>
|
||||
@@ -66,4 +66,16 @@
|
||||
<string name="feature_request_subject">Feature-Request von App-Nutzer</string>
|
||||
<string name="send_request">Anfrage senden</string>
|
||||
<string name="feature_request_sent">E-Mail-App wird geöffnet...</string>
|
||||
<string name="active_use_case_view">Aktive Use-Case-Ansicht</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_event_ticketing">Events & Ticketing</string>
|
||||
<string name="use_case_inventory_operations">Inventur & Betrieb</string>
|
||||
<string name="use_case_field_work">Außendienst & Service-Teams</string>
|
||||
<string name="use_case_office_admin">Büro- & Admin-Workflows</string>
|
||||
<string name="use_case_communication_shortcuts">Kommunikations-Shortcuts</string>
|
||||
<string name="use_case_security_browsing">Sicherheitsbewusstes Browsen</string>
|
||||
<string name="use_case_offline_low_connectivity">Offline / geringe Konnektivität</string>
|
||||
<string name="use_case_accessibility_speed">Barrierefreiheit & Geschwindigkeit</string>
|
||||
<string name="use_case_team_handover_transfer">Team-Übergabe & Datentransfer</string>
|
||||
</resources>
|
||||
|
||||
@@ -66,4 +66,16 @@
|
||||
<string name="feature_request_subject">Feature request from app user</string>
|
||||
<string name="send_request">Send request</string>
|
||||
<string name="feature_request_sent">Opening email app...</string>
|
||||
<string name="active_use_case_view">Active 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_event_ticketing">Event & ticketing</string>
|
||||
<string name="use_case_inventory_operations">Inventory & operations</string>
|
||||
<string name="use_case_field_work">Field work & service teams</string>
|
||||
<string name="use_case_office_admin">Office & admin workflows</string>
|
||||
<string name="use_case_communication_shortcuts">Communication shortcuts</string>
|
||||
<string name="use_case_security_browsing">Security-conscious browsing</string>
|
||||
<string name="use_case_offline_low_connectivity">Offline / low-connectivity</string>
|
||||
<string name="use_case_accessibility_speed">Accessibility & speed</string>
|
||||
<string name="use_case_team_handover_transfer">Team handover & data transfer</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user