Allow export to JPEG (#68)
* SettingScreen: export format * Allow export to JPEG * Adjust messages in UI to work with PDF and JPEG * Message for file size should depend on number of files * Fix call to MediaScanner to avoid crash when scanning multiple files * Fix strange handling of Open button
This commit is contained in:
@@ -23,7 +23,7 @@ import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import org.fairscan.app.data.FileLogger
|
||||
import org.fairscan.app.data.ImageRepository
|
||||
import org.fairscan.app.data.LogRepository
|
||||
import org.fairscan.app.data.PdfFileManager
|
||||
import org.fairscan.app.data.FileManager
|
||||
import org.fairscan.app.data.recentDocumentsDataStore
|
||||
import org.fairscan.app.domain.ImageSegmentationService
|
||||
import org.fairscan.app.platform.AndroidPdfWriter
|
||||
@@ -51,8 +51,9 @@ class AppContainer(context: Context) {
|
||||
private val density = context.resources.displayMetrics.density
|
||||
private val thumbnailSizePx = (THUMBNAIL_SIZE_DP * density).toInt()
|
||||
val imageRepository = ImageRepository(context.filesDir, OpenCvTransformations(), thumbnailSizePx)
|
||||
val pdfFileManager = PdfFileManager(
|
||||
File(context.cacheDir, "pdfs"),
|
||||
val preparationDir = File(context.cacheDir, "pdfs")
|
||||
val fileManager = FileManager(
|
||||
preparationDir,
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||
AndroidPdfWriter()
|
||||
)
|
||||
|
||||
@@ -45,12 +45,10 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.content.ContextCompat.checkSelfPermission
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.fairscan.app.data.GeneratedPdf
|
||||
import org.fairscan.app.ui.Navigation
|
||||
import org.fairscan.app.ui.Screen
|
||||
import org.fairscan.app.ui.components.rememberCameraPermissionState
|
||||
@@ -63,18 +61,18 @@ import org.fairscan.app.ui.screens.camera.CameraEvent
|
||||
import org.fairscan.app.ui.screens.camera.CameraScreen
|
||||
import org.fairscan.app.ui.screens.camera.CameraViewModel
|
||||
import org.fairscan.app.ui.screens.export.ExportEvent
|
||||
import org.fairscan.app.ui.screens.export.ExportResult
|
||||
import org.fairscan.app.ui.screens.export.ExportScreenWrapper
|
||||
import org.fairscan.app.ui.screens.export.ExportViewModel
|
||||
import org.fairscan.app.ui.screens.export.PdfGenerationActions
|
||||
import org.fairscan.app.ui.screens.export.ExportActions
|
||||
import org.fairscan.app.ui.screens.home.HomeScreen
|
||||
import org.fairscan.app.ui.screens.home.HomeViewModel
|
||||
import org.fairscan.app.ui.screens.settings.ExportFormat
|
||||
import org.fairscan.app.ui.screens.settings.SettingsScreen
|
||||
import org.fairscan.app.ui.screens.settings.SettingsViewModel
|
||||
import org.fairscan.app.ui.theme.FairScanTheme
|
||||
import org.opencv.android.OpenCVLoader
|
||||
|
||||
private const val PDF_MIME_TYPE = "application/pdf"
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -89,7 +87,7 @@ class MainActivity : ComponentActivity() {
|
||||
val settingsViewModel: SettingsViewModel
|
||||
by viewModels { appContainer.settingsViewModelFactory }
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
exportViewModel.cleanUpOldPdfs(1000 * 3600)
|
||||
exportViewModel.cleanUpOldPreparedFiles(1000 * 3600)
|
||||
}
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
@@ -97,6 +95,7 @@ class MainActivity : ComponentActivity() {
|
||||
val currentScreen by viewModel.currentScreen.collectAsStateWithLifecycle()
|
||||
val liveAnalysisState by cameraViewModel.liveAnalysisState.collectAsStateWithLifecycle()
|
||||
val document by viewModel.documentUiModel.collectAsStateWithLifecycle()
|
||||
val exportUiState by exportViewModel.uiState.collectAsStateWithLifecycle()
|
||||
val cameraPermission = rememberCameraPermissionState()
|
||||
CollectCameraEvents(cameraViewModel, viewModel)
|
||||
CollectExportEvents(context, exportViewModel)
|
||||
@@ -113,7 +112,7 @@ class MainActivity : ComponentActivity() {
|
||||
navigation = navigation,
|
||||
onClearScan = { viewModel.startNewDocument() },
|
||||
recentDocuments = recentDocs,
|
||||
onOpenPdf = { fileUri -> openPdf(fileUri) }
|
||||
onOpenPdf = { fileUri -> openUri(fileUri, ExportFormat.PDF.mimeType) }
|
||||
)
|
||||
}
|
||||
is Screen.Main.Camera -> {
|
||||
@@ -140,13 +139,13 @@ class MainActivity : ComponentActivity() {
|
||||
is Screen.Main.Export -> {
|
||||
ExportScreenWrapper(
|
||||
navigation = navigation,
|
||||
pdfActions = PdfGenerationActions(
|
||||
startGeneration = exportViewModel::startPdfGeneration,
|
||||
uiState = exportUiState,
|
||||
pdfActions = ExportActions(
|
||||
initializeExportScreen = exportViewModel::initializeExportScreen,
|
||||
setFilename = exportViewModel::setFilename,
|
||||
uiStateFlow = exportViewModel.pdfUiState,
|
||||
sharePdf = { sharePdf(exportViewModel.getFinalPdf(), exportViewModel) },
|
||||
savePdf = { exportViewModel.onSavePdfClicked() },
|
||||
openPdf = { openPdf(exportViewModel.pdfUiState.value.savedFileUri) }
|
||||
share = { share(exportViewModel.applyRenaming(), exportViewModel) },
|
||||
save = { exportViewModel.onSaveClicked() },
|
||||
open = { item -> openUri(item.uri, item.format.mimeType) }
|
||||
),
|
||||
onCloseScan = {
|
||||
viewModel.startNewDocument()
|
||||
@@ -188,7 +187,8 @@ class MainActivity : ComponentActivity() {
|
||||
settingsUiState,
|
||||
onChooseDirectoryClick = { launcher.launch(null) },
|
||||
onResetExportDirClick = { settingsViewModel.setExportDirUri(null) },
|
||||
onBack = nav.back
|
||||
onExportFormatChanged = { format -> settingsViewModel.setExportFormat(format) },
|
||||
onBack = nav.back,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ class MainActivity : ComponentActivity() {
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { isGranted ->
|
||||
if (isGranted) {
|
||||
exportViewModel.onSavePdfClicked()
|
||||
exportViewModel.onSaveClicked()
|
||||
} else {
|
||||
val message = getString(R.string.storage_permission_denied)
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
@@ -231,9 +231,9 @@ class MainActivity : ComponentActivity() {
|
||||
LaunchedEffect(Unit) {
|
||||
exportViewModel.events.collect { event ->
|
||||
when (event) {
|
||||
ExportEvent.RequestSavePdf -> {
|
||||
ExportEvent.RequestSave -> {
|
||||
checkPermissionThen(storagePermissionLauncher) {
|
||||
exportViewModel.onRequestPdfSave(context)
|
||||
exportViewModel.onRequestSave(context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,25 +260,36 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun sharePdf(generatedPdf: GeneratedPdf?, viewModel: ExportViewModel) {
|
||||
if (generatedPdf == null)
|
||||
return
|
||||
viewModel.setPdfAsShared()
|
||||
val file = generatedPdf.file
|
||||
private fun share(result: ExportResult?, viewModel: ExportViewModel) {
|
||||
if (result == null || result.files.isEmpty()) return
|
||||
|
||||
viewModel.setAsShared()
|
||||
|
||||
val authority = "${applicationContext.packageName}.fileprovider"
|
||||
val fileUri = FileProvider.getUriForFile(this, authority, file)
|
||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = PDF_MIME_TYPE
|
||||
putExtra(Intent.EXTRA_STREAM, fileUri)
|
||||
val uris = result.files.map { file ->
|
||||
FileProvider.getUriForFile(this, authority, file)
|
||||
}
|
||||
val intent = Intent().apply {
|
||||
action = if (uris.size == 1) Intent.ACTION_SEND else Intent.ACTION_SEND_MULTIPLE
|
||||
type = result.format.mimeType
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
if (uris.size == 1) {
|
||||
putExtra(Intent.EXTRA_STREAM, uris[0])
|
||||
} else {
|
||||
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
|
||||
}
|
||||
}
|
||||
val chooser = Intent.createChooser(intent, getString(R.string.share_document))
|
||||
|
||||
val resolveInfos = packageManager.queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
for (info in resolveInfos) {
|
||||
val pkg = info.activityInfo.packageName
|
||||
for (uri in uris) {
|
||||
grantUriPermission(pkg, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
}
|
||||
|
||||
val chooser = Intent.createChooser(shareIntent, getString(R.string.share_pdf))
|
||||
val resInfoList = packageManager.queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
for (resInfo in resInfoList) {
|
||||
val packageName = resInfo.activityInfo.packageName
|
||||
grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
startActivity(chooser)
|
||||
}
|
||||
|
||||
@@ -295,7 +306,7 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun openPdf(fileUri: Uri?) {
|
||||
private fun openUri(fileUri: Uri?, mimeType: String) {
|
||||
if (fileUri == null) return
|
||||
val uriToOpen: Uri =
|
||||
if (fileUri.scheme == ContentResolver.SCHEME_CONTENT) {
|
||||
@@ -305,13 +316,13 @@ class MainActivity : ComponentActivity() {
|
||||
FileProvider.getUriForFile(this, authority, fileUri.toFile())
|
||||
}
|
||||
val openIntent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(uriToOpen, PDF_MIME_TYPE)
|
||||
setDataAndType(uriToOpen, mimeType)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
try {
|
||||
startActivity(Intent.createChooser(openIntent, getString(R.string.open_pdf)))
|
||||
startActivity(Intent.createChooser(openIntent, getString(R.string.open_file)))
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
Toast.makeText(this, getString(R.string.error_no_pdf_app), Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(this, getString(R.string.error_no_app), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ fun interface PdfWriter {
|
||||
fun writePdfFromJpegs(jpegs: Sequence<ByteArray>, outputStream: OutputStream): Int
|
||||
}
|
||||
|
||||
class PdfFileManager(
|
||||
class FileManager(
|
||||
private val pdfDir: File,
|
||||
private val externalDir: File,
|
||||
private val pdfWriter: PdfWriter
|
||||
) {
|
||||
companion object {
|
||||
fun addExtensionIfMissing(fileName: String): String {
|
||||
fun addPdfExtensionIfMissing(fileName: String): String {
|
||||
return if (fileName.lowercase().endsWith(".pdf"))
|
||||
fileName
|
||||
else
|
||||
@@ -111,11 +111,12 @@ class ImageRepository(
|
||||
}
|
||||
|
||||
fun getContent(id: String): ByteArray? {
|
||||
return getFileFor(id)?.readBytes()
|
||||
}
|
||||
|
||||
fun getFileFor(id: String): File? {
|
||||
val file = File(scanDir, id)
|
||||
if (file.exists()) {
|
||||
return file.readBytes()
|
||||
}
|
||||
return null
|
||||
return if (file.exists()) file else null
|
||||
}
|
||||
|
||||
fun getThumbnail(id: String): ByteArray? {
|
||||
|
||||
@@ -32,7 +32,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.RotateLeft
|
||||
import androidx.compose.material.icons.automirrored.filled.RotateRight
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.PictureAsPdf
|
||||
import androidx.compose.material.icons.filled.Description
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -55,8 +55,8 @@ import androidx.compose.ui.unit.dp
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import net.engawapg.lib.zoomable.ZoomState
|
||||
import net.engawapg.lib.zoomable.zoomable
|
||||
import org.fairscan.app.ui.Navigation
|
||||
import org.fairscan.app.R
|
||||
import org.fairscan.app.ui.Navigation
|
||||
import org.fairscan.app.ui.components.CommonPageListState
|
||||
import org.fairscan.app.ui.components.ConfirmationDialog
|
||||
import org.fairscan.app.ui.components.MainActionButton
|
||||
@@ -225,8 +225,8 @@ private fun BottomBar(
|
||||
) {
|
||||
MainActionButton(
|
||||
onClick = navigation.toExportScreen,
|
||||
icon = Icons.Default.PictureAsPdf,
|
||||
text = stringResource(R.string.export_pdf),
|
||||
icon = Icons.Default.Description,
|
||||
text = stringResource(R.string.export),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -61,13 +60,13 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import org.fairscan.app.R
|
||||
import org.fairscan.app.data.GeneratedPdf
|
||||
import org.fairscan.app.ui.Navigation
|
||||
import org.fairscan.app.ui.components.AppOverflowMenu
|
||||
import org.fairscan.app.ui.components.BackButton
|
||||
@@ -76,6 +75,8 @@ import org.fairscan.app.ui.components.NewDocumentDialog
|
||||
import org.fairscan.app.ui.components.isLandscape
|
||||
import org.fairscan.app.ui.components.pageCountText
|
||||
import org.fairscan.app.ui.dummyNavigation
|
||||
import org.fairscan.app.ui.screens.settings.ExportFormat
|
||||
import org.fairscan.app.ui.screens.settings.ExportFormat.PDF
|
||||
import org.fairscan.app.ui.theme.FairScanTheme
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
@@ -85,17 +86,17 @@ import java.util.Locale
|
||||
@Composable
|
||||
fun ExportScreenWrapper(
|
||||
navigation: Navigation,
|
||||
pdfActions: PdfGenerationActions,
|
||||
uiState: ExportUiState,
|
||||
pdfActions: ExportActions,
|
||||
onCloseScan: () -> Unit,
|
||||
) {
|
||||
BackHandler { navigation.back() }
|
||||
|
||||
val showConfirmationDialog = rememberSaveable { mutableStateOf(false) }
|
||||
val filename = remember { mutableStateOf(defaultFilename()) }
|
||||
val uiState by pdfActions.uiStateFlow.collectAsState()
|
||||
LaunchedEffect(Unit) {
|
||||
pdfActions.setFilename(filename.value)
|
||||
pdfActions.startGeneration()
|
||||
pdfActions.initializeExportScreen()
|
||||
}
|
||||
|
||||
val onFilenameChange = { newName:String ->
|
||||
@@ -116,15 +117,15 @@ fun ExportScreenWrapper(
|
||||
navigation = navigation,
|
||||
onShare = {
|
||||
ensureCorrectFileName()
|
||||
pdfActions.sharePdf()
|
||||
pdfActions.share()
|
||||
},
|
||||
onSave = {
|
||||
ensureCorrectFileName()
|
||||
pdfActions.savePdf()
|
||||
pdfActions.save()
|
||||
},
|
||||
onOpen = { pdfActions.openPdf() },
|
||||
onOpen = pdfActions.open,
|
||||
onCloseScan = {
|
||||
if (uiState.hasSavedOrSharedPdf)
|
||||
if (uiState.hasSavedOrShared)
|
||||
onCloseScan()
|
||||
else
|
||||
showConfirmationDialog.value = true
|
||||
@@ -141,17 +142,17 @@ fun ExportScreenWrapper(
|
||||
fun ExportScreen(
|
||||
filename: MutableState<String>,
|
||||
onFilenameChange: (String) -> Unit,
|
||||
uiState: PdfGenerationUiState,
|
||||
uiState: ExportUiState,
|
||||
navigation: Navigation,
|
||||
onShare: () -> Unit,
|
||||
onSave: () -> Unit,
|
||||
onOpen: () -> Unit,
|
||||
onOpen: (SavedItem) -> Unit,
|
||||
onCloseScan: () -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.export_pdf)) },
|
||||
title = { Text(stringResource(R.string.export_as, uiState.format)) },
|
||||
navigationIcon = { BackButton(navigation.back) },
|
||||
actions = {
|
||||
AppOverflowMenu(navigation)
|
||||
@@ -195,12 +196,12 @@ fun ExportScreen(
|
||||
private fun TextFieldAndPdfInfos(
|
||||
filename: MutableState<String>,
|
||||
onFilenameChange: (String) -> Unit,
|
||||
uiState: PdfGenerationUiState,
|
||||
onOpen: () -> Unit,
|
||||
uiState: ExportUiState,
|
||||
onOpen: (SavedItem) -> Unit,
|
||||
) {
|
||||
FilenameTextField(filename, onFilenameChange)
|
||||
|
||||
val pdf = uiState.generatedPdf
|
||||
val result = uiState.result
|
||||
|
||||
// PDF infos
|
||||
Column(
|
||||
@@ -208,20 +209,22 @@ private fun TextFieldAndPdfInfos(
|
||||
) {
|
||||
|
||||
if (uiState.isGenerating) {
|
||||
Text(stringResource(R.string.creating_pdf), fontStyle = FontStyle.Italic)
|
||||
} else if (pdf != null) {
|
||||
Text(stringResource(R.string.creating_export), fontStyle = FontStyle.Italic)
|
||||
} else if (result != null) {
|
||||
val context = LocalContext.current
|
||||
val formattedFileSize = formatFileSize(pdf.sizeInBytes, context)
|
||||
Text(text = pageCountText(pdf.pageCount))
|
||||
val formattedFileSize = formatFileSize(result.sizeInBytes, context)
|
||||
Text(text = pageCountText(result.pageCount))
|
||||
val sizeMessageKey =
|
||||
if (result.files.size == 1) R.string.file_size else R.string.file_size_total
|
||||
Text(
|
||||
text = stringResource(R.string.file_size, formattedFileSize),
|
||||
text = stringResource(sizeMessageKey, formattedFileSize),
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (uiState.savedFileUri != null) {
|
||||
SavedPdfBar(uiState, onOpen)
|
||||
if (uiState.savedBundle != null) {
|
||||
SaveInfoBar(uiState.savedBundle, onOpen)
|
||||
}
|
||||
if (uiState.errorMessage != null) {
|
||||
ErrorBar(uiState.errorMessage)
|
||||
@@ -257,7 +260,7 @@ private fun FilenameTextField(
|
||||
|
||||
@Composable
|
||||
private fun MainActions(
|
||||
uiState: PdfGenerationUiState,
|
||||
uiState: ExportUiState,
|
||||
onShare: () -> Unit,
|
||||
onSave: () -> Unit,
|
||||
onCloseScan: () -> Unit,
|
||||
@@ -271,16 +274,16 @@ private fun MainActions(
|
||||
) {
|
||||
ExportButton(
|
||||
onClick = onShare,
|
||||
enabled = uiState.generatedPdf != null,
|
||||
isPrimary = !uiState.hasSavedOrSharedPdf,
|
||||
enabled = uiState.result != null,
|
||||
isPrimary = !uiState.hasSavedOrShared,
|
||||
icon = Icons.Default.Share,
|
||||
text = stringResource(R.string.share),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
ExportButton(
|
||||
onClick = onSave,
|
||||
enabled = uiState.generatedPdf != null,
|
||||
isPrimary = !uiState.hasSavedOrSharedPdf,
|
||||
enabled = uiState.result != null,
|
||||
isPrimary = !uiState.hasSavedOrShared,
|
||||
icon = Icons.Default.Download,
|
||||
text = stringResource(R.string.save),
|
||||
modifier = Modifier.weight(1f)
|
||||
@@ -291,7 +294,7 @@ private fun MainActions(
|
||||
text = stringResource(R.string.end_scan),
|
||||
onClick = onCloseScan,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isPrimary = uiState.hasSavedOrSharedPdf,
|
||||
isPrimary = uiState.hasSavedOrShared,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -335,8 +338,8 @@ fun ExportButton(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SavedPdfBar(uiState: PdfGenerationUiState, onOpen: () -> Unit) {
|
||||
val dirName = uiState.exportDirName?:stringResource(R.string.download_dirname)
|
||||
private fun SaveInfoBar(savedBundle: SavedBundle, onOpen: (SavedItem) -> Unit) {
|
||||
val dirName = savedBundle.exportDirName?:stringResource(R.string.download_dirname)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Absolute.SpaceBetween,
|
||||
@@ -345,17 +348,26 @@ private fun SavedPdfBar(uiState: PdfGenerationUiState, onOpen: () -> Unit) {
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
) {
|
||||
val items = savedBundle.items
|
||||
val nbFiles = items.size
|
||||
val firstFileName = items[0].fileName
|
||||
Text(
|
||||
text = stringResource(R.string.pdf_saved_to, dirName),
|
||||
text = LocalResources.current.getQuantityString(
|
||||
R.plurals.files_saved_to,
|
||||
nbFiles,
|
||||
nbFiles, firstFileName, dirName
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
MainActionButton(
|
||||
onClick = onOpen,
|
||||
text = stringResource(R.string.open),
|
||||
icon = Icons.AutoMirrored.Filled.OpenInNew,
|
||||
)
|
||||
if (nbFiles == 1) {
|
||||
Spacer(Modifier.width(8.dp))
|
||||
MainActionButton(
|
||||
onClick = { onOpen(items[0]) },
|
||||
text = stringResource(R.string.open),
|
||||
icon = Icons.AutoMirrored.Filled.OpenInNew,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +398,7 @@ fun formatFileSize(sizeInBytes: Long?, context: Context): String {
|
||||
@Composable
|
||||
fun PreviewExportScreenDuringGeneration() {
|
||||
ExportPreviewToCustomize(
|
||||
uiState = PdfGenerationUiState(isGenerating = true)
|
||||
uiState = ExportUiState(isGenerating = true)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -395,8 +407,8 @@ fun PreviewExportScreenDuringGeneration() {
|
||||
fun PreviewExportScreenAfterGeneration() {
|
||||
val file = File("fake.pdf")
|
||||
ExportPreviewToCustomize(
|
||||
uiState = PdfGenerationUiState(
|
||||
generatedPdf = GeneratedPdf(file, 442897L, 3),
|
||||
uiState = ExportUiState(
|
||||
result = ExportResult.Pdf(file, 442897L, 3),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -406,9 +418,11 @@ fun PreviewExportScreenAfterGeneration() {
|
||||
fun PreviewExportScreenAfterSave() {
|
||||
val file = File("fake.pdf")
|
||||
ExportPreviewToCustomize(
|
||||
uiState = PdfGenerationUiState(
|
||||
generatedPdf = GeneratedPdf(file, 442897L, 3),
|
||||
savedFileUri = file.toUri(),
|
||||
uiState = ExportUiState(
|
||||
result = ExportResult.Pdf(file, 442897L, 3),
|
||||
savedBundle = SavedBundle(
|
||||
listOf(SavedItem(file.toUri(), defaultFilename() + ".pdf", PDF))
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -417,7 +431,7 @@ fun PreviewExportScreenAfterSave() {
|
||||
@Composable
|
||||
fun ExportScreenPreviewWithError() {
|
||||
ExportPreviewToCustomize(
|
||||
PdfGenerationUiState(errorMessage = "PDF generation failed")
|
||||
ExportUiState(errorMessage = "PDF generation failed")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -426,16 +440,17 @@ fun ExportScreenPreviewWithError() {
|
||||
fun PreviewExportScreenAfterSaveHorizontal() {
|
||||
val file = File("fake.pdf")
|
||||
ExportPreviewToCustomize(
|
||||
uiState = PdfGenerationUiState(
|
||||
generatedPdf = GeneratedPdf(file, 442897L, 3),
|
||||
savedFileUri = file.toUri(),
|
||||
exportDirName = "MyVeryVeryLongDirectoryName"
|
||||
uiState = ExportUiState(
|
||||
result = ExportResult.Pdf(file, 442897L, 3),
|
||||
savedBundle = SavedBundle(
|
||||
listOf(SavedItem(file.toUri(), "my_file.pdf", PDF)),
|
||||
exportDirName="MyVeryVeryLongDirectoryName"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExportPreviewToCustomize(uiState: PdfGenerationUiState) {
|
||||
fun ExportPreviewToCustomize(uiState: ExportUiState) {
|
||||
FairScanTheme {
|
||||
ExportScreen(
|
||||
filename = remember { mutableStateOf("Scan 2025-07-02 17.40.42") },
|
||||
|
||||
@@ -15,16 +15,28 @@
|
||||
package org.fairscan.app.ui.screens.export
|
||||
|
||||
import android.net.Uri
|
||||
import org.fairscan.app.data.GeneratedPdf
|
||||
import org.fairscan.app.ui.screens.settings.ExportFormat
|
||||
|
||||
data class PdfGenerationUiState(
|
||||
data class ExportUiState(
|
||||
val format: ExportFormat = ExportFormat.PDF,
|
||||
val isGenerating: Boolean = false,
|
||||
val generatedPdf: GeneratedPdf? = null,
|
||||
val result: ExportResult? = null,
|
||||
val desiredFilename: String = "",
|
||||
val savedFileUri: Uri? = null,
|
||||
val exportDirName: String? = null,
|
||||
val hasSharedPdf: Boolean = false,
|
||||
val savedBundle: SavedBundle? = null,
|
||||
val hasShared: Boolean = false,
|
||||
val errorMessage: String? = null,
|
||||
) {
|
||||
val hasSavedOrSharedPdf get() = savedFileUri != null || hasSharedPdf
|
||||
val hasSavedOrShared get() = savedBundle != null || hasShared
|
||||
}
|
||||
|
||||
data class SavedItem(
|
||||
val uri: Uri,
|
||||
val fileName: String,
|
||||
val format: ExportFormat,
|
||||
)
|
||||
|
||||
data class SavedBundle(
|
||||
val items: List<SavedItem>,
|
||||
val exportDir: Uri? = null,
|
||||
val exportDirName: String? = null,
|
||||
)
|
||||
|
||||
@@ -22,7 +22,6 @@ import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -32,26 +31,25 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.fairscan.app.AppContainer
|
||||
import org.fairscan.app.RecentDocument
|
||||
import org.fairscan.app.data.GeneratedPdf
|
||||
import org.fairscan.app.data.PdfFileManager
|
||||
import org.fairscan.app.ui.screens.home.HomeViewModel
|
||||
import org.fairscan.app.data.FileManager
|
||||
import org.fairscan.app.ui.screens.settings.ExportFormat
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
private const val PDF_MIME_TYPE = "application/pdf"
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
sealed interface ExportEvent {
|
||||
data object RequestSavePdf : ExportEvent
|
||||
data object RequestSave : ExportEvent
|
||||
data object SaveError : ExportEvent
|
||||
}
|
||||
|
||||
class ExportViewModel(container: AppContainer): ViewModel() {
|
||||
|
||||
private val pdfFileManager = container.pdfFileManager
|
||||
private val preparationDir = container.preparationDir
|
||||
private val fileManager = container.fileManager
|
||||
private val imageRepository = container.imageRepository
|
||||
private val settingsRepository = container.settingsRepository
|
||||
private val recentDocumentsDataStore = container.recentDocumentsDataStore
|
||||
@@ -60,134 +58,166 @@ class ExportViewModel(container: AppContainer): ViewModel() {
|
||||
private val _events = MutableSharedFlow<ExportEvent>()
|
||||
val events = _events.asSharedFlow()
|
||||
|
||||
private suspend fun generatePdf(): GeneratedPdf = withContext(Dispatchers.IO) {
|
||||
private suspend fun generatePdf(): ExportResult.Pdf = withContext(Dispatchers.IO) {
|
||||
val imageIds = imageRepository.imageIds()
|
||||
val jpegs = imageIds.asSequence()
|
||||
.map { id -> imageRepository.getContent(id) }
|
||||
.filterNotNull()
|
||||
return@withContext pdfFileManager.generatePdf(jpegs)
|
||||
.mapNotNull { id -> imageRepository.getContent(id) }
|
||||
val pdf = fileManager.generatePdf(jpegs)
|
||||
return@withContext ExportResult.Pdf(pdf.file, pdf.sizeInBytes, pdf.pageCount)
|
||||
}
|
||||
|
||||
private val _pdfUiState = MutableStateFlow(PdfGenerationUiState())
|
||||
val pdfUiState: StateFlow<PdfGenerationUiState> = _pdfUiState.asStateFlow()
|
||||
private val _uiState = MutableStateFlow(ExportUiState())
|
||||
val uiState: StateFlow<ExportUiState> = _uiState.asStateFlow()
|
||||
|
||||
private var generationJob: Job? = null
|
||||
private var preparationJob: Job? = null
|
||||
private var desiredFilename: String = ""
|
||||
private var exportFormat = ExportFormat.PDF
|
||||
|
||||
fun setFilename(name: String) {
|
||||
desiredFilename = name
|
||||
}
|
||||
|
||||
fun startPdfGeneration() {
|
||||
cancelPdfGeneration()
|
||||
generationJob = viewModelScope.launch {
|
||||
fun initializeExportScreen() {
|
||||
cancelPreparation()
|
||||
|
||||
preparationJob = viewModelScope.launch {
|
||||
exportFormat = settingsRepository.exportFormat.first()
|
||||
_uiState.update { it.copy(format = exportFormat) }
|
||||
try {
|
||||
val result = generatePdf()
|
||||
_pdfUiState.update {
|
||||
it.copy(
|
||||
isGenerating = false,
|
||||
generatedPdf = result
|
||||
)
|
||||
val result = if (exportFormat == ExportFormat.JPEG) {
|
||||
val jpegFiles = imageRepository.imageIds()
|
||||
.mapNotNull { id -> imageRepository.getFileFor(id) }
|
||||
.map { f -> f.copyTo(File(preparationDir, f.name), overwrite = true) }
|
||||
val sizeInBytes = jpegFiles.sumOf { it.length() }
|
||||
ExportResult.Jpeg(jpegFiles, sizeInBytes)
|
||||
} else {
|
||||
generatePdf()
|
||||
}
|
||||
_uiState.update {
|
||||
it.copy(isGenerating = false, result = result)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.e("FairScan", "PDF generation failed", e)
|
||||
_pdfUiState.update {
|
||||
val message = "Failed to prepare $exportFormat export"
|
||||
logger.e("FairScan", message, e)
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isGenerating = false,
|
||||
errorMessage = "PDF generation failed"
|
||||
errorMessage = message
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelPdfGeneration() {
|
||||
generationJob?.cancel()
|
||||
_pdfUiState.value = PdfGenerationUiState()
|
||||
fun cancelPreparation() {
|
||||
preparationJob?.cancel()
|
||||
_uiState.value = ExportUiState()
|
||||
}
|
||||
|
||||
fun setPdfAsShared() {
|
||||
_pdfUiState.update { it.copy(hasSharedPdf = true) }
|
||||
fun setAsShared() {
|
||||
_uiState.update { it.copy(hasShared = true) }
|
||||
}
|
||||
|
||||
fun getFinalPdf(): GeneratedPdf? {
|
||||
val tempPdf = _pdfUiState.value.generatedPdf ?: return null
|
||||
val tempFile = tempPdf.file
|
||||
val fileName = PdfFileManager.addExtensionIfMissing(desiredFilename)
|
||||
val newFile = File(tempFile.parentFile, fileName)
|
||||
if (tempFile.absolutePath != newFile.absolutePath) {
|
||||
if (newFile.exists()) newFile.delete()
|
||||
val success = tempFile.renameTo(newFile)
|
||||
if (!success) return null
|
||||
_pdfUiState.update {
|
||||
it.copy(generatedPdf = GeneratedPdf(
|
||||
newFile, tempPdf.sizeInBytes, tempPdf.pageCount)
|
||||
)
|
||||
fun applyRenaming(): ExportResult? {
|
||||
val result = _uiState.value.result ?: return null
|
||||
when (result) {
|
||||
is ExportResult.Pdf -> {
|
||||
val fileName = FileManager.addPdfExtensionIfMissing(desiredFilename)
|
||||
val newFile = File(result.file.parentFile, fileName)
|
||||
val tempFile = result.file
|
||||
if (tempFile.absolutePath != newFile.absolutePath) {
|
||||
if (newFile.exists()) newFile.delete()
|
||||
val success = tempFile.renameTo(newFile)
|
||||
if (!success) return null
|
||||
_uiState.update {
|
||||
it.copy(result = ExportResult.Pdf(
|
||||
newFile, result.sizeInBytes, result.pageCount)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is ExportResult.Jpeg -> {
|
||||
val base = desiredFilename.removeSuffix(".jpg")
|
||||
val renamedFiles = result.files.mapIndexed { index, file ->
|
||||
val newFile = File(file.parentFile, "${base}_${index + 1}.jpg")
|
||||
if (newFile.exists()) newFile.delete()
|
||||
file.renameTo(newFile)
|
||||
newFile
|
||||
}
|
||||
val updated = result.copy(jpegFiles = renamedFiles)
|
||||
_uiState.update { it.copy(result = updated) }
|
||||
}
|
||||
}
|
||||
return _pdfUiState.value.generatedPdf
|
||||
return _uiState.value.result
|
||||
}
|
||||
|
||||
fun onSavePdfClicked() {
|
||||
fun onSaveClicked() {
|
||||
viewModelScope.launch {
|
||||
_events.emit(ExportEvent.RequestSavePdf)
|
||||
_events.emit(ExportEvent.RequestSave)
|
||||
}
|
||||
}
|
||||
|
||||
fun onRequestPdfSave(context: Context) {
|
||||
fun onRequestSave(context: Context) {
|
||||
viewModelScope.launch {
|
||||
performPdfSave(context)
|
||||
try {
|
||||
save(context)
|
||||
} catch (e: Exception) {
|
||||
logger.e("FairScan", "Failed to save PDF", e)
|
||||
_events.emit(ExportEvent.SaveError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun performPdfSave(context: Context) {
|
||||
try {
|
||||
val pdf = getFinalPdf() ?: return
|
||||
private suspend fun save(context:Context) {
|
||||
val result = applyRenaming() ?: return
|
||||
val exportDir = settingsRepository.exportDirUri.first()?.toUri()
|
||||
val savedItems = mutableListOf<SavedItem>()
|
||||
val filesForMediaScan = mutableListOf<File>()
|
||||
|
||||
val exportDir = settingsRepository.exportDirUri.first()
|
||||
var fileInDownloads: File? = null
|
||||
|
||||
var savedName: String
|
||||
val savedUri: Uri
|
||||
if (exportDir == null) {
|
||||
fileInDownloads = pdfFileManager.copyToExternalDir(pdf.file)
|
||||
savedUri = fileInDownloads.toUri()
|
||||
savedName = fileInDownloads.name
|
||||
for (file in result.files) {
|
||||
val saved = if (exportDir == null) {
|
||||
val out = fileManager.copyToExternalDir(file)
|
||||
filesForMediaScan.add(out)
|
||||
SavedItem(out.toUri(), out.name, exportFormat)
|
||||
} else {
|
||||
val saved = copyViaSaf(context, pdf.file, exportDir.toUri())
|
||||
savedUri = saved.uri
|
||||
savedName = saved.name?:pdf.file.name
|
||||
val safFile = copyViaSaf(context, file, exportDir, exportFormat)
|
||||
SavedItem(safFile.uri, safFile.name ?: file.name, exportFormat)
|
||||
}
|
||||
|
||||
_pdfUiState.update {
|
||||
it.copy(
|
||||
savedFileUri = savedUri,
|
||||
exportDirName = resolveExportDirName(context, exportDir?.toUri()))
|
||||
}
|
||||
|
||||
fileInDownloads?.let { mediaScan(context, it) }
|
||||
|
||||
addRecentDocument(savedUri, savedName, pdf.pageCount)
|
||||
} catch (e: Exception) {
|
||||
logger.e("FairScan", "Failed to save PDF", e)
|
||||
_events.emit(ExportEvent.SaveError)
|
||||
savedItems += saved
|
||||
}
|
||||
|
||||
val exportDirName = resolveExportDirName(context, exportDir)
|
||||
val bundle = SavedBundle(savedItems, exportDir, exportDirName)
|
||||
_uiState.update { it.copy(savedBundle = bundle) }
|
||||
|
||||
if (exportFormat == ExportFormat.PDF) {
|
||||
savedItems.forEach { item ->
|
||||
addRecentDocument(item.uri, item.fileName, result.pageCount)
|
||||
}
|
||||
}
|
||||
|
||||
filesForMediaScan.forEach { f -> mediaScan(context, f, exportFormat.mimeType) }
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private suspend fun mediaScan(context: Context, file: File) =
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
MediaScannerConnection.scanFile(
|
||||
context,
|
||||
arrayOf(file.absolutePath),
|
||||
arrayOf(PDF_MIME_TYPE)
|
||||
) { _, _ -> continuation.resume(Unit) {} }
|
||||
private suspend fun mediaScan(
|
||||
context: Context,
|
||||
file: File,
|
||||
mimeType: String
|
||||
): Uri? = suspendCoroutine { cont ->
|
||||
MediaScannerConnection.scanFile(
|
||||
context,
|
||||
arrayOf(file.absolutePath),
|
||||
arrayOf(mimeType)
|
||||
) { _, uri ->
|
||||
cont.resume(uri)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyViaSaf(
|
||||
context: Context,
|
||||
source: File,
|
||||
exportDirUri: Uri,
|
||||
exportFormat: ExportFormat,
|
||||
): DocumentFile {
|
||||
val resolver = context.contentResolver
|
||||
|
||||
@@ -195,7 +225,7 @@ class ExportViewModel(container: AppContainer): ViewModel() {
|
||||
?: throw IllegalStateException("Invalid SAF directory")
|
||||
|
||||
// Name collisions are handled automatically by SAF provider
|
||||
val target = tree.createFile(PDF_MIME_TYPE, source.name)
|
||||
val target = tree.createFile(exportFormat.mimeType, source.name)
|
||||
?: throw IllegalStateException("Unable to create SAF file")
|
||||
|
||||
resolver.openOutputStream(target.uri)?.use { output ->
|
||||
@@ -207,8 +237,8 @@ class ExportViewModel(container: AppContainer): ViewModel() {
|
||||
return target
|
||||
}
|
||||
|
||||
fun cleanUpOldPdfs(thresholdInMillis: Int) {
|
||||
pdfFileManager.cleanUpOldFiles(thresholdInMillis)
|
||||
fun cleanUpOldPreparedFiles(thresholdInMillis: Int) {
|
||||
fileManager.cleanUpOldFiles(thresholdInMillis)
|
||||
}
|
||||
|
||||
private fun resolveExportDirName(context: Context, exportDirUri: Uri?): String? {
|
||||
@@ -241,11 +271,35 @@ class ExportViewModel(container: AppContainer): ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
data class PdfGenerationActions(
|
||||
val startGeneration: () -> Unit,
|
||||
sealed class ExportResult {
|
||||
abstract val files: List<File>
|
||||
abstract val sizeInBytes: Long
|
||||
abstract val pageCount: Int
|
||||
abstract val format: ExportFormat
|
||||
|
||||
data class Pdf(
|
||||
val file: File,
|
||||
override val sizeInBytes: Long,
|
||||
override val pageCount: Int,
|
||||
) : ExportResult() {
|
||||
override val files get() = listOf(file)
|
||||
override val format: ExportFormat = ExportFormat.PDF
|
||||
}
|
||||
|
||||
data class Jpeg(
|
||||
val jpegFiles: List<File>,
|
||||
override val sizeInBytes: Long,
|
||||
) : ExportResult() {
|
||||
override val files get() = jpegFiles
|
||||
override val pageCount get() = jpegFiles.size
|
||||
override val format: ExportFormat = ExportFormat.JPEG
|
||||
}
|
||||
}
|
||||
|
||||
data class ExportActions(
|
||||
val initializeExportScreen: () -> Unit,
|
||||
val setFilename: (String) -> Unit,
|
||||
val uiStateFlow: StateFlow<PdfGenerationUiState>,// TODO is it ok to have that here?
|
||||
val sharePdf: () -> Unit,
|
||||
val savePdf: () -> Unit,
|
||||
val openPdf: () -> Unit,
|
||||
val share: () -> Unit,
|
||||
val save: () -> Unit,
|
||||
val open: (SavedItem) -> Unit,
|
||||
)
|
||||
|
||||
@@ -26,12 +26,22 @@ private val Context.dataStore by preferencesDataStore(name = "fairscan_settings"
|
||||
class SettingsRepository(private val context: Context) {
|
||||
|
||||
private val EXPORT_DIR_URI = stringPreferencesKey("export_dir_uri")
|
||||
private val EXPORT_FORMAT = stringPreferencesKey("export_format")
|
||||
|
||||
val exportDirUri: Flow<String?> =
|
||||
context.dataStore.data.map { prefs ->
|
||||
prefs[EXPORT_DIR_URI]
|
||||
}
|
||||
|
||||
val exportFormat: Flow<ExportFormat> =
|
||||
context.dataStore.data.map { prefs ->
|
||||
when (prefs[EXPORT_FORMAT]) {
|
||||
"JPEG" -> ExportFormat.JPEG
|
||||
"PDF", null -> ExportFormat.PDF
|
||||
else -> ExportFormat.PDF
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setExportDirUri(uri: String?) {
|
||||
context.dataStore.edit { prefs ->
|
||||
if (uri == null) {
|
||||
@@ -41,4 +51,15 @@ class SettingsRepository(private val context: Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setExportFormat(format: ExportFormat) {
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs[EXPORT_FORMAT] = format.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class ExportFormat(val mimeType: String) {
|
||||
PDF("application/pdf"),
|
||||
JPEG("image/jpeg"),
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ 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.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Folder
|
||||
import androidx.compose.material3.Card
|
||||
@@ -34,6 +36,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
@@ -56,6 +59,7 @@ fun SettingsScreen(
|
||||
uiState: SettingsUiState,
|
||||
onChooseDirectoryClick: () -> Unit,
|
||||
onResetExportDirClick: () -> Unit,
|
||||
onExportFormatChanged: (ExportFormat) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
) {
|
||||
BackHandler { onBack() }
|
||||
@@ -67,10 +71,13 @@ fun SettingsScreen(
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
SettingsContent(uiState, onChooseDirectoryClick, onResetExportDirClick, modifier = Modifier.padding(paddingValues))
|
||||
SettingsContent(
|
||||
uiState,
|
||||
onChooseDirectoryClick,
|
||||
onResetExportDirClick,
|
||||
onExportFormatChanged,
|
||||
modifier = Modifier.padding(paddingValues))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -78,6 +85,7 @@ private fun SettingsContent(
|
||||
uiState: SettingsUiState,
|
||||
onChooseDirectoryClick: () -> Unit,
|
||||
onResetExportDirClick: () -> Unit,
|
||||
onExportFormatChanged: (ExportFormat) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -89,6 +97,7 @@ private fun SettingsContent(
|
||||
modifier
|
||||
.fillMaxSize()
|
||||
.padding(20.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
DirectorySettingItem(
|
||||
label = stringResource(R.string.export_directory),
|
||||
@@ -106,6 +115,26 @@ private fun SettingsContent(
|
||||
Text(stringResource(R.string.reset_to_default))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(32.dp))
|
||||
|
||||
Text("Export format", style = MaterialTheme.typography.titleLarge)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
RadioButton(
|
||||
selected = uiState.exportFormat == ExportFormat.PDF,
|
||||
onClick = { onExportFormatChanged(ExportFormat.PDF) },
|
||||
)
|
||||
Text("PDF")
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
RadioButton(
|
||||
selected = uiState.exportFormat == ExportFormat.JPEG,
|
||||
onClick = { onExportFormatChanged(ExportFormat.JPEG) },
|
||||
)
|
||||
Text("JPEG")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,6 +202,12 @@ fun SettingsScreenPreviewWithDir() {
|
||||
@Composable
|
||||
fun SettingsScreenPreview(uiState: SettingsUiState) {
|
||||
FairScanTheme {
|
||||
SettingsScreen(uiState, onChooseDirectoryClick = {}, onResetExportDirClick = {}, onBack= {})
|
||||
SettingsScreen(
|
||||
uiState,
|
||||
onChooseDirectoryClick = {},
|
||||
onResetExportDirClick = {},
|
||||
onExportFormatChanged = {},
|
||||
onBack = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,25 +21,37 @@ import kotlinx.coroutines.launch
|
||||
import org.fairscan.app.AppContainer
|
||||
|
||||
data class SettingsUiState(
|
||||
val exportDirUri: String? = null
|
||||
val exportDirUri: String? = null,
|
||||
val exportFormat: ExportFormat = ExportFormat.PDF,
|
||||
)
|
||||
|
||||
class SettingsViewModel(container: AppContainer) : ViewModel() {
|
||||
|
||||
private val repo = container.settingsRepository
|
||||
|
||||
val uiState: StateFlow<SettingsUiState> =
|
||||
repo.exportDirUri
|
||||
.map { uri -> SettingsUiState(exportDirUri = uri) }
|
||||
.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5000),
|
||||
SettingsUiState()
|
||||
)
|
||||
val uiState = combine(
|
||||
repo.exportDirUri,
|
||||
repo.exportFormat,
|
||||
) { dir, format ->
|
||||
SettingsUiState(
|
||||
exportDirUri = dir,
|
||||
exportFormat = format,
|
||||
)
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5000),
|
||||
SettingsUiState()
|
||||
)
|
||||
|
||||
fun setExportDirUri(uri: String?) {
|
||||
viewModelScope.launch {
|
||||
repo.setExportDirUri(uri)
|
||||
}
|
||||
}
|
||||
|
||||
fun setExportFormat(format: ExportFormat) {
|
||||
viewModelScope.launch {
|
||||
repo.setExportFormat(format)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<string name="contact">Kontakt</string>
|
||||
<string name="copied_logs">Protokoly zkopírovány do schránky</string>
|
||||
<string name="copy_logs">Kopírovat protokoly</string>
|
||||
<string name="creating_pdf">Vytváření PDF…</string>
|
||||
<string name="creating_export">Příprava exportu…</string>
|
||||
<string name="delete_page">Smazat stránku</string>
|
||||
<string name="delete_page_warning">Chcete smazat tuto stránku?</string>
|
||||
<string name="developer">Vývojář</string>
|
||||
@@ -19,12 +19,14 @@
|
||||
<string name="download_dirname">stažených</string>
|
||||
<string name="end_scan">Ukončit skenování</string>
|
||||
<string name="error">Chyba: %1$s</string>
|
||||
<string name="error_no_app">Nebyla nalezena žádná aplikace pro otevření tohoto souboru</string>
|
||||
<string name="error_no_document">Nebyl rozpoznán žádná dokument</string>
|
||||
<string name="error_no_pdf_app">Nebyla nazelena žádná aplikace pro otevření PDF</string>
|
||||
<string name="error_save">Chyba při ukládání PDF</string>
|
||||
<string name="error_save">Soubor se nepodařilo uložit</string>
|
||||
<string name="export">Exportovat</string>
|
||||
<string name="export_as">Exportovat jako %1$s</string>
|
||||
<string name="export_directory">Složka pro export</string>
|
||||
<string name="export_pdf">Exportovat PDF</string>
|
||||
<string name="file_size">Velikost souboru: %1$s</string>
|
||||
<string name="file_size_total">Celková velikost: %1$s</string>
|
||||
<string name="filename">Název souboru</string>
|
||||
<string name="grant_permission">Povolit přístup</string>
|
||||
<string name="last_saved_pdf_files">Poslední PDF uložené v tomto zařízení:</string>
|
||||
@@ -36,8 +38,7 @@
|
||||
<string name="menu">Menu</string>
|
||||
<string name="new_document_warning">Toto skenování bude ztraceno. Chcete pokračovat?</string>
|
||||
<string name="open">Otevřít</string>
|
||||
<string name="open_pdf">Otevřít PDF</string>
|
||||
<string name="pdf_saved_to">PDF bylo uloženo do %1s</string>
|
||||
<string name="open_file">Otevřít soubor</string>
|
||||
<string name="reset_to_default">Obnovit výchozí</string>
|
||||
<string name="resume">Obnovit</string>
|
||||
<string name="save">Uložit</string>
|
||||
@@ -45,8 +46,8 @@
|
||||
<string name="scan_in_progress">Probíhá skenování</string>
|
||||
<string name="settings">Nastavení</string>
|
||||
<string name="share">Sdílet</string>
|
||||
<string name="share_pdf">Sdílet PDF</string>
|
||||
<string name="storage_permission_denied">Nelze uložit PDF: přístup zakázán</string>
|
||||
<string name="share_document">Sdílet dokument</string>
|
||||
<string name="storage_permission_denied">Nelze uložit soubor: oprávnění bylo odmítnuto</string>
|
||||
<string name="turn_off_torch">Vypnout svítilnu</string>
|
||||
<string name="turn_on_torch">Zapnout svítilnu</string>
|
||||
<string name="unknown_size">Neznámá velikost</string>
|
||||
@@ -54,6 +55,12 @@
|
||||
<string name="view_full_list">Zobrazit úplný seznam</string>
|
||||
<string name="view_the_full_license">Zobrazit úplnou licenci</string>
|
||||
<string name="yes">Ano</string>
|
||||
<plurals name="files_saved_to">
|
||||
<item quantity="one">%2$s uložen do %3$s</item>
|
||||
<item quantity="few">%1$d soubory uloženy do %3$s</item>
|
||||
<item quantity="many">%1$d souborů uloženo do %3$s</item>
|
||||
<item quantity="other">%1$d souborů uloženo do %3$s</item>
|
||||
</plurals>
|
||||
<plurals name="page_count">
|
||||
<item quantity="one">%d stránka</item> <!-- 1 -->
|
||||
<item quantity="few">%d stránky</item> <!-- 2–4 -->
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<string name="contact">Kontakt</string>
|
||||
<string name="copied_logs">Logs in die Zwischenablage kopiert</string>
|
||||
<string name="copy_logs">Logs kopieren</string>
|
||||
<string name="creating_pdf">PDF wird erstellt…</string>
|
||||
<string name="creating_export">Export wird vorbereitet…</string>
|
||||
<string name="delete_page">Seite löschen</string>
|
||||
<string name="delete_page_warning">Möchten Sie diese Seite löschen?</string>
|
||||
<string name="developer">Entwickler</string>
|
||||
@@ -19,12 +19,14 @@
|
||||
<string name="download_dirname">Downloads</string>
|
||||
<string name="end_scan">Scan beenden</string>
|
||||
<string name="error">Fehler: %1$s</string>
|
||||
<string name="error_no_app">Keine App zum Öffnen dieser Datei gefunden</string>
|
||||
<string name="error_no_document">Kein Dokument erkannt</string>
|
||||
<string name="error_no_pdf_app">Keine App zum Öffnen von PDF gefunden</string>
|
||||
<string name="error_save">PDF konnte nicht gespeichert werden</string>
|
||||
<string name="error_save">Datei konnte nicht gespeichert werden</string>
|
||||
<string name="export">Exportieren</string>
|
||||
<string name="export_as">Als %1$s exportieren</string>
|
||||
<string name="export_directory">Exportordner</string>
|
||||
<string name="export_pdf">PDF exportieren</string>
|
||||
<string name="file_size">Dateigröße: %1$s</string>
|
||||
<string name="file_size_total">Gesamtgröße: %1$s</string>
|
||||
<string name="filename">Dateiname</string>
|
||||
<string name="grant_permission">Berechtigung erteilen</string>
|
||||
<string name="last_saved_pdf_files">Zuletzt auf diesem Gerät gespeicherte PDFs:</string>
|
||||
@@ -36,8 +38,7 @@
|
||||
<string name="menu">Menü</string>
|
||||
<string name="new_document_warning">Das aktuelle Dokument geht verloren. Möchten Sie fortfahren?</string>
|
||||
<string name="open">Öffnen</string>
|
||||
<string name="open_pdf">PDF öffnen</string>
|
||||
<string name="pdf_saved_to">PDF gespeichert in %1s</string>
|
||||
<string name="open_file">Datei öffnen</string>
|
||||
<string name="reset_to_default">Auf Standard zurücksetzen</string>
|
||||
<string name="resume">Fortsetzen</string>
|
||||
<string name="save">Speichern</string>
|
||||
@@ -45,8 +46,8 @@
|
||||
<string name="scan_in_progress">Scan läuft</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="share">Teilen</string>
|
||||
<string name="share_pdf">PDF teilen</string>
|
||||
<string name="storage_permission_denied">PDF-Datei kann nicht gespeichert werden: Berechtigung verweigert</string>
|
||||
<string name="share_document">Dokument teilen</string>
|
||||
<string name="storage_permission_denied">Datei kann nicht gespeichert werden: Berechtigung verweigert</string>
|
||||
<string name="turn_off_torch">Taschenlampe ausschalten</string>
|
||||
<string name="turn_on_torch">Taschenlampe einschalten</string>
|
||||
<string name="unknown_size">Unbekannte Größe</string>
|
||||
@@ -54,6 +55,10 @@
|
||||
<string name="view_full_list">Vollständige Liste anzeigen</string>
|
||||
<string name="view_the_full_license">Vollständige Lizenz anzeigen</string>
|
||||
<string name="yes">Ja</string>
|
||||
<plurals name="files_saved_to">
|
||||
<item quantity="one">%2$s gespeichert in %3$s</item>
|
||||
<item quantity="other">%1$d Dateien gespeichert in %3$s</item>
|
||||
</plurals>
|
||||
<plurals name="page_count">
|
||||
<item quantity="one">%d Seite</item>
|
||||
<item quantity="other">%d Seiten</item>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<string name="contact">Contacto</string>
|
||||
<string name="copied_logs">Registros copiados al portapapeles</string>
|
||||
<string name="copy_logs">Copiar registros</string>
|
||||
<string name="creating_pdf">Creando PDF…</string>
|
||||
<string name="creating_export">Preparando la exportación…</string>
|
||||
<string name="delete_page">Eliminar página</string>
|
||||
<string name="delete_page_warning">¿Quieres eliminar esta página?</string>
|
||||
<string name="developer">Desarrollador</string>
|
||||
@@ -19,12 +19,14 @@
|
||||
<string name="download_dirname">Descargas</string>
|
||||
<string name="end_scan">Finalizar escaneo</string>
|
||||
<string name="error">Error: %1$s</string>
|
||||
<string name="error_no_app">No se encontró ninguna aplicación para abrir este archivo</string>
|
||||
<string name="error_no_document">No se detectó ningún documento</string>
|
||||
<string name="error_no_pdf_app">No se encontró ninguna aplicación para abrir PDF</string>
|
||||
<string name="error_save">Error al guardar el PDF</string>
|
||||
<string name="error_save">No se pudo guardar el archivo</string>
|
||||
<string name="export">Exportar</string>
|
||||
<string name="export_as">Exportar como %1$s</string>
|
||||
<string name="export_directory">Carpeta de exportación</string>
|
||||
<string name="export_pdf">Exportar PDF</string>
|
||||
<string name="file_size">Tamaño del archivo: %1$s</string>
|
||||
<string name="file_size_total">Tamaño total: %1$s</string>
|
||||
<string name="filename">Nombre del archivo</string>
|
||||
<string name="grant_permission">Conceder permiso</string>
|
||||
<string name="last_saved_pdf_files">PDF recientes guardados en este dispositivo:</string>
|
||||
@@ -36,8 +38,7 @@
|
||||
<string name="menu">Menú</string>
|
||||
<string name="new_document_warning">El escaneo actual se perderá. ¿Deseas continuar?</string>
|
||||
<string name="open">Abrir</string>
|
||||
<string name="open_pdf">Abrir PDF</string>
|
||||
<string name="pdf_saved_to">PDF guardado en %1s</string>
|
||||
<string name="open_file">Abrir archivo</string>
|
||||
<string name="reset_to_default">Restablecer valores predeterminados</string>
|
||||
<string name="resume">Reanudar</string>
|
||||
<string name="save">Guardar</string>
|
||||
@@ -45,8 +46,8 @@
|
||||
<string name="scan_in_progress">Escaneo en curso</string>
|
||||
<string name="settings">Ajustes</string>
|
||||
<string name="share">Compartir</string>
|
||||
<string name="share_pdf">Compartir PDF</string>
|
||||
<string name="storage_permission_denied">No se puede guardar el archivo PDF: permiso denegado</string>
|
||||
<string name="share_document">Compartir documento</string>
|
||||
<string name="storage_permission_denied">No se puede guardar el archivo: permiso denegado</string>
|
||||
<string name="turn_off_torch">Apagar linterna</string>
|
||||
<string name="turn_on_torch">Encender linterna</string>
|
||||
<string name="unknown_size">Tamaño desconocido</string>
|
||||
@@ -54,6 +55,10 @@
|
||||
<string name="view_full_list">Ver lista completa</string>
|
||||
<string name="view_the_full_license">Ver la licencia completa</string>
|
||||
<string name="yes">Sí</string>
|
||||
<plurals name="files_saved_to" tools:ignore="MissingQuantity">
|
||||
<item quantity="one">%2$s guardado en %3$s</item>
|
||||
<item quantity="other">%1$d archivos guardados en %3$s</item>
|
||||
</plurals>
|
||||
<plurals name="page_count" tools:ignore="MissingQuantity">
|
||||
<item quantity="one">%d página</item>
|
||||
<item quantity="other">%d páginas</item>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<string name="contact">Contact</string>
|
||||
<string name="copied_logs">Logs copiés dans le presse-papiers</string>
|
||||
<string name="copy_logs">Copier les logs</string>
|
||||
<string name="creating_pdf">Création du PDF…</string>
|
||||
<string name="creating_export">Préparation de l’export…</string>
|
||||
<string name="delete_page">Supprimer la page</string>
|
||||
<string name="delete_page_warning">Voulez-vous supprimer cette page ?</string>
|
||||
<string name="developer">Développeur</string>
|
||||
@@ -19,12 +19,14 @@
|
||||
<string name="download_dirname">Téléchargements</string>
|
||||
<string name="end_scan">Terminer le scan</string>
|
||||
<string name="error">Erreur : %1$s</string>
|
||||
<string name="error_no_app">Aucune application trouvée pour ouvrir ce fichier</string>
|
||||
<string name="error_no_document">Aucun document détecté</string>
|
||||
<string name="error_no_pdf_app">Aucune application trouvée pour ouvrir un PDF</string>
|
||||
<string name="error_save">Échec de l\'enregistrement du PDF</string>
|
||||
<string name="error_save">Échec de l\'enregistrement du fichier</string>
|
||||
<string name="export">Exporter</string>
|
||||
<string name="export_as">Exporter en %1$s</string>
|
||||
<string name="export_directory">Dossier d’export</string>
|
||||
<string name="export_pdf">Exporter en PDF</string>
|
||||
<string name="file_size">Taille du fichier : %1$s</string>
|
||||
<string name="file_size_total">Taille totale : %1$s</string>
|
||||
<string name="filename">Nom de fichier</string>
|
||||
<string name="grant_permission">Autoriser</string>
|
||||
<string name="last_saved_pdf_files">Derniers PDF enregistrés sur l’appareil :</string>
|
||||
@@ -36,8 +38,7 @@
|
||||
<string name="menu">Menu</string>
|
||||
<string name="new_document_warning">Le scan en cours sera perdu. Voulez-vous continuer ?</string>
|
||||
<string name="open">Ouvrir</string>
|
||||
<string name="open_pdf">Ouvrir le PDF</string>
|
||||
<string name="pdf_saved_to">PDF enregistré dans %1s</string>
|
||||
<string name="open_file">Ouvrir le fichier</string>
|
||||
<string name="reset_to_default">Réinitialiser par défaut</string>
|
||||
<string name="resume">Reprendre</string>
|
||||
<string name="save">Enregistrer</string>
|
||||
@@ -45,8 +46,8 @@
|
||||
<string name="scan_in_progress">Scan en cours</string>
|
||||
<string name="settings">Paramètres</string>
|
||||
<string name="share">Partager</string>
|
||||
<string name="share_pdf">Partager le PDF</string>
|
||||
<string name="storage_permission_denied">Impossible d’enregistrer le fichier PDF : permission refusée</string>
|
||||
<string name="share_document">Partager le document</string>
|
||||
<string name="storage_permission_denied">Impossible d’enregistrer le fichier : permission refusée</string>
|
||||
<string name="turn_off_torch">Éteindre la torche</string>
|
||||
<string name="turn_on_torch">Allumer la torche</string>
|
||||
<string name="unknown_size">Taille inconnue</string>
|
||||
@@ -54,6 +55,10 @@
|
||||
<string name="view_full_list">Voir la liste complète</string>
|
||||
<string name="view_the_full_license">Voir la licence complète</string>
|
||||
<string name="yes">Oui</string>
|
||||
<plurals name="files_saved_to" tools:ignore="MissingQuantity">
|
||||
<item quantity="one">%2$s enregistré dans %3$s</item>
|
||||
<item quantity="other">%1$d fichiers enregistrés dans %3$s</item>
|
||||
</plurals>
|
||||
<plurals name="page_count" tools:ignore="MissingQuantity">
|
||||
<item quantity="one">%d page</item>
|
||||
<item quantity="other">%d pages</item>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="about">Informazioni</string>
|
||||
<string name="add_page">Aggiungi pagina</string>
|
||||
<string name="app_tagline">Un\'app semplice e rispettosa per scansionare i tuoi documenti.</string>
|
||||
@@ -11,7 +11,7 @@
|
||||
<string name="contact">Contatti</string>
|
||||
<string name="copied_logs">Log copiati negli appunti</string>
|
||||
<string name="copy_logs">Copia log</string>
|
||||
<string name="creating_pdf">Creazione PDF…</string>
|
||||
<string name="creating_export">Preparazione dell’esportazione…</string>
|
||||
<string name="delete_page">Elimina pagina</string>
|
||||
<string name="delete_page_warning">Vuoi eliminare questa pagina?</string>
|
||||
<string name="developer">Sviluppatore</string>
|
||||
@@ -19,12 +19,14 @@
|
||||
<string name="download_dirname">Download</string>
|
||||
<string name="end_scan">Termina scansione</string>
|
||||
<string name="error">Errore: %1$s</string>
|
||||
<string name="error_no_app">Nessuna app trovata per aprire questo file</string>
|
||||
<string name="error_no_document">Nessun documento rilevato</string>
|
||||
<string name="error_no_pdf_app">Nessuna app trovata per aprire PDF</string>
|
||||
<string name="error_save">Salvataggio PDF fallito</string>
|
||||
<string name="error_save">Impossibile salvare il file</string>
|
||||
<string name="export">Esporta</string>
|
||||
<string name="export_as">Esporta come %1$s</string>
|
||||
<string name="export_directory">Cartella di esportazione</string>
|
||||
<string name="export_pdf">Esporta PDF</string>
|
||||
<string name="file_size">Dimensione file: %1$s</string>
|
||||
<string name="file_size">Dimensione del file: %1$s</string>
|
||||
<string name="file_size_total">Dimensione totale: %1$s</string>
|
||||
<string name="filename">Nome file</string>
|
||||
<string name="grant_permission">Concendi autorizzazione</string>
|
||||
<string name="last_saved_pdf_files">PDF recenti salvati su questo dispositivo:</string>
|
||||
@@ -36,8 +38,7 @@
|
||||
<string name="menu">Menu</string>
|
||||
<string name="new_document_warning">La scansiona attuale verrà persa. Vuoi continuare?</string>
|
||||
<string name="open">Apri</string>
|
||||
<string name="open_pdf">Apri PDF</string>
|
||||
<string name="pdf_saved_to">PDF salvato in %1s</string>
|
||||
<string name="open_file">Apri file</string>
|
||||
<string name="reset_to_default">Ripristina impostazioni predefinite</string>
|
||||
<string name="resume">Riprendi</string>
|
||||
<string name="save">Salva</string>
|
||||
@@ -45,8 +46,8 @@
|
||||
<string name="scan_in_progress">Scansione in corso</string>
|
||||
<string name="settings">Impostazioni</string>
|
||||
<string name="share">Condividi</string>
|
||||
<string name="share_pdf">Condividi PDF</string>
|
||||
<string name="storage_permission_denied">Impossibile salvare il file PDF: autorizzazione negata</string>
|
||||
<string name="share_document">Condividi documento</string>
|
||||
<string name="storage_permission_denied">Impossibile salvare il file: permesso negato</string>
|
||||
<string name="turn_off_torch">Spegni la torcia</string>
|
||||
<string name="turn_on_torch">Accendi la torcia</string>
|
||||
<string name="unknown_size">Dimensione sconosciuta</string>
|
||||
@@ -54,7 +55,11 @@
|
||||
<string name="view_full_list">Vedi l\'elenco completo</string>
|
||||
<string name="view_the_full_license">Vedi la licenza completa</string>
|
||||
<string name="yes">Sì</string>
|
||||
<plurals name="page_count">
|
||||
<plurals name="files_saved_to" tools:ignore="MissingQuantity">
|
||||
<item quantity="one">%2$s salvato in %3$s</item>
|
||||
<item quantity="other">%1$d file salvati in %3$s</item>
|
||||
</plurals>
|
||||
<plurals name="page_count" tools:ignore="MissingQuantity">
|
||||
<item quantity="one">%d pagina</item>
|
||||
<item quantity="other">%d pagine</item>
|
||||
</plurals>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<string name="contact">Contato</string>
|
||||
<string name="copied_logs">Registros copiados para a área de transferência</string>
|
||||
<string name="copy_logs">Copiar registros</string>
|
||||
<string name="creating_pdf">Criando PDF…</string>
|
||||
<string name="creating_export">Preparando exportação…</string>
|
||||
<string name="delete_page">Excluir página</string>
|
||||
<string name="delete_page_warning">Deseja excluir esta página?</string>
|
||||
<string name="developer">Desenvolvedor</string>
|
||||
@@ -19,12 +19,14 @@
|
||||
<string name="download_dirname">Downloads</string>
|
||||
<string name="end_scan">Finalizar digitalização</string>
|
||||
<string name="error">Erro: %1$s</string>
|
||||
<string name="error_no_app">Nenhum app encontrado para abrir este arquivo</string>
|
||||
<string name="error_no_document">Nenhum documento detectado</string>
|
||||
<string name="error_no_pdf_app">Nenhum aplicativo encontrado para abrir PDF</string>
|
||||
<string name="error_save">Falha ao salvar PDF</string>
|
||||
<string name="error_save">Falha ao salvar o arquivo</string>
|
||||
<string name="export">Exportar</string>
|
||||
<string name="export_as">Exportar como %1$s</string>
|
||||
<string name="export_directory">Diretório de exportação</string>
|
||||
<string name="export_pdf">Exportar PDF</string>
|
||||
<string name="file_size">Tamanho do arquivo: %1$s</string>
|
||||
<string name="file_size_total">Tamanho total: %1$s</string>
|
||||
<string name="filename">Nome do arquivo</string>
|
||||
<string name="grant_permission">Conceder permissão</string>
|
||||
<string name="last_saved_pdf_files">PDFs recentes salvos neste dispositivo:</string>
|
||||
@@ -36,8 +38,7 @@
|
||||
<string name="menu">Menu</string>
|
||||
<string name="new_document_warning">A digitalização atual será perdida. Deseja continuar?</string>
|
||||
<string name="open">Abrir</string>
|
||||
<string name="open_pdf">Abrir PDF</string>
|
||||
<string name="pdf_saved_to">PDF salvo em %1s</string>
|
||||
<string name="open_file">Abrir arquivo</string>
|
||||
<string name="reset_to_default">Restaurar padrão</string>
|
||||
<string name="resume">Retomar</string>
|
||||
<string name="save">Salvar</string>
|
||||
@@ -45,8 +46,8 @@
|
||||
<string name="scan_in_progress">Digitalização em andamento</string>
|
||||
<string name="settings">Configurações</string>
|
||||
<string name="share">Compartilhar</string>
|
||||
<string name="share_pdf">Compartilhar PDF</string>
|
||||
<string name="storage_permission_denied">Não foi possível salvar o arquivo PDF: permissão negada</string>
|
||||
<string name="share_document">Compartilhar documento</string>
|
||||
<string name="storage_permission_denied">Não foi possível salvar o arquivo: permissão negada</string>
|
||||
<string name="turn_off_torch">Desligar lanterna</string>
|
||||
<string name="turn_on_torch">Ligar lanterna</string>
|
||||
<string name="unknown_size">Tamanho desconhecido</string>
|
||||
@@ -54,6 +55,10 @@
|
||||
<string name="view_full_list">Ver lista completa</string>
|
||||
<string name="view_the_full_license">Ver licença completa</string>
|
||||
<string name="yes">Sim</string>
|
||||
<plurals name="files_saved_to" tools:ignore="MissingQuantity">
|
||||
<item quantity="one">%2$s salvo em %3$s</item>
|
||||
<item quantity="other">%1$d arquivos salvos em %3$s</item>
|
||||
</plurals>
|
||||
<plurals name="page_count" tools:ignore="MissingQuantity">
|
||||
<item quantity="one">%d página</item>
|
||||
<item quantity="other">%d páginas</item>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<string name="contact">Контакты</string>
|
||||
<string name="copied_logs">Журналы скопированы в буфер обмена</string>
|
||||
<string name="copy_logs">Копировать журналы</string>
|
||||
<string name="creating_pdf">Создание PDF…</string>
|
||||
<string name="creating_export">Подготовка экспорта…</string>
|
||||
<string name="delete_page">Удалить страницу</string>
|
||||
<string name="delete_page_warning">Вы желаете удалить эту страницу?</string>
|
||||
<string name="developer">Разработчик</string>
|
||||
@@ -19,12 +19,14 @@
|
||||
<string name="download_dirname">Download</string>
|
||||
<string name="end_scan">Закончить</string>
|
||||
<string name="error">Ошибка: %1$s</string>
|
||||
<string name="error_no_app">Не найдено приложение для открытия этого файла</string>
|
||||
<string name="error_no_document">Документ не обнаружен</string>
|
||||
<string name="error_no_pdf_app">Приложения для работы с PDF не обнаружено</string>
|
||||
<string name="error_save">Сбой при сохранении PDF</string>
|
||||
<string name="error_save">Не удалось сохранить файл</string>
|
||||
<string name="export">Экспорт</string>
|
||||
<string name="export_as">Экспортировать как %1$s</string>
|
||||
<string name="export_directory">Папка экспорта</string>
|
||||
<string name="export_pdf">Экспорт PDF</string>
|
||||
<string name="file_size">Размер файла: %1$s</string>
|
||||
<string name="file_size_total">Общий размер: %1$s</string>
|
||||
<string name="filename">Имя файла</string>
|
||||
<string name="grant_permission">Предоставить разрешение</string>
|
||||
<string name="last_saved_pdf_files">Последние PDF, сохранённые на этом устройстве:</string>
|
||||
@@ -36,8 +38,7 @@
|
||||
<string name="menu">Меню</string>
|
||||
<string name="new_document_warning">Результаты текущего сканирования будут потеряны. Желаете продолжить?</string>
|
||||
<string name="open">Открыть</string>
|
||||
<string name="open_pdf">Открыть PDF</string>
|
||||
<string name="pdf_saved_to">PDF сохранен в %1s</string>
|
||||
<string name="open_file">Открыть файл</string>
|
||||
<string name="reset_to_default">Сбросить по умолчанию</string>
|
||||
<string name="resume">Продолжить</string>
|
||||
<string name="save">Сохранить</string>
|
||||
@@ -45,8 +46,8 @@
|
||||
<string name="scan_in_progress">Сканирование выполняется</string>
|
||||
<string name="settings">Настройки</string>
|
||||
<string name="share">Поделиться</string>
|
||||
<string name="share_pdf">Поделиться PDF</string>
|
||||
<string name="storage_permission_denied">Не удается сохранить файл PDF: в разрешении отказано</string>
|
||||
<string name="share_document">Поделиться документом</string>
|
||||
<string name="storage_permission_denied">Невозможно сохранить файл: доступ запрещён</string>
|
||||
<string name="turn_off_torch">Выключить фонарик</string>
|
||||
<string name="turn_on_torch">Включить фонарик</string>
|
||||
<string name="unknown_size">Неизвестный размер</string>
|
||||
@@ -54,6 +55,12 @@
|
||||
<string name="view_full_list">Просмотреть полный список</string>
|
||||
<string name="view_the_full_license">Просмотреть полную лицензию</string>
|
||||
<string name="yes">Да</string>
|
||||
<plurals name="files_saved_to">
|
||||
<item quantity="one">%2$s сохранён в %3$s</item>
|
||||
<item quantity="few">%1$d файла сохранены в %3$s</item>
|
||||
<item quantity="many">%1$d файлов сохранено в %3$s</item>
|
||||
<item quantity="other">%1$d файла сохранено в %3$s</item>
|
||||
</plurals>
|
||||
<plurals name="page_count">
|
||||
<item quantity="one">%d страница</item>
|
||||
<item quantity="few">%d страницы</item>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<string name="contact">联系人</string>
|
||||
<string name="copied_logs">日志已复制到剪贴板</string>
|
||||
<string name="copy_logs">复制日志</string>
|
||||
<string name="creating_pdf">正在创建 PDF…</string>
|
||||
<string name="creating_export">正在准备导出…</string>
|
||||
<string name="delete_page">删除页面</string>
|
||||
<string name="delete_page_warning">是否要删除此页面?</string>
|
||||
<string name="developer">开发者</string>
|
||||
@@ -19,12 +19,14 @@
|
||||
<string name="download_dirname">下载</string>
|
||||
<string name="end_scan">结束扫描</string>
|
||||
<string name="error">错误: %1$s</string>
|
||||
<string name="error_no_app">未找到可打开此文件的应用</string>
|
||||
<string name="error_no_document">未检测到任何文档</string>
|
||||
<string name="error_no_pdf_app">未找到可打开PDF的应用</string>
|
||||
<string name="error_save">保存PDF失败</string>
|
||||
<string name="error_save">无法保存文件</string>
|
||||
<string name="export">导出</string>
|
||||
<string name="export_as">导出为 %1$s</string>
|
||||
<string name="export_directory">导出目录</string>
|
||||
<string name="export_pdf">导出PDF</string>
|
||||
<string name="file_size">文件大小: %1$s</string>
|
||||
<string name="file_size">文件大小:%1$s</string>
|
||||
<string name="file_size_total">总大小:%1$s</string>
|
||||
<string name="filename">文件名字</string>
|
||||
<string name="grant_permission">授予权限</string>
|
||||
<string name="last_saved_pdf_files">最近保存在此设备上的 PDF:</string>
|
||||
@@ -36,8 +38,7 @@
|
||||
<string name="menu">菜单</string>
|
||||
<string name="new_document_warning">当前扫描将丢失。是否继续?</string>
|
||||
<string name="open">打开</string>
|
||||
<string name="open_pdf">打开 PDF</string>
|
||||
<string name="pdf_saved_to">PDF 已保存到 %1$s</string>
|
||||
<string name="open_file">打开文件</string>
|
||||
<string name="reset_to_default">恢复默认设置</string>
|
||||
<string name="resume">恢复</string>
|
||||
<string name="save">保存</string>
|
||||
@@ -45,8 +46,8 @@
|
||||
<string name="scan_in_progress">正在进行扫描</string>
|
||||
<string name="settings">设置</string>
|
||||
<string name="share">共享</string>
|
||||
<string name="share_pdf">共享 PDF</string>
|
||||
<string name="storage_permission_denied">无法保存PDF文件:权限被拒绝</string>
|
||||
<string name="share_document">分享文档</string>
|
||||
<string name="storage_permission_denied">无法保存文件:权限被拒绝</string>
|
||||
<string name="turn_off_torch">关闭手电筒</string>
|
||||
<string name="turn_on_torch">打开手电筒</string>
|
||||
<string name="unknown_size">未知大小</string>
|
||||
@@ -54,8 +55,10 @@
|
||||
<string name="view_full_list">查看完整列表</string>
|
||||
<string name="view_the_full_license">查看完整许可证</string>
|
||||
<string name="yes">是</string>
|
||||
<plurals name="files_saved_to">
|
||||
<item quantity="other">%1$d 个文件已保存到 %3$s</item>
|
||||
</plurals>
|
||||
<plurals name="page_count">
|
||||
<item quantity="one">%d 页</item>
|
||||
<item quantity="other">%d 页</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<string name="contact">Contact</string>
|
||||
<string name="copied_logs">Logs copied to clipboard</string>
|
||||
<string name="copy_logs">Copy logs</string>
|
||||
<string name="creating_pdf">Creating PDF…</string>
|
||||
<string name="creating_export">Preparing export…</string>
|
||||
<string name="delete_page">Delete page</string>
|
||||
<string name="delete_page_warning">Do you want to delete this page?</string>
|
||||
<string name="developer">Developer</string>
|
||||
@@ -20,12 +20,14 @@
|
||||
<string name="download_dirname">Downloads</string>
|
||||
<string name="end_scan">End scan</string>
|
||||
<string name="error">Error: %1$s</string>
|
||||
<string name="error_no_app">No app found to open this file</string>
|
||||
<string name="error_no_document">No document detected</string>
|
||||
<string name="error_no_pdf_app">No app found to open PDF</string>
|
||||
<string name="error_save">Failed to save PDF</string>
|
||||
<string name="error_save">Failed to save file</string>
|
||||
<string name="export">Export</string>
|
||||
<string name="export_as">Export as %1$s</string>
|
||||
<string name="export_directory">Export directory</string>
|
||||
<string name="export_pdf">Export PDF</string>
|
||||
<string name="file_size">File size: %1$s</string>
|
||||
<string name="file_size_total">Total size: %1$s</string>
|
||||
<string name="filename">Filename</string>
|
||||
<string name="grant_permission">Grant permission</string>
|
||||
<string name="last_saved_pdf_files">Recent PDFs saved on this device:</string>
|
||||
@@ -37,8 +39,7 @@
|
||||
<string name="menu">Menu</string>
|
||||
<string name="new_document_warning">The current scan will be lost. Do you want to continue?</string>
|
||||
<string name="open">Open</string>
|
||||
<string name="open_pdf">Open PDF</string>
|
||||
<string name="pdf_saved_to">PDF saved in %1s</string>
|
||||
<string name="open_file">Open file</string>
|
||||
<string name="reset_to_default">Reset to default</string>
|
||||
<string name="resume">Resume</string>
|
||||
<string name="save">Save</string>
|
||||
@@ -46,8 +47,8 @@
|
||||
<string name="scan_in_progress">Scan in progress</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="share_pdf">Share PDF</string>
|
||||
<string name="storage_permission_denied">Cannot save PDF file: permission was denied</string>
|
||||
<string name="share_document">Share document</string>
|
||||
<string name="storage_permission_denied">Cannot save file: permission was denied</string>
|
||||
<string name="turn_off_torch">Turn off torch</string>
|
||||
<string name="turn_on_torch">Turn on torch</string>
|
||||
<string name="unknown_size">Unknown size</string>
|
||||
@@ -55,6 +56,10 @@
|
||||
<string name="view_full_list">View full list</string>
|
||||
<string name="view_the_full_license">View the full license</string>
|
||||
<string name="yes">Yes</string>
|
||||
<plurals name="files_saved_to">
|
||||
<item quantity="one">%2$s saved to %3$s</item>
|
||||
<item quantity="other">%1$d files saved to %3$s</item>
|
||||
</plurals>
|
||||
<plurals name="page_count">
|
||||
<item quantity="one">%d page</item>
|
||||
<item quantity="other">%d pages</item>
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.io.File
|
||||
import java.io.OutputStream
|
||||
import kotlin.io.path.createTempDirectory
|
||||
|
||||
class PdfFileManagerTest {
|
||||
class FileManagerTest {
|
||||
|
||||
val pdfDir: File = createTempDirectory().toFile()
|
||||
val externalDir: File = createTempDirectory().toFile()
|
||||
@@ -33,7 +33,7 @@ class PdfFileManagerTest {
|
||||
val f = File(externalDir, "f.pdf")
|
||||
assertThat(f).doesNotExist()
|
||||
|
||||
val manager = PdfFileManager(pdfDir, externalDir, dummyPdfWriter)
|
||||
val manager = FileManager(pdfDir, externalDir, dummyPdfWriter)
|
||||
assertThat(manager.copyToExternalDir(original))
|
||||
.isEqualTo(f)
|
||||
.hasContent("original content")
|
||||
@@ -48,7 +48,7 @@ class PdfFileManagerTest {
|
||||
@Test
|
||||
fun cleanUpOldFiles() {
|
||||
val subDir = File(pdfDir,"subDir")
|
||||
val manager = PdfFileManager(subDir, externalDir, dummyPdfWriter)
|
||||
val manager = FileManager(subDir, externalDir, dummyPdfWriter)
|
||||
manager.cleanUpOldFiles(10)
|
||||
assertThat(subDir).doesNotExist()
|
||||
|
||||
@@ -76,7 +76,7 @@ class PdfFileManagerTest {
|
||||
return list.size
|
||||
}
|
||||
}
|
||||
val manager = PdfFileManager(pdfDir, externalDir, fakePdfWriter)
|
||||
val manager = FileManager(pdfDir, externalDir, fakePdfWriter)
|
||||
val jpegs = listOf(byteArrayOf(0x01, 0x02), byteArrayOf(0x11)).asSequence()
|
||||
val pdf = manager.generatePdf(jpegs)
|
||||
assertThat(pdf.pageCount).isEqualTo(2)
|
||||
@@ -87,9 +87,9 @@ class PdfFileManagerTest {
|
||||
|
||||
@Test
|
||||
fun addExtensionIfMissing() {
|
||||
assertThat(PdfFileManager.addExtensionIfMissing("f1.pdf")).isEqualTo("f1.pdf")
|
||||
assertThat(PdfFileManager.addExtensionIfMissing("f2.PDF")).isEqualTo("f2.PDF")
|
||||
assertThat(PdfFileManager.addExtensionIfMissing("f3")).isEqualTo("f3.pdf")
|
||||
assertThat(PdfFileManager.addExtensionIfMissing("f4.txt")).isEqualTo("f4.txt.pdf")
|
||||
assertThat(FileManager.addPdfExtensionIfMissing("f1.pdf")).isEqualTo("f1.pdf")
|
||||
assertThat(FileManager.addPdfExtensionIfMissing("f2.PDF")).isEqualTo("f2.PDF")
|
||||
assertThat(FileManager.addPdfExtensionIfMissing("f3")).isEqualTo("f3.pdf")
|
||||
assertThat(FileManager.addPdfExtensionIfMissing("f4.txt")).isEqualTo("f4.txt.pdf")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user