From ebd5453b650112a0c246c9d3eb6e94ad82bf6771 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Nicolas <6371790+pynicolas@users.noreply.github.com> Date: Wed, 4 Jun 2025 16:41:38 +0200 Subject: [PATCH] Ability to delete one of the scanned pages --- .../org/mydomain/myscan/ImageRepository.kt | 5 ++ .../java/org/mydomain/myscan/MainViewModel.kt | 5 ++ .../mydomain/myscan/view/FinalizeDocument.kt | 69 ++++++++++++------- .../mydomain/myscan/ImageRepositoryTest.kt | 12 ++++ 4 files changed, 68 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/mydomain/myscan/ImageRepository.kt b/app/src/main/java/org/mydomain/myscan/ImageRepository.kt index 4054dfd..c70abf6 100644 --- a/app/src/main/java/org/mydomain/myscan/ImageRepository.kt +++ b/app/src/main/java/org/mydomain/myscan/ImageRepository.kt @@ -33,4 +33,9 @@ class ImageRepository(appFilesDir: File) { throw IllegalArgumentException("No image for id: $id") } + fun delete(id: String) { + val file = File(scanDir, id) + file.delete() + fileNames.remove(id) + } } \ No newline at end of file diff --git a/app/src/main/java/org/mydomain/myscan/MainViewModel.kt b/app/src/main/java/org/mydomain/myscan/MainViewModel.kt index 212d8df..04863f9 100644 --- a/app/src/main/java/org/mydomain/myscan/MainViewModel.kt +++ b/app/src/main/java/org/mydomain/myscan/MainViewModel.kt @@ -116,6 +116,11 @@ class MainViewModel( _pageIds.value = imageRepository.imageIds() } + fun deletePage(id: String) { + imageRepository.delete(id) + _pageIds.value = imageRepository.imageIds() + } + fun pageCount(): Int = pageIds.value.size fun getBitmap(id: String): Bitmap { diff --git a/app/src/main/java/org/mydomain/myscan/view/FinalizeDocument.kt b/app/src/main/java/org/mydomain/myscan/view/FinalizeDocument.kt index 2e498b9..ebd9fad 100644 --- a/app/src/main/java/org/mydomain/myscan/view/FinalizeDocument.kt +++ b/app/src/main/java/org/mydomain/myscan/view/FinalizeDocument.kt @@ -1,8 +1,10 @@ package org.mydomain.myscan.view import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues @@ -14,10 +16,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Share import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Button @@ -35,7 +39,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel @@ -49,7 +52,6 @@ fun FinalizeDocumentScreen( onSavePressed: () -> Unit, onSharePressed: () -> Unit, ) { - val pageIds by viewModel.pageIds.collectAsStateWithLifecycle() Scaffold ( topBar = { TopAppBar( @@ -80,36 +82,57 @@ fun FinalizeDocumentScreen( } } } - ) { padding -> - Column(modifier = Modifier + ) { padding -> DocumentPreview(padding, viewModel) } +} + +@Composable +private fun DocumentPreview( + padding: PaddingValues, + viewModel: MainViewModel +) { + val pageIds by viewModel.pageIds.collectAsStateWithLifecycle() + Column( + modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) - .padding(padding)) { - - Text( - "Pages", - modifier = Modifier.padding(start = 16.dp, top = 16.dp), - style = MaterialTheme.typography.titleMedium - ) - FlowRow ( - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - pageIds.forEachIndexed { index, id -> - Column(horizontalAlignment = Alignment.CenterHorizontally) { + .padding(padding) + ) { + Text( + "Pages", + modifier = Modifier.padding(start = 16.dp, top = 16.dp), + style = MaterialTheme.typography.titleMedium + ) + FlowRow( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + pageIds.forEachIndexed { index, id -> + Column(horizontalAlignment = Alignment.CenterHorizontally) { + // TODO Display small images rather than big ones + // TODO Make it possible to zoom on an image + Box { Image( bitmap = viewModel.getBitmap(id).asImageBitmap(), contentDescription = "Page ${index + 1}", modifier = Modifier .size(160.dp) + .padding(4.dp) .clip(RoundedCornerShape(8.dp)) - .border(1.dp, Color.DarkGray, RoundedCornerShape(8.dp)), - contentScale = ContentScale.Fit + .border(1.dp, Color.DarkGray) ) - Text("Page ${index + 1}") + + IconButton( + onClick = { viewModel.deletePage(id) }, + modifier = Modifier + .align(Alignment.TopEnd) + .size(24.dp) + .background(Color.Black.copy(alpha = 0.5f), shape = CircleShape) + ) { + Icon(Icons.Default.Delete, contentDescription = "Delete", tint = Color.White) + } } } } diff --git a/app/src/test/java/org/mydomain/myscan/ImageRepositoryTest.kt b/app/src/test/java/org/mydomain/myscan/ImageRepositoryTest.kt index 7fb3694..6b3c62f 100644 --- a/app/src/test/java/org/mydomain/myscan/ImageRepositoryTest.kt +++ b/app/src/test/java/org/mydomain/myscan/ImageRepositoryTest.kt @@ -35,6 +35,18 @@ class ImageRepositoryTest { assertThat(repo.getContent(repo.imageIds()[0])).isEqualTo(bytes) } + @Test + fun delete_image() { + val repo = repo() + val bytes = byteArrayOf(101, 102, 103) + repo.add(bytes) + assertThat(repo.imageIds()).hasSize(1) + repo.delete(repo.imageIds()[0]) + assertThat(repo.imageIds()).isEmpty() + val repo2 = repo() + assertThat(repo2.imageIds()).isEmpty() + } + @Test fun `should find existing files at initialization`() { val bytes = byteArrayOf(101, 102, 103)