HomeScreen: list of recent documents
This commit is contained in:
committed by
pynicolas
parent
eb1f3b64ed
commit
f3e814b93a
@@ -3,6 +3,7 @@ plugins {
|
|||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
alias(libs.plugins.aboutLibraries)
|
alias(libs.plugins.aboutLibraries)
|
||||||
|
alias(libs.plugins.protobuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -85,6 +86,8 @@ dependencies {
|
|||||||
implementation(libs.androidx.camera.camera2)
|
implementation(libs.androidx.camera.camera2)
|
||||||
implementation(libs.androidx.camera.lifecycle)
|
implementation(libs.androidx.camera.lifecycle)
|
||||||
implementation(libs.androidx.camera.view)
|
implementation(libs.androidx.camera.view)
|
||||||
|
implementation(libs.androidx.datastore)
|
||||||
|
implementation(libs.protobuf.javalite)
|
||||||
implementation(libs.litert)
|
implementation(libs.litert)
|
||||||
implementation(libs.litert.support)
|
implementation(libs.litert.support)
|
||||||
implementation(libs.litert.metadata)
|
implementation(libs.litert.metadata)
|
||||||
@@ -107,3 +110,19 @@ dependencies {
|
|||||||
aboutLibraries {
|
aboutLibraries {
|
||||||
android.registerAndroidTasks = true
|
android.registerAndroidTasks = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protobuf {
|
||||||
|
protoc {
|
||||||
|
artifact = "com.google.protobuf:protoc:4.32.0"
|
||||||
|
}
|
||||||
|
generateProtoTasks {
|
||||||
|
all().forEach { task ->
|
||||||
|
task.builtins {
|
||||||
|
create("java") {
|
||||||
|
option("lite")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import androidx.activity.viewModels
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -72,11 +73,14 @@ class MainActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
when (val screen = currentScreen) {
|
when (val screen = currentScreen) {
|
||||||
is Screen.Home -> {
|
is Screen.Home -> {
|
||||||
|
val recentDocs by viewModel.recentDocuments.collectAsStateWithLifecycle()
|
||||||
HomeScreen(
|
HomeScreen(
|
||||||
cameraPermission = cameraPermission,
|
cameraPermission = cameraPermission,
|
||||||
currentDocument = document,
|
currentDocument = document,
|
||||||
navigation = navigation,
|
navigation = navigation,
|
||||||
onStartNewScan = navigation.toCameraScreen,
|
onStartNewScan = navigation.toCameraScreen,
|
||||||
|
recentDocuments = recentDocs,
|
||||||
|
onOpenPdf = { file -> openPdf(file.toUri()) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Screen.Camera -> {
|
is Screen.Camera -> {
|
||||||
@@ -149,6 +153,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
appScope.launch {
|
appScope.launch {
|
||||||
try {
|
try {
|
||||||
val targetFile = viewModel.saveFile(generatedPdf.file)
|
val targetFile = viewModel.saveFile(generatedPdf.file)
|
||||||
|
viewModel.addRecentDocument(targetFile.absolutePath, generatedPdf.pageCount)
|
||||||
|
|
||||||
suspendCancellableCoroutine { continuation ->
|
suspendCancellableCoroutine { continuation ->
|
||||||
MediaScannerConnection.scanFile(
|
MediaScannerConnection.scanFile(
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import android.os.Environment
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.camera.core.ImageProxy
|
import androidx.camera.core.ImageProxy
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
@@ -37,7 +38,9 @@ import kotlinx.coroutines.flow.stateIn
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.mydomain.myscan.data.recentDocumentsDataStore
|
||||||
import org.mydomain.myscan.ui.PdfGenerationUiState
|
import org.mydomain.myscan.ui.PdfGenerationUiState
|
||||||
|
import org.mydomain.myscan.ui.RecentDocumentUiState
|
||||||
import org.mydomain.myscan.view.DocumentUiModel
|
import org.mydomain.myscan.view.DocumentUiModel
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -46,6 +49,7 @@ class MainViewModel(
|
|||||||
private val imageSegmentationService: ImageSegmentationService,
|
private val imageSegmentationService: ImageSegmentationService,
|
||||||
private val imageRepository: ImageRepository,
|
private val imageRepository: ImageRepository,
|
||||||
private val pdfFileManager: PdfFileManager,
|
private val pdfFileManager: PdfFileManager,
|
||||||
|
private val recentDocumentsDataStore: DataStore<RecentDocuments>,
|
||||||
): ViewModel() {
|
): ViewModel() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -59,6 +63,7 @@ class MainViewModel(
|
|||||||
File(context.cacheDir, "pdfs"),
|
File(context.cacheDir, "pdfs"),
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||||
AndroidPdfWriter()),
|
AndroidPdfWriter()),
|
||||||
|
context.recentDocumentsDataStore,
|
||||||
) as T
|
) as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,6 +319,42 @@ class MainViewModel(
|
|||||||
fun cleanUpOldPdfs(thresholdInMillis: Int) {
|
fun cleanUpOldPdfs(thresholdInMillis: Int) {
|
||||||
pdfFileManager.cleanUpOldFiles(thresholdInMillis)
|
pdfFileManager.cleanUpOldFiles(thresholdInMillis)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val recentDocuments: StateFlow<List<RecentDocumentUiState>> =
|
||||||
|
recentDocumentsDataStore.data.map {
|
||||||
|
it.documentsList.map {
|
||||||
|
doc ->
|
||||||
|
RecentDocumentUiState(
|
||||||
|
file = File(doc.filePath),
|
||||||
|
saveTimestamp = doc.createdAt,
|
||||||
|
pageCount = doc.pageCount,
|
||||||
|
)
|
||||||
|
}.filter { doc -> doc.file.exists() }
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(5_000),
|
||||||
|
initialValue = emptyList(),
|
||||||
|
)
|
||||||
|
fun addRecentDocument(filePath: String, pageCount: Int) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
recentDocumentsDataStore.updateData { current ->
|
||||||
|
val newDoc = RecentDocument.newBuilder()
|
||||||
|
.setFilePath(filePath)
|
||||||
|
.setPageCount(pageCount)
|
||||||
|
.setCreatedAt(System.currentTimeMillis())
|
||||||
|
.build()
|
||||||
|
current.toBuilder()
|
||||||
|
.addDocuments(0, newDoc)
|
||||||
|
.also { builder ->
|
||||||
|
while (builder.documentsCount > 10) {
|
||||||
|
builder.removeDocuments(builder.documentsCount - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class GeneratedPdf(
|
data class GeneratedPdf(
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Pierre-Yves Nicolas
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.mydomain.myscan.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.core.CorruptionException
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.core.Serializer
|
||||||
|
import androidx.datastore.dataStore
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException
|
||||||
|
import org.mydomain.myscan.RecentDocuments
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
object RecentDocumentsSerializer : Serializer<RecentDocuments> {
|
||||||
|
override val defaultValue: RecentDocuments = RecentDocuments.getDefaultInstance()
|
||||||
|
|
||||||
|
override suspend fun readFrom(input: InputStream): RecentDocuments {
|
||||||
|
return try {
|
||||||
|
RecentDocuments.parseFrom(input)
|
||||||
|
} catch (e: InvalidProtocolBufferException) {
|
||||||
|
throw CorruptionException("Cannot read proto.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun writeTo(
|
||||||
|
t: RecentDocuments,
|
||||||
|
output: OutputStream
|
||||||
|
) = t.writeTo(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
val Context.recentDocumentsDataStore: DataStore<RecentDocuments> by dataStore(
|
||||||
|
fileName = "recent_documents.pb",
|
||||||
|
serializer = RecentDocumentsSerializer
|
||||||
|
)
|
||||||
@@ -16,6 +16,7 @@ package org.mydomain.myscan.ui
|
|||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import org.mydomain.myscan.GeneratedPdf
|
import org.mydomain.myscan.GeneratedPdf
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
data class PdfGenerationUiState(
|
data class PdfGenerationUiState(
|
||||||
val isGenerating: Boolean = false,
|
val isGenerating: Boolean = false,
|
||||||
@@ -25,3 +26,9 @@ data class PdfGenerationUiState(
|
|||||||
val saveDirectoryName: String? = null,
|
val saveDirectoryName: String? = null,
|
||||||
val errorMessage: String? = null
|
val errorMessage: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class RecentDocumentUiState(
|
||||||
|
val file: File,
|
||||||
|
val saveTimestamp: Long,
|
||||||
|
val pageCount: Int,
|
||||||
|
)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package org.mydomain.myscan.view
|
package org.mydomain.myscan.view
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -27,10 +28,14 @@ import androidx.compose.foundation.rememberScrollState
|
|||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.PhotoCamera
|
import androidx.compose.material.icons.filled.PhotoCamera
|
||||||
|
import androidx.compose.material.icons.filled.PictureAsPdf
|
||||||
import androidx.compose.material3.BottomAppBar
|
import androidx.compose.material3.BottomAppBar
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -49,7 +54,10 @@ import org.mydomain.myscan.CameraPermissionState
|
|||||||
import org.mydomain.myscan.Navigation
|
import org.mydomain.myscan.Navigation
|
||||||
import org.mydomain.myscan.R
|
import org.mydomain.myscan.R
|
||||||
import org.mydomain.myscan.rememberCameraPermissionState
|
import org.mydomain.myscan.rememberCameraPermissionState
|
||||||
|
import org.mydomain.myscan.ui.RecentDocumentUiState
|
||||||
import org.mydomain.myscan.ui.theme.MyScanTheme
|
import org.mydomain.myscan.ui.theme.MyScanTheme
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -57,7 +65,9 @@ fun HomeScreen(
|
|||||||
cameraPermission: CameraPermissionState,
|
cameraPermission: CameraPermissionState,
|
||||||
currentDocument: DocumentUiModel,
|
currentDocument: DocumentUiModel,
|
||||||
navigation: Navigation,
|
navigation: Navigation,
|
||||||
onStartNewScan: () -> Unit
|
onStartNewScan: () -> Unit,
|
||||||
|
recentDocuments: List<RecentDocumentUiState>,
|
||||||
|
onOpenPdf: (File) -> Unit,
|
||||||
) {
|
) {
|
||||||
val showCloseDocDialog = rememberSaveable { mutableStateOf(false) }
|
val showCloseDocDialog = rememberSaveable { mutableStateOf(false) }
|
||||||
Scaffold (
|
Scaffold (
|
||||||
@@ -104,6 +114,11 @@ fun HomeScreen(
|
|||||||
CurrentDocumentCard(currentDocument, navigation)
|
CurrentDocumentCard(currentDocument, navigation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (recentDocuments.isNotEmpty()) {
|
||||||
|
SectionTitle(stringResource(R.string.last_saved_documents))
|
||||||
|
RecentDocumentList(recentDocuments, onOpenPdf)
|
||||||
|
}
|
||||||
|
|
||||||
if (showCloseDocDialog.value) {
|
if (showCloseDocDialog.value) {
|
||||||
NewDocumentDialog(
|
NewDocumentDialog(
|
||||||
onConfirm = onStartNewScan,
|
onConfirm = onStartNewScan,
|
||||||
@@ -166,6 +181,32 @@ private fun CurrentDocumentCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RecentDocumentList(
|
||||||
|
recentDocuments: List<RecentDocumentUiState>,
|
||||||
|
onOpenPdf: (File) -> Unit
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
val maxListSize = 5
|
||||||
|
recentDocuments.subList(0, min(maxListSize, recentDocuments.size)).forEach { doc ->
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text(doc.file.name) },
|
||||||
|
supportingContent = {
|
||||||
|
Text(
|
||||||
|
text = pageCountText(doc.pageCount) + " • " +
|
||||||
|
formatDate(doc.saveTimestamp, LocalContext.current)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Icon(Icons.Default.PictureAsPdf, contentDescription = null)
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable { onOpenPdf(doc.file) }
|
||||||
|
)
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SectionTitle(text: String) {
|
private fun SectionTitle(text: String) {
|
||||||
Text(
|
Text(
|
||||||
@@ -183,7 +224,9 @@ fun HomeScreenPreviewOnFirstLaunch() {
|
|||||||
cameraPermission = rememberCameraPermissionState(),
|
cameraPermission = rememberCameraPermissionState(),
|
||||||
currentDocument = DocumentUiModel(listOf()) { _ -> null },
|
currentDocument = DocumentUiModel(listOf()) { _ -> null },
|
||||||
navigation = dummyNavigation(),
|
navigation = dummyNavigation(),
|
||||||
onStartNewScan = {}
|
onStartNewScan = {},
|
||||||
|
recentDocuments = listOf(),
|
||||||
|
onOpenPdf = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,7 +241,12 @@ fun HomeScreenPreviewWithCurrentDocument() {
|
|||||||
listOf("gallica.bnf.fr-bpt6k5530456s-1.jpg"),
|
listOf("gallica.bnf.fr-bpt6k5530456s-1.jpg"),
|
||||||
LocalContext.current),
|
LocalContext.current),
|
||||||
navigation = dummyNavigation(),
|
navigation = dummyNavigation(),
|
||||||
onStartNewScan = {}
|
onStartNewScan = {},
|
||||||
|
recentDocuments = listOf(
|
||||||
|
RecentDocumentUiState(File("/path/my_file.pdf"), 1755971180000, 3),
|
||||||
|
RecentDocumentUiState(File("/path/scan2.pdf"), 1755000500000, 1)
|
||||||
|
),
|
||||||
|
onOpenPdf = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,21 @@
|
|||||||
*/
|
*/
|
||||||
package org.mydomain.myscan.view
|
package org.mydomain.myscan.view
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.format.DateFormat
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import org.mydomain.myscan.R
|
import org.mydomain.myscan.R
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun pageCountText(quantity: Int): String {
|
fun pageCountText(quantity: Int): String {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
return context.resources.getQuantityString(R.plurals.page_count, quantity, quantity)
|
return context.resources.getQuantityString(R.plurals.page_count, quantity, quantity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun formatDate(timestamp: Long, context: Context): String {
|
||||||
|
val date = Date(timestamp)
|
||||||
|
return DateFormat.getMediumDateFormat(context).format(date) + " " +
|
||||||
|
DateFormat.getTimeFormat(context).format(date)
|
||||||
|
}
|
||||||
|
|||||||
14
app/src/main/proto/recent_documents.proto
Normal file
14
app/src/main/proto/recent_documents.proto
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option java_package = "org.mydomain.myscan";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
message RecentDocument {
|
||||||
|
string file_path = 1;
|
||||||
|
int64 created_at = 2; // timestamp in ms
|
||||||
|
int32 page_count = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RecentDocuments {
|
||||||
|
repeated RecentDocument documents = 1;
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
<string name="export_pdf">Exporter en PDF</string>
|
<string name="export_pdf">Exporter en PDF</string>
|
||||||
<string name="filename">Nom de fichier</string>
|
<string name="filename">Nom de fichier</string>
|
||||||
<string name="grant_permission">Autoriser</string>
|
<string name="grant_permission">Autoriser</string>
|
||||||
|
<string name="last_saved_documents">Derniers documents enregistrés</string>
|
||||||
<string name="libraries">Bibliothèques</string>
|
<string name="libraries">Bibliothèques</string>
|
||||||
<string name="libraries_intro">Cette application utilise plusieurs bibliothèques open source, notamment :</string>
|
<string name="libraries_intro">Cette application utilise plusieurs bibliothèques open source, notamment :</string>
|
||||||
<string name="libraries_open_source">Bibliothèques open source</string>
|
<string name="libraries_open_source">Bibliothèques open source</string>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
<string name="export_pdf">Export PDF</string>
|
<string name="export_pdf">Export PDF</string>
|
||||||
<string name="filename">Filename</string>
|
<string name="filename">Filename</string>
|
||||||
<string name="grant_permission">Grant permission</string>
|
<string name="grant_permission">Grant permission</string>
|
||||||
|
<string name="last_saved_documents">Last saved documents</string>
|
||||||
<string name="libraries">Libraries</string>
|
<string name="libraries">Libraries</string>
|
||||||
<string name="libraries_intro">This application uses several open-source libraries, including:</string>
|
<string name="libraries_intro">This application uses several open-source libraries, including:</string>
|
||||||
<string name="libraries_open_source">Open-source libraries</string>
|
<string name="libraries_open_source">Open-source libraries</string>
|
||||||
|
|||||||
@@ -10,12 +10,15 @@ lifecycleRuntimeKtx = "2.9.1"
|
|||||||
activityCompose = "1.10.1"
|
activityCompose = "1.10.1"
|
||||||
composeBom = "2025.06.01"
|
composeBom = "2025.06.01"
|
||||||
camerax = "1.4.2"
|
camerax = "1.4.2"
|
||||||
|
datastore = "1.1.7"
|
||||||
litert = "1.4.0"
|
litert = "1.4.0"
|
||||||
opencv = "4.12.0"
|
opencv = "4.12.0"
|
||||||
assertj = "3.27.3"
|
assertj = "3.27.3"
|
||||||
pdfbox = "2.0.27.0"
|
pdfbox = "2.0.27.0"
|
||||||
zoomable = "2.8.1"
|
zoomable = "2.8.1"
|
||||||
aboutLibraries = "12.2.4"
|
aboutLibraries = "12.2.4"
|
||||||
|
protobuf = "0.9.5"
|
||||||
|
protobufJavaLite = "4.32.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
@@ -40,6 +43,8 @@ androidx-camera-core = { group = "androidx.camera", name = "camera-core", versio
|
|||||||
androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" }
|
androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" }
|
||||||
androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" }
|
androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" }
|
||||||
androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "camerax" }
|
androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "camerax" }
|
||||||
|
androidx-datastore = { group = "androidx.datastore", name = "datastore" , version.ref = "datastore" }
|
||||||
|
protobuf-javalite = { group = "com.google.protobuf", name="protobuf-javalite", version.ref = "protobufJavaLite"}
|
||||||
litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = "litert" }
|
litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = "litert" }
|
||||||
litert-support = { group = "com.google.ai.edge.litert", name = "litert-support", version.ref = "litert" }
|
litert-support = { group = "com.google.ai.edge.litert", name = "litert-support", version.ref = "litert" }
|
||||||
litert-metadata = { group = "com.google.ai.edge.litert", name = "litert-metadata", version.ref = "litert" }
|
litert-metadata = { group = "com.google.ai.edge.litert", name = "litert-metadata", version.ref = "litert" }
|
||||||
@@ -56,4 +61,5 @@ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
|||||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
license = { id = "com.github.hierynomus.license", version.ref = "license" }
|
license = { id = "com.github.hierynomus.license", version.ref = "license" }
|
||||||
aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutLibraries" }
|
aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutLibraries" }
|
||||||
|
protobuf = { id = "com.google.protobuf", version.ref = "protobuf" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user