From 55daaf16b790ea371ca6e0f15e6ff0a39b65b9a8 Mon Sep 17 00:00:00 2001 From: DP-Hridayan Date: Wed, 5 Aug 2026 21:23:11 +0530 Subject: [PATCH 1/2] refactor(history): update swipe-to-dismiss implementation Migrate the item deletion logic from the state's `confirmValueChange` callback to the `SwipeToDismissBox` `onDismiss` parameter. This aligns with the standard pattern for handling dismissal events in Compose. Increase the `positionalThreshold` from 0.5 to 0.75 to require a more intentional swipe gesture, reducing the likelihood of accidental item deletions. --- .../history/presentation/screen/HistoryScreen.kt | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/skeler/scanely/history/presentation/screen/HistoryScreen.kt b/app/src/main/java/com/skeler/scanely/history/presentation/screen/HistoryScreen.kt index 75af3d1..6af9dbe 100644 --- a/app/src/main/java/com/skeler/scanely/history/presentation/screen/HistoryScreen.kt +++ b/app/src/main/java/com/skeler/scanely/history/presentation/screen/HistoryScreen.kt @@ -36,7 +36,6 @@ import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SwipeToDismissBox -import androidx.compose.material3.SwipeToDismissBoxValue import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults @@ -181,19 +180,14 @@ fun HistoryScreen() { ) { items(historyItems, key = { it.id }) { item -> val dismissState = rememberSwipeToDismissBoxState( - confirmValueChange = { value -> - if (value == SwipeToDismissBoxValue.EndToStart) { - historyViewModel.deleteItem(item.id) - true - } else { - false - } - }, - positionalThreshold = { totalDistance -> totalDistance * 0.5f } + positionalThreshold = { totalDistance -> totalDistance * 0.75f } ) SwipeToDismissBox( state = dismissState, + onDismiss = { + historyViewModel.deleteItem(item.id) + }, backgroundContent = { Box( modifier = Modifier From 3f3966f494972a504ba3227357606b8009caca3b Mon Sep 17 00:00:00 2001 From: DP-Hridayan Date: Wed, 5 Aug 2026 21:29:18 +0530 Subject: [PATCH 2/2] Updated deprecated SheetState definition in ModalBottomSheets --- .../ui/components/ProductDetailSheet.kt | 12 +++++++-- .../scanely/ui/components/RateLimitSheet.kt | 17 +++++++------ .../ui/screens/BarcodeScannerScreen.kt | 25 +++++++++++++++---- .../skeler/scanely/ui/screens/HomeScreen.kt | 15 +++++++---- .../scanely/ui/screens/ScanReviewScreen.kt | 16 +++++++++--- 5 files changed, 63 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/skeler/scanely/ui/components/ProductDetailSheet.kt b/app/src/main/java/com/skeler/scanely/ui/components/ProductDetailSheet.kt index 417dc1d..8d01983 100644 --- a/app/src/main/java/com/skeler/scanely/ui/components/ProductDetailSheet.kt +++ b/app/src/main/java/com/skeler/scanely/ui/components/ProductDetailSheet.kt @@ -28,8 +28,9 @@ import androidx.compose.material3.Icon import androidx.compose.material3.LoadingIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.SheetValue import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.material3.rememberBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -55,7 +56,10 @@ fun ProductDetailSheet( isLoading: Boolean, onDismiss: () -> Unit ) { - val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val sheetState = rememberBottomSheetState( + initialValue = SheetValue.Hidden, + enabledValues = setOf(SheetValue.Hidden, SheetValue.Expanded) + ) ModalBottomSheet( onDismissRequest = onDismiss, @@ -159,15 +163,19 @@ private fun ProductContent(product: ProductInfo) { ProductCategory.FOOD, ProductCategory.PET_FOOD -> { product.foodData?.let { FoodContentSection(it) } } + ProductCategory.BOOK -> { product.bookData?.let { BookContentSection(it) } } + ProductCategory.MEDICINE -> { product.medicineData?.let { MedicineContentSection(it) } } + ProductCategory.COSMETICS -> { product.cosmeticsData?.let { CosmeticsContentSection(it) } } + ProductCategory.GENERIC -> { product.description?.let { Text(it, style = MaterialTheme.typography.bodyMedium) diff --git a/app/src/main/java/com/skeler/scanely/ui/components/RateLimitSheet.kt b/app/src/main/java/com/skeler/scanely/ui/components/RateLimitSheet.kt index bd39c9f..0cbc6dd 100644 --- a/app/src/main/java/com/skeler/scanely/ui/components/RateLimitSheet.kt +++ b/app/src/main/java/com/skeler/scanely/ui/components/RateLimitSheet.kt @@ -16,9 +16,9 @@ import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.LinearWavyProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.SheetState +import androidx.compose.material3.SheetValue import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.material3.rememberBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -38,8 +38,11 @@ fun RateLimitSheet( modifier: Modifier = Modifier, adAvailable: Boolean = false, onWatchAd: (() -> Unit)? = null, - sheetState: SheetState = rememberModalBottomSheetState() ) { + val sheetState = rememberBottomSheetState( + initialValue = SheetValue.Hidden, + enabledValues = setOf(SheetValue.Hidden, SheetValue.Expanded) + ) val targetProgress = 1f - (remainingSeconds.toFloat() / RATE_LIMIT_SECONDS) val animatedProgress by animateFloatAsState( targetValue = targetProgress, @@ -73,8 +76,8 @@ fun RateLimitSheet( Text( text = "Scanly's AI runs on the developer's free-tier keys — shared " + - "across Gemini, Mistral OCR, OpenRouter and Hugging Face — " + - "so scans pause for a moment when they get busy.", + "across Gemini, Mistral OCR, OpenRouter and Hugging Face — " + + "so scans pause for a moment when they get busy.", style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center @@ -84,8 +87,8 @@ fun RateLimitSheet( Text( text = "Add your own key for any provider in Settings → AI Providers to " + - "remove this limit entirely. Even a free tier works — it just runs " + - "on your quota instead of the shared one.", + "remove this limit entirely. Even a free tier works — it just runs " + + "on your quota instead of the shared one.", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center diff --git a/app/src/main/java/com/skeler/scanely/ui/screens/BarcodeScannerScreen.kt b/app/src/main/java/com/skeler/scanely/ui/screens/BarcodeScannerScreen.kt index 4eac8c2..46b2ad8 100644 --- a/app/src/main/java/com/skeler/scanely/ui/screens/BarcodeScannerScreen.kt +++ b/app/src/main/java/com/skeler/scanely/ui/screens/BarcodeScannerScreen.kt @@ -35,8 +35,9 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.SheetValue import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.material3.rememberBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -95,8 +96,14 @@ fun BarcodeScannerScreen( var showActionsSheet by remember { mutableStateOf(false) } var textToShow by remember { mutableStateOf(null) } var isProcessingGallery by remember { mutableStateOf(false) } - val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) - val textSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val sheetState = rememberBottomSheetState( + initialValue = SheetValue.Hidden, + enabledValues = setOf(SheetValue.Hidden, SheetValue.Expanded) + ) + val textSheetState = rememberBottomSheetState( + initialValue = SheetValue.Hidden, + enabledValues = setOf(SheetValue.Hidden, SheetValue.Expanded) + ) var showProductSheet by remember { mutableStateOf(false) } var lookupResult by remember { mutableStateOf(null) } @@ -218,7 +225,12 @@ fun BarcodeScannerScreen( modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically ) { - Icon(Icons.Rounded.PhotoLibrary, null, tint = Color.White, modifier = Modifier.size(18.dp)) + Icon( + Icons.Rounded.PhotoLibrary, + null, + tint = Color.White, + modifier = Modifier.size(18.dp) + ) Spacer(modifier = Modifier.width(8.dp)) Text( text = if (isProcessingGallery) "Scanning..." else "Upload from gallery", @@ -269,9 +281,11 @@ fun BarcodeScannerScreen( showActionsSheet = false textToShow = action.text } + is ScanAction.LookupProduct -> { lookupProduct(action.barcode) } + else -> { showActionsSheet = false ActionExecutor.execute(context, action) @@ -290,7 +304,8 @@ fun BarcodeScannerScreen( TextDetailSheet( text = text, onCopy = { - val clipboard = context.getSystemService(android.content.ClipboardManager::class.java) + val clipboard = + context.getSystemService(android.content.ClipboardManager::class.java) clipboard.setPrimaryClip(ClipData.newPlainText("Barcode Content", text)) Toast.makeText(context, "Copied to clipboard", Toast.LENGTH_SHORT).show() }, diff --git a/app/src/main/java/com/skeler/scanely/ui/screens/HomeScreen.kt b/app/src/main/java/com/skeler/scanely/ui/screens/HomeScreen.kt index 0ea8a28..5a6ffe2 100644 --- a/app/src/main/java/com/skeler/scanely/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/skeler/scanely/ui/screens/HomeScreen.kt @@ -15,11 +15,12 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.SheetValue import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.material3.rememberBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -55,7 +56,6 @@ import com.skeler.scanely.ui.components.rememberMultiGalleryPicker import com.skeler.scanely.ui.viewmodel.AiScanViewModel import com.skeler.scanely.ui.viewmodel.OcrViewModel import com.skeler.scanely.ui.viewmodel.UnifiedScanViewModel -import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -70,7 +70,10 @@ fun HomeScreen() { val snackbarHostState = remember { SnackbarHostState() } var showAiBottomSheet by remember { mutableStateOf(false) } - val aiSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val aiSheetState = rememberBottomSheetState( + initialValue = SheetValue.Hidden, + enabledValues = setOf(SheetValue.Hidden, SheetValue.Expanded) + ) var pendingAiMode by remember { mutableStateOf(null) } var pendingAiProvider by remember { mutableStateOf(AiProvider.DEFAULT) } @@ -107,7 +110,8 @@ fun HomeScreen() { uri, android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION ) - } catch (_: Exception) { } + } catch (_: Exception) { + } if (scanViewModel.triggerAiWithRateLimit(provider) { scanViewModel.onNewScanSelected() aiViewModel.processMultipleFiles(listOf(uri), mode, provider) @@ -137,7 +141,8 @@ fun HomeScreen() { uri, android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION ) - } catch (_: Exception) { } + } catch (_: Exception) { + } aiViewModel.clearResult() ocrViewModel.clearResult() scanViewModel.onNewScanSelected() diff --git a/app/src/main/java/com/skeler/scanely/ui/screens/ScanReviewScreen.kt b/app/src/main/java/com/skeler/scanely/ui/screens/ScanReviewScreen.kt index 1362b8f..bcdb413 100644 --- a/app/src/main/java/com/skeler/scanely/ui/screens/ScanReviewScreen.kt +++ b/app/src/main/java/com/skeler/scanely/ui/screens/ScanReviewScreen.kt @@ -47,6 +47,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold +import androidx.compose.material3.SheetValue import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface @@ -54,7 +55,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.material3.rememberBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -504,7 +505,11 @@ private fun ExportBar(enabled: Boolean, onClick: () -> Unit) { .height(60.dp), shape = CircleShape ) { - Icon(Icons.Rounded.IosShare, contentDescription = null, modifier = Modifier.size(20.dp)) + Icon( + Icons.Rounded.IosShare, + contentDescription = null, + modifier = Modifier.size(20.dp) + ) Spacer(Modifier.size(10.dp)) Text( "Save or share", @@ -532,9 +537,14 @@ private fun ExportSheet( ) { val pageLabel = "$pageCount page${if (pageCount > 1) "s" else ""}" + val sheetState = rememberBottomSheetState( + initialValue = SheetValue.Hidden, + enabledValues = setOf(SheetValue.Hidden, SheetValue.Expanded) + ) + ModalBottomSheet( onDismissRequest = onDismiss, - sheetState = rememberModalBottomSheetState() + sheetState = sheetState ) { Column( modifier = Modifier