diff --git a/app/src/main/java/com/skeler/scanely/MainActivity.kt b/app/src/main/java/com/skeler/scanely/MainActivity.kt index 202303b..d797eee 100644 --- a/app/src/main/java/com/skeler/scanely/MainActivity.kt +++ b/app/src/main/java/com/skeler/scanely/MainActivity.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier import com.skeler.scanely.navigation.ScanelyNavigation import com.skeler.scanely.ui.theme.ScanelyTheme import com.skeler.scanely.ui.theme.ThemeMode +import androidx.core.content.edit class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -22,7 +23,7 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { // Load preferences directly (no remember needed for the reference itself, but needed for the state) - val prefs = getPreferences(Context.MODE_PRIVATE) + val prefs = getPreferences(MODE_PRIVATE) // Load initial state immediately // We use mutableStateOf with the loaded value directly to ensure it initializes correctly. @@ -44,13 +45,13 @@ class MainActivity : ComponentActivity() { onThemeChanged = { newMode -> themeMode = newMode // Commit immediately to ensure persistence - prefs.edit().putString("theme_mode", newMode.name).commit() + prefs.edit(commit = true) { putString("theme_mode", newMode.name) } }, ocrLanguages = ocrLanguages, onOcrLanguagesChanged = { newLangs -> ocrLanguages = newLangs // Commit immediately - prefs.edit().putStringSet("ocr_langs", newLangs).commit() + prefs.edit(commit = true) { putStringSet("ocr_langs", newLangs) } } ) } diff --git a/app/src/main/java/com/skeler/scanely/navigation/Navigation.kt b/app/src/main/java/com/skeler/scanely/navigation/Navigation.kt index e63fc46..e862096 100644 --- a/app/src/main/java/com/skeler/scanely/navigation/Navigation.kt +++ b/app/src/main/java/com/skeler/scanely/navigation/Navigation.kt @@ -39,6 +39,7 @@ object Routes { const val CAMERA = "camera" const val RESULTS = "results" const val BARCODE_SCANNER = "barcode_scanner" + const val HISTORY = "history" } @@ -213,13 +214,11 @@ fun ScanelyNavigation( onThemeChanged = onThemeChanged, ocrLanguages = ocrLanguages, onOcrLanguagesChanged = onOcrLanguagesChanged, - onCaptureClick = { navController.navigate(Routes.CAMERA) }, onGalleryClick = { launchGalleryPicker() }, onPdfClick = { pdfLauncher.launch(arrayOf("application/pdf")) }, - onBarcodeClick = { navController.navigate(Routes.BARCODE_SCANNER) }, - onHistoryClick = { navController.navigate("history") } + ) } 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 2a25ea4..7b59338 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 @@ -1,5 +1,21 @@ package com.skeler.scanely.ui.screens +/** + * Clean & Simple HomeScreen + * - Center: 3 Main Actions (Camera, Gallery, PDF) + * - Top: App Bar with Settings + * - Bottom: History Link + * - Settings: Theme Selection + Developer Info + */ +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -11,19 +27,19 @@ 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.selection.toggleable import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.History import androidx.compose.material.icons.filled.QrCodeScanner import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.outlined.CameraAlt import androidx.compose.material.icons.outlined.PhotoLibrary import androidx.compose.material.icons.outlined.PictureAsPdf -import androidx.compose.material3.AlertDialog import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.HorizontalDivider @@ -32,11 +48,14 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -48,21 +67,13 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import com.skeler.scanely.R +import com.skeler.scanely.navigation.LocalNavController +import com.skeler.scanely.navigation.Routes import com.skeler.scanely.ui.theme.ThemeMode -/** - * Clean & Simple HomeScreen - * - Center: 3 Main Actions (Camera, Gallery, PDF) - * - Top: App Bar with Settings - * - Bottom: History Link - * - Settings: Theme Selection + Developer Info - */ -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.selection.toggleable -import androidx.compose.material3.Checkbox - @OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeScreen( @@ -70,13 +81,12 @@ fun HomeScreen( onThemeChanged: (ThemeMode) -> Unit, ocrLanguages: Set, onOcrLanguagesChanged: (Set) -> Unit, - onCaptureClick: () -> Unit = {}, onGalleryClick: () -> Unit = {}, onPdfClick: () -> Unit = {}, - onBarcodeClick: () -> Unit = {}, onSettingsClick: () -> Unit = {}, - onHistoryClick: () -> Unit = {} ) { + val navController = LocalNavController.current + var showSettingsDialog by remember { mutableStateOf(false) } if (showSettingsDialog) { @@ -131,48 +141,48 @@ fun HomeScreen( textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.onBackground ) - + Spacer(modifier = Modifier.height(48.dp)) - + MainActionButton( icon = Icons.Outlined.CameraAlt, title = "Capture Photo", subtitle = "Scan text using camera", - onClick = onCaptureClick + onClick = { navController.navigate(Routes.CAMERA) } ) - + Spacer(modifier = Modifier.height(16.dp)) - + MainActionButton( icon = Icons.Outlined.PhotoLibrary, title = "From Gallery", subtitle = "Import image file", onClick = onGalleryClick ) - + Spacer(modifier = Modifier.height(16.dp)) - + MainActionButton( icon = Icons.Outlined.PictureAsPdf, title = "Extract PDF", subtitle = "Import PDF document", onClick = onPdfClick ) - + Spacer(modifier = Modifier.height(16.dp)) MainActionButton( icon = Icons.Default.QrCodeScanner, title = "Scan Barcode/QR", subtitle = "Scan QR, Barcodes & More", - onClick = onBarcodeClick + onClick = { navController.navigate(Routes.BARCODE_SCANNER) } ) - + Spacer(modifier = Modifier.height(48.dp)) - + // History Link FilledTonalButton( - onClick = onHistoryClick, + onClick = { navController.navigate(Routes.HISTORY) }, shape = MaterialTheme.shapes.medium ) { Icon( @@ -210,49 +220,47 @@ private fun SettingsDialog( ) { // Use derivedStateOf for expensive computed values val allKeys = remember { SUPPORTED_LANGUAGES_MAP.keys } - val allSelected by androidx.compose.runtime.derivedStateOf { - ocrLanguages.containsAll(allKeys) + val allSelected by remember { + derivedStateOf { + ocrLanguages.containsAll(allKeys) + } } - + // Optimized Dialog with high-quality spring animation - androidx.compose.ui.window.Dialog( + Dialog( onDismissRequest = onDismiss, - properties = androidx.compose.ui.window.DialogProperties( + properties = DialogProperties( usePlatformDefaultWidth = false, decorFitsSystemWindows = false ) ) { // Scrim with blur effect (mocked by color alpha) - val animatedAlpha by androidx.compose.animation.core.animateFloatAsState( - targetValue = 1f, - animationSpec = androidx.compose.animation.core.tween(300) - ) - - androidx.compose.animation.AnimatedVisibility( + + AnimatedVisibility( visible = true, - enter = androidx.compose.animation.slideInVertically( + enter = slideInVertically( initialOffsetY = { fullHeight -> fullHeight / 2 }, // Slide up from bottom - animationSpec = androidx.compose.animation.core.spring( - dampingRatio = androidx.compose.animation.core.Spring.DampingRatioMediumBouncy, - stiffness = androidx.compose.animation.core.Spring.StiffnessLow + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow ) - ) + androidx.compose.animation.fadeIn( - animationSpec = androidx.compose.animation.core.tween(300) - ) + androidx.compose.animation.scaleIn( + ) + fadeIn( + animationSpec = tween(300) + ) + scaleIn( initialScale = 0.9f, - animationSpec = androidx.compose.animation.core.spring( - dampingRatio = androidx.compose.animation.core.Spring.DampingRatioMediumBouncy, - stiffness = androidx.compose.animation.core.Spring.StiffnessLow + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow ) ), - exit = androidx.compose.animation.slideOutVertically( + exit = slideOutVertically( targetOffsetY = { it / 2 }, - animationSpec = androidx.compose.animation.core.tween(200) - ) + androidx.compose.animation.fadeOut( - animationSpec = androidx.compose.animation.core.tween(200) + animationSpec = tween(200) + ) + fadeOut( + animationSpec = tween(200) ) ) { - androidx.compose.material3.Surface( + Surface( modifier = Modifier .fillMaxWidth(0.92f) .padding(vertical = 24.dp), @@ -269,9 +277,9 @@ private fun SettingsDialog( fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface ) - + Spacer(modifier = Modifier.height(16.dp)) - + // Scrollable Content Column( modifier = Modifier @@ -285,16 +293,21 @@ private fun SettingsDialog( fontWeight = FontWeight.SemiBold ) Spacer(modifier = Modifier.height(8.dp)) - - ThemeOptionRow("System Default", ThemeMode.System, currentTheme, onThemeChange) + + ThemeOptionRow( + "System Default", + ThemeMode.System, + currentTheme, + onThemeChange + ) ThemeOptionRow("Light Mode", ThemeMode.Light, currentTheme, onThemeChange) ThemeOptionRow("Dark Mode", ThemeMode.Dark, currentTheme, onThemeChange) ThemeOptionRow("OLED Black", ThemeMode.Oled, currentTheme, onThemeChange) - + Spacer(modifier = Modifier.height(24.dp)) HorizontalDivider() Spacer(modifier = Modifier.height(16.dp)) - + Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, @@ -306,7 +319,7 @@ private fun SettingsDialog( color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.SemiBold ) - + TextButton(onClick = { if (allSelected) { onOcrLanguagesChanged(setOf("eng")) @@ -317,12 +330,12 @@ private fun SettingsDialog( Text(if (allSelected) "None" else "All") } } - + Spacer(modifier = Modifier.height(8.dp)) - + // Use key() for stable recomposition SUPPORTED_LANGUAGES_MAP.forEach { (code, name) -> - androidx.compose.runtime.key(code) { + key(code) { LanguageCheckboxRow( label = name, checked = ocrLanguages.contains(code), @@ -340,14 +353,14 @@ private fun SettingsDialog( ) } } - + Spacer(modifier = Modifier.height(24.dp)) HorizontalDivider() Spacer(modifier = Modifier.height(16.dp)) - + // About Section val uriHandler = LocalUriHandler.current - + Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally @@ -378,22 +391,22 @@ private fun SettingsDialog( ) } } - + Spacer(modifier = Modifier.height(12.dp)) - + // Developer Info Text( text = "Developed by Azyrn Skeler", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) - + Spacer(modifier = Modifier.height(8.dp)) - + // GitHub Link Row Row( modifier = Modifier - .clickable { + .clickable { uriHandler.openUri("https://github.com/Azyrn/Scanly") } .padding(horizontal = 16.dp, vertical = 8.dp), @@ -414,9 +427,9 @@ private fun SettingsDialog( } } } - + Spacer(modifier = Modifier.height(24.dp)) - + Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End