Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 752d4972bb |
@@ -24,8 +24,8 @@ android {
|
|||||||
applicationId = "eu.geyskens.pdfscan"
|
applicationId = "eu.geyskens.pdfscan"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 2
|
||||||
versionName = "0.1.0"
|
versionName = "0.1.1"
|
||||||
|
|
||||||
// OpenCV native libs are large; ship only the ABIs real phones use.
|
// OpenCV native libs are large; ship only the ABIs real phones use.
|
||||||
ndk {
|
ndk {
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package eu.geyskens.pdfscan.ui.screens
|
package eu.geyskens.pdfscan.ui.screens
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.media.MediaActionSound
|
||||||
import androidx.camera.core.CameraSelector
|
import androidx.camera.core.CameraSelector
|
||||||
import androidx.camera.core.ImageCapture
|
import androidx.camera.core.ImageCapture
|
||||||
import androidx.camera.core.ImageCaptureException
|
import androidx.camera.core.ImageCaptureException
|
||||||
import androidx.camera.core.Preview
|
import androidx.camera.core.Preview
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.camera.view.PreviewView
|
import androidx.camera.view.PreviewView
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -31,6 +35,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -98,6 +103,22 @@ private fun CameraContent(vm: ScanViewModel, navController: androidx.navigation.
|
|||||||
}
|
}
|
||||||
var capturing by remember { mutableStateOf(false) }
|
var capturing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// Capture feedback: system shutter sound + a brief white flash over the preview,
|
||||||
|
// so there's clear confirmation even when the phone is on silent.
|
||||||
|
val shutterSound = remember { MediaActionSound().apply { load(MediaActionSound.SHUTTER_CLICK) } }
|
||||||
|
val audioManager = remember { ContextCompat.getSystemService(context, AudioManager::class.java) }
|
||||||
|
val flash = remember { Animatable(0f) }
|
||||||
|
var captureTick by remember { mutableIntStateOf(0) }
|
||||||
|
androidx.compose.runtime.LaunchedEffect(captureTick) {
|
||||||
|
if (captureTick > 0) {
|
||||||
|
flash.snapTo(0.75f)
|
||||||
|
flash.animateTo(0f, tween(320))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
androidx.compose.runtime.DisposableEffect(Unit) {
|
||||||
|
onDispose { shutterSound.release() }
|
||||||
|
}
|
||||||
|
|
||||||
val previewView = remember {
|
val previewView = remember {
|
||||||
PreviewView(context).apply { scaleType = PreviewView.ScaleType.FILL_CENTER }
|
PreviewView(context).apply { scaleType = PreviewView.ScaleType.FILL_CENTER }
|
||||||
}
|
}
|
||||||
@@ -146,6 +167,11 @@ private fun CameraContent(vm: ScanViewModel, navController: androidx.navigation.
|
|||||||
onClick = {
|
onClick = {
|
||||||
if (capturing) return@FilledIconButton
|
if (capturing) return@FilledIconButton
|
||||||
capturing = true
|
capturing = true
|
||||||
|
// Flash always; play the shutter sound only when the ringer isn't silenced.
|
||||||
|
if (audioManager?.ringerMode == AudioManager.RINGER_MODE_NORMAL) {
|
||||||
|
shutterSound.play(MediaActionSound.SHUTTER_CLICK)
|
||||||
|
}
|
||||||
|
captureTick++
|
||||||
val file = File(vm.capturesDir(), "cap_${System.currentTimeMillis()}.jpg")
|
val file = File(vm.capturesDir(), "cap_${System.currentTimeMillis()}.jpg")
|
||||||
val options = ImageCapture.OutputFileOptions.Builder(file).build()
|
val options = ImageCapture.OutputFileOptions.Builder(file).build()
|
||||||
imageCapture.takePicture(
|
imageCapture.takePicture(
|
||||||
@@ -183,6 +209,11 @@ private fun CameraContent(vm: ScanViewModel, navController: androidx.navigation.
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutter flash overlay (drawn on top, fades out quickly).
|
||||||
|
if (flash.value > 0f) {
|
||||||
|
Box(Modifier.fillMaxSize().background(Color.White.copy(alpha = flash.value)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
androidx.compose.runtime.DisposableEffect(Unit) {
|
androidx.compose.runtime.DisposableEffect(Unit) {
|
||||||
|
|||||||
@@ -155,6 +155,11 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.paperlessUrl.isNotBlank() || !settings.hasToken) {
|
if (!settings.paperlessUrl.isNotBlank() || !settings.hasToken) {
|
||||||
|
Text(
|
||||||
|
"Configureer eerst Paperless (server-URL + token) in de instellingen om te kunnen uploaden.",
|
||||||
|
style = androidx.compose.material3.MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color(0xFF9A9A9A),
|
||||||
|
)
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = { navController.navigate(Routes.SETTINGS) },
|
onClick = { navController.navigate(Routes.SETTINGS) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -51,8 +53,10 @@ fun SettingsScreen(vm: ScanViewModel, navController: NavController) {
|
|||||||
var token by remember { mutableStateOf(vm.currentToken().orEmpty()) }
|
var token by remember { mutableStateOf(vm.currentToken().orEmpty()) }
|
||||||
var testing by remember { mutableStateOf(false) }
|
var testing by remember { mutableStateOf(false) }
|
||||||
var testResult by remember { mutableStateOf<String?>(null) }
|
var testResult by remember { mutableStateOf<String?>(null) }
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("Instellingen") },
|
title = { Text("Instellingen") },
|
||||||
@@ -116,7 +120,12 @@ fun SettingsScreen(vm: ScanViewModel, navController: NavController) {
|
|||||||
) { Text("Verbinding testen") }
|
) { Text("Verbinding testen") }
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = { scope.launch { vm.savePaperless(url, token) } },
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
vm.savePaperless(url, token)
|
||||||
|
snackbarHostState.showSnackbar("Instellingen opgeslagen ✓")
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
) { Text("Opslaan") }
|
) { Text("Opslaan") }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user