scan document + creation de note
This commit is contained in:
@@ -18,14 +18,15 @@ import com.vaultdrop.mobile.data.local.entity.UserPreferenceEntity
|
|||||||
* DB SQLite locale, `dot.db` (même nom que la version Expo).
|
* DB SQLite locale, `dot.db` (même nom que la version Expo).
|
||||||
* v1: folders ; v2: user_preferences ; v3: files ; v4: category sur files ;
|
* v1: folders ; v2: user_preferences ; v3: files ; v4: category sur files ;
|
||||||
* v5: created_in_app sur folders ; v6: processed sur files (mode review) ;
|
* v5: created_in_app sur folders ; v6: processed sur files (mode review) ;
|
||||||
* v7: pending_operations (outbox) ; v8: scan_sessions + scan_pages (scanner).
|
* v7: pending_operations (outbox) ; v8: scan_sessions + scan_pages (scanner) ;
|
||||||
|
* v9: content sur files (corps des notes créées dans l'app).
|
||||||
*/
|
*/
|
||||||
@Database(
|
@Database(
|
||||||
entities = [
|
entities = [
|
||||||
FolderEntity::class, UserPreferenceEntity::class, FileEntity::class,
|
FolderEntity::class, UserPreferenceEntity::class, FileEntity::class,
|
||||||
PendingOperationEntity::class, ScanSessionEntity::class, ScanPageEntity::class,
|
PendingOperationEntity::class, ScanSessionEntity::class, ScanPageEntity::class,
|
||||||
],
|
],
|
||||||
version = 8,
|
version = 9,
|
||||||
exportSchema = false,
|
exportSchema = false,
|
||||||
)
|
)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ data class FileEntity(
|
|||||||
val category: String? = null,
|
val category: String? = null,
|
||||||
@ColumnInfo(name = "sync_status")
|
@ColumnInfo(name = "sync_status")
|
||||||
val syncStatus: String = "local",
|
val syncStatus: String = "local",
|
||||||
|
@ColumnInfo(name = "content")
|
||||||
|
val content: String? = null,
|
||||||
@ColumnInfo(name = "processed")
|
@ColumnInfo(name = "processed")
|
||||||
val processed: Boolean = false,
|
val processed: Boolean = false,
|
||||||
@ColumnInfo(name = "added_at")
|
@ColumnInfo(name = "added_at")
|
||||||
|
|||||||
+9
-1
@@ -22,6 +22,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
|
|||||||
* docs/api-v1.md §6.1).
|
* docs/api-v1.md §6.1).
|
||||||
* v8 : tables `scan_sessions` + `scan_pages` (scanner appareil photo) — session
|
* v8 : tables `scan_sessions` + `scan_pages` (scanner appareil photo) — session
|
||||||
* multi-pages persistée pour survivre au process death avant export SAF.
|
* multi-pages persistée pour survivre au process death avant export SAF.
|
||||||
|
* v9 : ajout colonne `content` sur `files` — corps d'une note créée dans
|
||||||
|
* l'app (fichier cloud-only, uri = NULL). Null pour les fichiers importés.
|
||||||
*/
|
*/
|
||||||
object Migrations {
|
object Migrations {
|
||||||
|
|
||||||
@@ -158,7 +160,13 @@ object Migrations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val MIGRATION_8_9 = object : Migration(8, 9) {
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
db.execSQL("ALTER TABLE `files` ADD COLUMN `content` TEXT")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val ALL: Array<Migration> = arrayOf(
|
val ALL: Array<Migration> = arrayOf(
|
||||||
MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8,
|
MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
+50
@@ -117,6 +117,56 @@ class FileRepository @Inject constructor(
|
|||||||
suspend fun getFile(resourceId: String): FileEntity? =
|
suspend fun getFile(resourceId: String): FileEntity? =
|
||||||
fileDao.getByResourceId(resourceId)
|
fileDao.getByResourceId(resourceId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée une note fichier directement dans l'app : ligne `files` cloud-only
|
||||||
|
* (uri NULL — aucun copie physique, le corps est stocké dans `content`)
|
||||||
|
* avec `processed = 1` (jamais dans la file de review). Le `create_resource`
|
||||||
|
* est poussé dans la même transaction (pattern transactional outbox).
|
||||||
|
*/
|
||||||
|
suspend fun createNote(
|
||||||
|
title: String,
|
||||||
|
body: String,
|
||||||
|
folderResourceId: String,
|
||||||
|
ownerId: String? = null,
|
||||||
|
): FileEntity {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val trimmedTitle = title.trim()
|
||||||
|
val noteName = if (trimmedTitle.endsWith(".txt", ignoreCase = true)) {
|
||||||
|
trimmedTitle
|
||||||
|
} else {
|
||||||
|
"$trimmedTitle.txt"
|
||||||
|
}
|
||||||
|
val entity = FileEntity(
|
||||||
|
resourceId = generateId.newResourceId(),
|
||||||
|
uri = null,
|
||||||
|
name = noteName,
|
||||||
|
folderResourceId = folderResourceId,
|
||||||
|
extension = "txt",
|
||||||
|
size = body.length.toLong(),
|
||||||
|
mimeType = "text/plain",
|
||||||
|
exists = 1,
|
||||||
|
ownerId = ownerId,
|
||||||
|
category = computeCategory("text/plain", "txt").dbValue,
|
||||||
|
syncStatus = FileStatus.CLOUD,
|
||||||
|
content = body,
|
||||||
|
processed = true,
|
||||||
|
addedAt = now,
|
||||||
|
updatedAt = now,
|
||||||
|
)
|
||||||
|
appDatabase.withTransaction {
|
||||||
|
fileDao.upsert(entity)
|
||||||
|
outboxRepository.enqueueCreateResource(
|
||||||
|
resourceId = entity.resourceId,
|
||||||
|
resourceType = "file",
|
||||||
|
name = entity.name,
|
||||||
|
parentResourceId = entity.folderResourceId,
|
||||||
|
mimeType = entity.mimeType,
|
||||||
|
extension = entity.extension,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getAll(): List<FileEntity> = fileDao.getAll()
|
suspend fun getAll(): List<FileEntity> = fileDao.getAll()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+38
@@ -1,9 +1,18 @@
|
|||||||
package com.vaultdrop.mobile.ui.document.content
|
package com.vaultdrop.mobile.ui.document.content
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vaultdrop.mobile.data.local.entity.FileEntity
|
import com.vaultdrop.mobile.data.local.entity.FileEntity
|
||||||
import com.vaultdrop.mobile.domain.FileCategory
|
import com.vaultdrop.mobile.domain.FileCategory
|
||||||
import com.vaultdrop.mobile.ui.components.categoryValue
|
import com.vaultdrop.mobile.ui.components.categoryValue
|
||||||
@@ -11,6 +20,8 @@ import com.vaultdrop.mobile.ui.components.categoryValue
|
|||||||
/**
|
/**
|
||||||
* Lecteur central d'un document : dispatch par état puis par catégorie.
|
* Lecteur central d'un document : dispatch par état puis par catégorie.
|
||||||
*
|
*
|
||||||
|
* - `content` non null → note créée dans l'app (fichier cloud-only, corps
|
||||||
|
* stocké localement) → lecture texte directe
|
||||||
* - `uri == null` → fichier cloud-only, aucun contenu local
|
* - `uri == null` → fichier cloud-only, aucun contenu local
|
||||||
* - PDF → rendu `PdfRenderer` intégré
|
* - PDF → rendu `PdfRenderer` intégré
|
||||||
* - IMAGE → rendu Coil intégré
|
* - IMAGE → rendu Coil intégré
|
||||||
@@ -25,6 +36,11 @@ fun DocumentContentViewer(
|
|||||||
) {
|
) {
|
||||||
val contentResolver: ContentResolver = LocalContext.current.contentResolver
|
val contentResolver: ContentResolver = LocalContext.current.contentResolver
|
||||||
|
|
||||||
|
file.content?.let { note ->
|
||||||
|
NoteDocumentViewer(text = note, modifier = modifier)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (file.uri == null) {
|
if (file.uri == null) {
|
||||||
CloudOnlyPlaceholder(file, modifier)
|
CloudOnlyPlaceholder(file, modifier)
|
||||||
return
|
return
|
||||||
@@ -55,3 +71,25 @@ fun DocumentContentViewer(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Lecture d'une note créée dans l'app — corps stocké en base, pas de flux SAF. */
|
||||||
|
@Composable
|
||||||
|
private fun NoteDocumentViewer(
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(modifier.fillMaxSize()) {
|
||||||
|
SelectionContainer(Modifier.weight(1f)) {
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+103
@@ -30,10 +30,13 @@ import androidx.compose.material.icons.filled.ArrowUpward
|
|||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.CreateNewFolder
|
import androidx.compose.material.icons.filled.CreateNewFolder
|
||||||
import androidx.compose.material.icons.filled.Description
|
import androidx.compose.material.icons.filled.Description
|
||||||
|
import androidx.compose.material.icons.filled.DocumentScanner
|
||||||
|
import androidx.compose.material.icons.filled.EditNote
|
||||||
import androidx.compose.material.icons.filled.Folder
|
import androidx.compose.material.icons.filled.Folder
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.material.icons.filled.Share
|
import androidx.compose.material.icons.filled.Share
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
@@ -45,11 +48,14 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SmallFloatingActionButton
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -118,6 +124,7 @@ fun FolderListScreen(
|
|||||||
onTabSelected: (NavTab) -> Unit,
|
onTabSelected: (NavTab) -> Unit,
|
||||||
onOpenDocument: (String) -> Unit,
|
onOpenDocument: (String) -> Unit,
|
||||||
onBuildPdf: (List<String>) -> Unit,
|
onBuildPdf: (List<String>) -> Unit,
|
||||||
|
onOpenScan: () -> Unit,
|
||||||
syncViewModel: SyncViewModel,
|
syncViewModel: SyncViewModel,
|
||||||
connectionStatusViewModel: ConnectionStatusViewModel,
|
connectionStatusViewModel: ConnectionStatusViewModel,
|
||||||
viewModel: FolderListViewModel = hiltViewModel(),
|
viewModel: FolderListViewModel = hiltViewModel(),
|
||||||
@@ -140,6 +147,8 @@ fun FolderListScreen(
|
|||||||
var shareTarget by remember { mutableStateOf<ShareTarget?>(null) }
|
var shareTarget by remember { mutableStateOf<ShareTarget?>(null) }
|
||||||
var showShareMenu by remember { mutableStateOf(false) }
|
var showShareMenu by remember { mutableStateOf(false) }
|
||||||
var shareEnabled by remember { mutableStateOf(false) }
|
var shareEnabled by remember { mutableStateOf(false) }
|
||||||
|
var showCreateMenu by remember { mutableStateOf(false) }
|
||||||
|
var showNoteDialog by remember { mutableStateOf(false) }
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
// Le partage multi-sélection ne vaut que pour un dossier unique sélectionné,
|
// Le partage multi-sélection ne vaut que pour un dossier unique sélectionné,
|
||||||
@@ -396,6 +405,43 @@ fun FolderListScreen(
|
|||||||
else -> FloatingNavBar(selected = selectedTab, onSelect = onTabSelected)
|
else -> FloatingNavBar(selected = selectedTab, onSelect = onTabSelected)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
floatingActionButton = {
|
||||||
|
if (!selection.active && !uiState.moveMode) {
|
||||||
|
Box {
|
||||||
|
SmallFloatingActionButton(
|
||||||
|
onClick = { showCreateMenu = true },
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Add,
|
||||||
|
contentDescription = stringResource(R.string.create_option_title),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showCreateMenu,
|
||||||
|
onDismissRequest = { showCreateMenu = false },
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.create_option_scan)) },
|
||||||
|
leadingIcon = { Icon(Icons.Filled.DocumentScanner, contentDescription = null) },
|
||||||
|
onClick = {
|
||||||
|
showCreateMenu = false
|
||||||
|
onOpenScan()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.create_option_note)) },
|
||||||
|
leadingIcon = { Icon(Icons.Filled.EditNote, contentDescription = null) },
|
||||||
|
onClick = {
|
||||||
|
showCreateMenu = false
|
||||||
|
showNoteDialog = true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
) { padding ->
|
) { padding ->
|
||||||
Column(
|
Column(
|
||||||
@@ -440,6 +486,16 @@ fun FolderListScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showNoteDialog) {
|
||||||
|
NoteDialog(
|
||||||
|
onDismiss = { showNoteDialog = false },
|
||||||
|
onConfirm = { title, body ->
|
||||||
|
showNoteDialog = false
|
||||||
|
viewModel.createNote(title, body)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val deleteReview = pendingDeleteReview
|
val deleteReview = pendingDeleteReview
|
||||||
if (showDeleteWarning && deleteReview != null) {
|
if (showDeleteWarning && deleteReview != null) {
|
||||||
DeleteWarningDialog(
|
DeleteWarningDialog(
|
||||||
@@ -1109,3 +1165,50 @@ private fun formatSize(bytes: Long): String {
|
|||||||
val mb = bytes / (1_024f * 1_024f)
|
val mb = bytes / (1_024f * 1_024f)
|
||||||
return String.format(Locale.getDefault(), "%.1f %s", mb, stringResource(R.string.unit_megabytes))
|
return String.format(Locale.getDefault(), "%.1f %s", mb, stringResource(R.string.unit_megabytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Création d'une note : titre requis, corps de texte libre. */
|
||||||
|
@Composable
|
||||||
|
private fun NoteDialog(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onConfirm: (title: String, body: String) -> Unit,
|
||||||
|
) {
|
||||||
|
var title by remember { mutableStateOf("") }
|
||||||
|
var body by remember { mutableStateOf("") }
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text(stringResource(R.string.note_dialog_title)) },
|
||||||
|
text = {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = title,
|
||||||
|
onValueChange = { title = it },
|
||||||
|
label = { Text(stringResource(R.string.note_title_label)) },
|
||||||
|
placeholder = { Text(stringResource(R.string.note_title_hint)) },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = body,
|
||||||
|
onValueChange = { body = it },
|
||||||
|
label = { Text(stringResource(R.string.note_body_label)) },
|
||||||
|
minLines = 4,
|
||||||
|
maxLines = 8,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = { onConfirm(title, body) },
|
||||||
|
enabled = title.isNotBlank(),
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.note_create))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(R.string.note_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
+29
@@ -252,6 +252,35 @@ class FolderListViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée une note fichier cloud-only dans le dossier courant de l'explorateur
|
||||||
|
* (racine par défaut si on est en haut). Le contenu reste local en V1.
|
||||||
|
*/
|
||||||
|
fun createNote(title: String, body: String) {
|
||||||
|
val trimmed = title.trim()
|
||||||
|
if (trimmed.isBlank()) {
|
||||||
|
_uiState.update { it.copy(createError = context.getString(R.string.note_empty_title)) }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
val targetId = _browseFolderId.value ?: _defaultRootId.value
|
||||||
|
if (targetId == null) {
|
||||||
|
_uiState.update { it.copy(createError = context.getString(R.string.new_folder_error)) }
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
runCatching {
|
||||||
|
fileRepository.createNote(
|
||||||
|
title = trimmed,
|
||||||
|
body = body,
|
||||||
|
folderResourceId = targetId,
|
||||||
|
ownerId = deviceIdentity.getOrCreate(),
|
||||||
|
)
|
||||||
|
}.onFailure {
|
||||||
|
_uiState.update { it.copy(createError = context.getString(R.string.new_folder_error)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Consomme une erreur transitoire de création (Snackbar). */
|
/** Consomme une erreur transitoire de création (Snackbar). */
|
||||||
fun clearCreateError() {
|
fun clearCreateError() {
|
||||||
_uiState.update { it.copy(createError = null) }
|
_uiState.update { it.copy(createError = null) }
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ fun NavGraph(
|
|||||||
onTabSelected = onTabSelected,
|
onTabSelected = onTabSelected,
|
||||||
onOpenDocument = { id -> navController.navigate(Routes.document(id)) },
|
onOpenDocument = { id -> navController.navigate(Routes.document(id)) },
|
||||||
onBuildPdf = { ids -> navController.navigate(Routes.pdfBuilder(ids)) },
|
onBuildPdf = { ids -> navController.navigate(Routes.pdfBuilder(ids)) },
|
||||||
|
onOpenScan = { navController.navigate(Routes.SCAN) },
|
||||||
syncViewModel = syncViewModel,
|
syncViewModel = syncViewModel,
|
||||||
connectionStatusViewModel = connectionStatusViewModel,
|
connectionStatusViewModel = connectionStatusViewModel,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,6 +21,16 @@
|
|||||||
<string name="new_folder_name_required">The folder name cannot be empty.</string>
|
<string name="new_folder_name_required">The folder name cannot be empty.</string>
|
||||||
<string name="new_folder_cloud_only">This folder is cloud-only — import a physical folder first to create subfolders inside it.</string>
|
<string name="new_folder_cloud_only">This folder is cloud-only — import a physical folder first to create subfolders inside it.</string>
|
||||||
<string name="new_folder_error">Could not create the folder.</string>
|
<string name="new_folder_error">Could not create the folder.</string>
|
||||||
|
<string name="create_option_scan">Scan a document</string>
|
||||||
|
<string name="create_option_note">Add a note</string>
|
||||||
|
<string name="create_option_title">Add</string>
|
||||||
|
<string name="note_dialog_title">New note</string>
|
||||||
|
<string name="note_title_label">Title</string>
|
||||||
|
<string name="note_title_hint">e.g. shopping list</string>
|
||||||
|
<string name="note_body_label">Note text</string>
|
||||||
|
<string name="note_cancel">Cancel</string>
|
||||||
|
<string name="note_create">Create</string>
|
||||||
|
<string name="note_empty_title">The note title cannot be empty.</string>
|
||||||
<string name="move_files">Move</string>
|
<string name="move_files">Move</string>
|
||||||
<string name="move_files_title">Move to folder</string>
|
<string name="move_files_title">Move to folder</string>
|
||||||
<string name="move_files_count">%1$d file(s)</string>
|
<string name="move_files_count">%1$d file(s)</string>
|
||||||
|
|||||||
@@ -39,6 +39,16 @@
|
|||||||
<string name="new_folder_name_required">Le nom du dossier ne peut pas être vide.</string>
|
<string name="new_folder_name_required">Le nom du dossier ne peut pas être vide.</string>
|
||||||
<string name="new_folder_cloud_only">Ce dossier est disponible uniquement sur le cloud — importe d\'abord un dossier physique pour y créer des sous-dossiers.</string>
|
<string name="new_folder_cloud_only">Ce dossier est disponible uniquement sur le cloud — importe d\'abord un dossier physique pour y créer des sous-dossiers.</string>
|
||||||
<string name="new_folder_error">Impossible de créer le dossier.</string>
|
<string name="new_folder_error">Impossible de créer le dossier.</string>
|
||||||
|
<string name="create_option_scan">Scanner un document</string>
|
||||||
|
<string name="create_option_note">Ajouter une note</string>
|
||||||
|
<string name="create_option_title">Ajouter</string>
|
||||||
|
<string name="note_dialog_title">Nouvelle note</string>
|
||||||
|
<string name="note_title_label">Titre</string>
|
||||||
|
<string name="note_title_hint">ex. liste de courses</string>
|
||||||
|
<string name="note_body_label">Texte de la note</string>
|
||||||
|
<string name="note_cancel">Annuler</string>
|
||||||
|
<string name="note_create">Créer</string>
|
||||||
|
<string name="note_empty_title">Le titre de la note ne peut pas être vide.</string>
|
||||||
<string name="move_files">Déplacer</string>
|
<string name="move_files">Déplacer</string>
|
||||||
<string name="move_files_title">Déplacer vers un dossier</string>
|
<string name="move_files_title">Déplacer vers un dossier</string>
|
||||||
<string name="move_files_count">%1$d fichier(s)</string>
|
<string name="move_files_count">%1$d fichier(s)</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user