diff --git a/README.fr.md b/README.fr.md index d31ae097..cd86c42f 100644 --- a/README.fr.md +++ b/README.fr.md @@ -4,13 +4,13 @@ 🇬🇧 [English version](README.md) -**v3.0.0 — octobre 2026** · [Politique de confidentialité](PRIVACY.md) · [Conditions d'utilisation](TERMS.md) · [Sécurité](SECURITY.fr.md) +**v3.1.0 — octobre 2026** · [Politique de confidentialité](PRIVACY.md) · [Conditions d'utilisation](TERMS.md) · [Sécurité](SECURITY.fr.md) Application de prise de notes Markdown chiffrée pour Android, écrite en **Kotlin** avec Jetpack Compose. **100 % locale, sans permission Internet.** Interface en **anglais, français, allemand, italien et espagnol**. Coffres par dossier (phrase secrète Argon2id ou code lié au Keystore), verrouillage de l'application par code et biométrie forte, recherche plein texte FTS5, dictée -Whisper sur l'appareil, rétroliens `[[note]]`, aperçu Markdown, mode panique en plusieurs étapes. +Whisper sur l'appareil, rétroliens `[[note]]`, notes lues en fiche à leur couleur, mode panique en plusieurs étapes. Pour les penseurs, thérapeutes, étudiants, chercheurs, auteurs et journalistes qui veulent prendre des notes sensibles ou denses sans qu'elles quittent jamais leur téléphone. @@ -21,6 +21,20 @@ vérifier.** --- +## Nouveautés de la 3.1.0 + +- **Lecture** : toucher une note l'ouvre en lecture, sous forme de fiche à sa couleur — son titre, le + texte mis en forme, sa date —, avec un bouton **Éditer** ; la coche ✓ enregistre et revient à la + lecture. Une note vide s'ouvre en écriture. +- **Appui long sur une note** : éditer, mettre à la corbeille (avec Annuler) ou supprimer + définitivement. Les actions d'une note de coffre ne sont proposées qu'une fois le coffre ouvert. +- **Couleurs** : huit couleurs pastel par note, depuis le menu ⋮, visibles dans les listes. Une note de + coffre ne montre sa couleur que coffre ouvert ; comme ses étiquettes, la couleur est stockée hors du + chiffrement propre au coffre (dans la base chiffrée) — la politique de confidentialité le dit. +- **« + » dans un coffre fermé** crée la note une fois le coffre déverrouillé. +- Un seul bleu pour les boutons principaux ; pages légales datées et relues contre le code ; chaîne de + build passée à AGP 9. La base passe au schéma 10 (une colonne ajoutée, toutes les notes conservées). + ## Nouveautés de la 3.0.0 **Notes Tech est réécrite en Kotlin.** Les versions 1.x et 2.x étaient construites avec Flutter. La @@ -79,10 +93,11 @@ avec sa propre clé. Une copie installée depuis F-Droid reçoit la 3.0.0 par F- ### Édition Markdown - Création, édition, enregistrement automatique. -- **Éditer / Aperçu** : l'aperçu affiche titres, listes, emphase et liens. Un lien `[[Titre]]` ouvre la +- **Lecture et écriture** : une note s'ouvre en lecture — titres, listes, emphase et liens mis en + forme —, et **Éditer** permet de l'écrire. Un lien `[[Titre]]` ouvre la note qu'il désigne ; un lien `http`, `https` ou `mailto` s'ouvre dans l'application du système, tout autre schéma est ignoré. Les images ne sont jamais chargées : leur texte de remplacement s'affiche. -- Épingler, favoris, archives, corbeille (30 jours), ordre de tri, thème clair / sombre / système. +- Épingler, favoris, couleurs, archives, corbeille (30 jours), ordre de tri, thème clair / sombre / système. ### Coffres par dossier - **Mode phrase secrète** — Argon2id (64 Mio, t = 3) et AES-256-GCM. La clé du coffre (32 octets @@ -134,7 +149,7 @@ avec sa propre clé. Une copie installée depuis F-Droid reçoit la 3.0.0 par F- ## Installation 1. **APK publié** — depuis les [Releases GitHub](https://github.com/gitubpatrice/notes_tech/releases), - l'APK universel (`notes-tech-universel-3.0.0.apk`, tous téléphones), ou le fichier plus léger de + l'APK universel (`notes-tech-universel-3.1.0.apk`, tous téléphones), ou le fichier plus léger de votre appareil (`arm64-v8a` convient à presque tous les téléphones depuis 2016). Vérifiez l'empreinte SHA-256 publiée dans les notes de version. 2. **F-Droid** — [f-droid.org/packages/com.filestech.notes_tech](https://f-droid.org/packages/com.filestech.notes_tech/), construite et signée par F-Droid. diff --git a/README.md b/README.md index 124779bb..79359a90 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ 🇫🇷 [Version française](README.fr.md) -**v3.0.0 — October 2026** · [Privacy policy](PRIVACY.md) · [Terms of use](TERMS.md) · [Security](SECURITY.md) +**v3.1.0 — October 2026** · [Privacy policy](PRIVACY.md) · [Terms of use](TERMS.md) · [Security](SECURITY.md) Encrypted Markdown note-taking app for Android, written in **Kotlin** with Jetpack Compose. **100% local, no Internet permission.** Interface in **English, French, German, Italian and Spanish**. Per-folder vaults (Argon2id passphrase or Keystore-bound PIN), an app lock with PIN and strong biometrics, FTS5 full-text search, on-device Whisper voice dictation, `[[note]]` backlinks, -Markdown preview, multi-step panic mode. +notes read as cards in their colour, multi-step panic mode. For thinkers, therapists, students, researchers, writers and journalists who want to take sensitive or dense notes without them ever leaving their phone. @@ -20,6 +20,20 @@ technically unable to send anything, and you can check that in its manifest.** --- +## What's new in 3.1.0 + +- **Reading**: tapping a note opens it to be read, drawn as a card in its colour — its title, the + rendered text, its date — with an **Edit** button; the ✓ tick saves and goes back to reading. A blank + note opens to be written. +- **Long press on a note**: edit, move to trash (with Undo) or delete permanently. A vault note's + actions are offered only once its vault is open. +- **Colours**: eight pastel colours per note, from the ⋮ menu, shown in the lists. A vault note shows its + colour only while its vault is open; like its tags, the colour is stored outside the vault's own + encryption (in the encrypted database) — the privacy policy says so. +- **"+" in a closed vault** creates the note once the vault is unlocked. +- One blue for the main buttons; legal pages dated and checked against the code; build toolchain moved + to AGP 9. The database moves to schema 10 (one column added, every note kept). + ## What's new in 3.0.0 **Notes Tech is rewritten in Kotlin.** Versions 1.x and 2.x were built with Flutter. The 3.0.0 keeps @@ -74,10 +88,11 @@ A copy installed from F-Droid receives the 3.0.0 through F-Droid. ### Markdown editing - Create, edit, autosave. -- **Edit / Preview**: the preview renders headings, lists, emphasis and links. A `[[Title]]` link +- **Reading and writing**: a note opens to be read — headings, lists, emphasis and links rendered —, + and **Edit** writes it. A `[[Title]]` link opens the note it points to; an `http`, `https` or `mailto` link opens in the system app, any other scheme is ignored. Images are never loaded: their alternative text stands in. -- Pin, favourites, archive, trash (30-day retention), sort order, light / dark / system theme. +- Pin, favourites, colours, archive, trash (30-day retention), sort order, light / dark / system theme. ### Per-folder vaults - **Passphrase mode** — Argon2id (64 MiB, t = 3) and AES-256-GCM. The vault key (32 random bytes) is @@ -128,7 +143,7 @@ A copy installed from F-Droid receives the 3.0.0 through F-Droid. ## Installation 1. **Published APK** — from [GitHub Releases](https://github.com/gitubpatrice/notes_tech/releases), - the universal APK (`notes-tech-universel-3.0.0.apk`, any phone), or the lighter one for your device + the universal APK (`notes-tech-universel-3.1.0.apk`, any phone), or the lighter one for your device (`arm64-v8a` fits almost every phone since 2016). Check the SHA-256 published in the release notes. 2. **F-Droid** — [f-droid.org/packages/com.filestech.notes_tech](https://f-droid.org/packages/com.filestech.notes_tech/), built and signed by F-Droid. diff --git a/SECURITY.fr.md b/SECURITY.fr.md index 257ff4ae..58d8d627 100644 --- a/SECURITY.fr.md +++ b/SECURITY.fr.md @@ -2,10 +2,30 @@ *English version: [SECURITY.md](SECURITY.md)* -**Version actuelle : v3.0.0 — octobre 2026.** Notes Tech 3.0 est une réécriture en Kotlin. Le journal +**Version actuelle : v3.1.0 — octobre 2026.** Notes Tech 3.0 est une réécriture en Kotlin. Le journal de sécurité des versions Flutter 1.x et 2.x est conservé tel quel, dans [`SECURITY.fr.md` au tag v2.0.9](https://github.com/gitubpatrice/notes_tech/blob/v2.0.9/SECURITY.fr.md). +## v3.1.0 — appui long, mode lecture, couleur des notes (2026-10-10) + +- **Mettre à la corbeille ou effacer une note de coffre ne demande aucune clé** : la ligne part, blob + scellé compris. L'appui long sur une note, et la nouvelle « Supprimer définitivement » de l'éditeur, + vérifient donc le coffre **au moment d'agir, sur une relecture de la note** : coffre fermé, le geste + demande le secret et s'exécute une fois le coffre ouvert. Prouvé par sabotage : sans ce contrôle, les + tests de refus échouent. +- **La couleur d'une note est stockée hors de l'enveloppe du coffre**, comme ses étiquettes (dans la base + SQLCipher). Les listes ne l'affichent sur une note de coffre que coffre ouvert, et la masquent à + nouveau au verrouillage automatique. La politique de confidentialité le dit désormais, pour les + étiquettes aussi. +- **Le mode lecture** garde les protections d'une note de coffre (FLAG_SECURE, session, verrouillage + automatique), n'ouvre aucun clavier, et son rendu n'est pas sélectionnable — aucun chemin de copie qui + contournerait le presse-papiers protégé. +- **Schéma 10** : une colonne facultative (`color_id`), ajoutée par une migration rejouée en test sur une + base Flutter et sur une base adoptée par la 3.0.0 ; aucune note ni aucun blob scellé ne change. +- Relu modification par modification par deux modèles externes et un audit pré-publication sur trois + axes (aucun critique, aucun élevé ; un moyen corrigé : un geste en attente d'un coffre disparaît avec + sa feuille de déverrouillage). + ## v3.0.0 — réécriture en Kotlin et audit de sécurité complet (2026-09-26) Le portage a été audité avant publication selon une méthode de chercheurs et de panel adverse : cinq diff --git a/SECURITY.md b/SECURITY.md index f3d1ebf6..a9aeffc3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,10 +2,26 @@ *Version française : [SECURITY.fr.md](SECURITY.fr.md)* -**Current version: v3.0.0 — October 2026.** Notes Tech 3.0 is a rewrite in Kotlin. The security log +**Current version: v3.1.0 — October 2026.** Notes Tech 3.0 is a rewrite in Kotlin. The security log of the Flutter versions 1.x and 2.x is kept as it was, in [`SECURITY.md` at tag v2.0.9](https://github.com/gitubpatrice/notes_tech/blob/v2.0.9/SECURITY.md). +## v3.1.0 — long press, reading mode, note colours (2026-10-10) + +- **Trashing or erasing a vault note needs no key** — the row goes, sealed blob and all. So the long + press on a note, and the editor's new "Delete permanently", check the vault **at execution, on a fresh + read of the note**: with its vault closed, the gesture asks for the secret and runs once it is open. + Proved by sabotage: without the check, the refusal tests fail. +- **A note's colour is stored outside the vault's envelope**, like its tags (in the SQLCipher database). + The lists show it on a vault note only while its vault is open, and hide it again at the automatic + lock. The privacy policy now says it, for the tags too. +- **Reading mode** keeps a vault note's protections (FLAG_SECURE, session, automatic lock), opens no + keyboard, and its rendering is not selectable — no copy path around the protected clipboard. +- **Schema 10**: one nullable column (`color_id`), added by a migration replayed in tests on a Flutter + base and on a base adopted by 3.0.0; no note or sealed blob changes. +- Reviewed per change by two external models and a three-axis pre-release audit (no Critical, no High; + one Medium fixed: a gesture waiting for a vault now dies with its unlock sheet). + ## v3.0.0 — Kotlin rewrite and full security audit (2026-09-26) The port was audited before release by a researcher-and-adversarial-panel method: five researchers, diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index c42bc51d..c968c059 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -1,11 +1,10 @@ # Third-party notices — Notes Tech -Notes Tech 3.0 (`com.filestech.notes_tech`) is written in Kotlin with Jetpack Compose and is licensed +Notes Tech 3.1 (`com.filestech.notes_tech`) is written in Kotlin with Jetpack Compose and is licensed under the Apache License 2.0 ([LICENSE](LICENSE)). Everything it ships that it did not write is listed -here. The list was read on 2026-10-05 from the resolved `releaseRuntimeClasspath` of the `:app` module -after the dependency updates merged that evening (the 3.0.0 APK itself ships Room 2.8.4, Kotlin -2.3.20 and Bouncy Castle 1.81: see this file at tag `v3.0.0`), and each licence from the POM that the -library publishes — not from memory. +here. The list was read on 2026-10-10 from the resolved `releaseRuntimeClasspath` of the `:app` module +for 3.1.0, after the move to AGP 9 (earlier versions: see this file at their tag, e.g. `v3.0.0`), and +each licence from the POM that the library publishes — not from memory. **No Google Play Services, no ML Kit, no Firebase, no analytics SDK, no crash reporter, no network library.** The app does not hold the `INTERNET` permission. @@ -15,12 +14,12 @@ library.** The app does not hold the `INTERNET` permission. | Library | Version | Publisher | Licence | |---|---|---|---| | AndroidX: Core, Activity, AppCompat, Lifecycle, Navigation, SavedState, Fragment, Emoji2, Window, Startup, Tracing, Profile Installer, DocumentFile, Biometric, Transition and their support modules | various | Google / AOSP | Apache 2.0 | -| Jetpack Compose: UI, Foundation, Animation, Runtime, Material 3, Material Icons | BOM 2026.06.00 (UI 1.11.3, Material 3 1.4.0) | Google / AOSP | Apache 2.0 | +| Jetpack Compose: UI, Foundation, Animation, Runtime, Material 3, Material Icons | BOM 2026.09.00 (UI 1.12.1, Material 3 1.4.0) | Google / AOSP | Apache 2.0 | | Room (`androidx.room`) and `androidx.sqlite` | 2.8.5 / 2.6.2 | Google / AOSP | Apache 2.0 | | DataStore Preferences | 1.1.1 | Google / AOSP | Apache 2.0 | -| Hilt and Dagger (`com.google.dagger`, `androidx.hilt`) | 2.57.2 / 1.3.0 | Google | Apache 2.0 | +| Hilt and Dagger (`com.google.dagger`, `androidx.hilt`) | 2.60.1 / 1.4.0 | Google | Apache 2.0 | | Guava `listenablefuture`, JSR-305 annotations | 1.0 / 3.0.2 | Google | Apache 2.0 | -| Kotlin standard library | 2.3.21 | JetBrains | Apache 2.0 | +| Kotlin standard library | 2.4.10 | JetBrains | Apache 2.0 | | kotlinx.coroutines | 1.11.0 | JetBrains | Apache 2.0 | | `org.jetbrains:markdown` — the Markdown parser behind the preview | 0.7.14 | JetBrains | Apache 2.0 | | JetBrains annotations, JSpecify | 23.0.0 / 1.0.0 | JetBrains / JSpecify | Apache 2.0 | diff --git a/app/src/androidTest/java/com/filestech/notes_tech/ui/editor/EditeurTest.kt b/app/src/androidTest/java/com/filestech/notes_tech/ui/editor/EditeurTest.kt index 9fdd0fbc..df54578f 100644 --- a/app/src/androidTest/java/com/filestech/notes_tech/ui/editor/EditeurTest.kt +++ b/app/src/androidTest/java/com/filestech/notes_tech/ui/editor/EditeurTest.kt @@ -107,6 +107,7 @@ class EditeurTest { private val exports = mutableListOf() private val copies = mutableListOf() private val corbeilles = mutableListOf() + private val suppressions = mutableListOf() private val notesOuvertes = mutableListOf() private val fantomes = mutableListOf() private val basculements = mutableListOf() @@ -167,6 +168,7 @@ class EditeurTest { onExporter = { exports += Unit }, onCopier = { copies += Unit }, onCorbeille = { corbeilles += Unit }, + onSupprimerDefinitivement = { suppressions += Unit }, onOuvrirNote = { notesOuvertes += it }, onLienFantome = { fantomes += it }, lecture = lectureCourante.value, @@ -556,6 +558,7 @@ class EditeurTest { R.string.note_editor_menu_export, R.string.note_editor_menu_copy_markdown, R.string.note_editor_menu_trash, + R.string.trash_delete_forever, ).forEach { entree -> regle.onNodeWithContentDescription(texte(R.string.note_editor_tooltip_more)).performClick() regle.onNodeWithText(texte(entree)).performClick() @@ -566,6 +569,7 @@ class EditeurTest { assertThat(exports).hasSize(1) assertThat(copies).hasSize(1) assertThat(corbeilles).hasSize(1) + assertThat(suppressions).hasSize(1) } // ------------------------------------------------------------------ le panneau de liens diff --git a/app/src/main/java/com/filestech/notes_tech/ui/NotesTechNavHost.kt b/app/src/main/java/com/filestech/notes_tech/ui/NotesTechNavHost.kt index dd01a84d..177cb075 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/NotesTechNavHost.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/NotesTechNavHost.kt @@ -29,6 +29,7 @@ fun NotesTechNavHost(navController: NavHostController) { composable(Destination.Home.route) { HomeRoute( onOpenNote = { navController.navigate(Destination.Editor(it.id).route) }, + onEditNote = { navController.navigate(Destination.Editor(it.id, ecrire = true).route) }, onOpenSearch = { navController.navigate(Destination.Search.route) }, onOpenTrash = { navController.navigate(Destination.Trash.route) }, onOpenSettings = { navController.navigate(Destination.Settings.route) }, @@ -38,7 +39,13 @@ fun NotesTechNavHost(navController: NavHostController) { composable( route = Destination.EDITOR_PATTERN, - arguments = listOf(navArgument(Destination.ARG_NOTE_ID) { type = NavType.StringType }), + arguments = listOf( + navArgument(Destination.ARG_NOTE_ID) { type = NavType.StringType }, + navArgument(Destination.ARG_ECRIRE) { + type = NavType.BoolType + defaultValue = false + }, + ), ) { // L'identifiant n'est pas relu ici : `SavedStateHandle` le remet au ViewModel, qui est // le seul à en avoir besoin. Le faire transiter par le composable ajouterait un second diff --git a/app/src/main/java/com/filestech/notes_tech/ui/about/AboutScreen.kt b/app/src/main/java/com/filestech/notes_tech/ui/about/AboutScreen.kt index 4e4f0c69..d8cf70b1 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/about/AboutScreen.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/about/AboutScreen.kt @@ -71,6 +71,7 @@ import com.filestech.notes_tech.ui.common.HoteDeMessages import com.filestech.notes_tech.ui.common.TitreDeSection import com.filestech.notes_tech.ui.common.ouvrirUnLien import com.filestech.notes_tech.ui.theme.Formes +import com.filestech.notes_tech.ui.theme.couleursDeBoutonPrincipal import kotlinx.coroutines.launch /** @@ -217,6 +218,7 @@ private fun EnTete(onVerifierLesMisesAJour: () -> Unit) { FilledTonalButton( onClick = onVerifierLesMisesAJour, shape = Formes.bouton, + colors = couleursDeBoutonPrincipal(), modifier = Modifier.padding(top = 16.dp), ) { Icon(Icons.Outlined.SystemUpdateAlt, contentDescription = null, modifier = Modifier.size(18.dp)) diff --git a/app/src/main/java/com/filestech/notes_tech/ui/applock/AppLockScreen.kt b/app/src/main/java/com/filestech/notes_tech/ui/applock/AppLockScreen.kt index 222fb58a..822ec5b1 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/applock/AppLockScreen.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/applock/AppLockScreen.kt @@ -64,6 +64,7 @@ import com.filestech.notes_tech.ui.panic.PanicConfirmDialog import com.filestech.notes_tech.ui.panic.activityPanicViewModel import com.filestech.notes_tech.ui.secure.SecureWindowGuard import com.filestech.notes_tech.ui.theme.Formes +import com.filestech.notes_tech.ui.theme.couleursDeBoutonPrincipal import kotlinx.coroutines.launch /** @@ -229,6 +230,7 @@ fun AppLockScreen( tailleDeTouche = KEY_SIZE, ) Button( + colors = couleursDeBoutonPrincipal(), onClick = { onSubmit(typed) }, enabled = canType && AppLockParams.isValidPin(typed), shape = Formes.bouton, diff --git a/app/src/main/java/com/filestech/notes_tech/ui/applock/AppLockSettings.kt b/app/src/main/java/com/filestech/notes_tech/ui/applock/AppLockSettings.kt index e2e03d18..f29655e4 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/applock/AppLockSettings.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/applock/AppLockSettings.kt @@ -68,6 +68,7 @@ import com.filestech.notes_tech.ui.secure.ProprietesDeFeuilleSecrete import com.filestech.notes_tech.ui.secure.SecureWindowGuard import com.filestech.notes_tech.ui.settings.DialogueDeChoix import com.filestech.notes_tech.ui.theme.Formes +import com.filestech.notes_tech.ui.theme.couleursDeBoutonPrincipal import kotlinx.coroutines.launch /** @@ -311,6 +312,7 @@ internal fun AppLockPinSheet(sheet: PinSheetState, onPin: (String) -> Unit, onDi onDelete = { typed = typed.dropLast(1) }, ) Button( + colors = couleursDeBoutonPrincipal(), onClick = { onPin(typed) }, enabled = canType && typed.isNotEmpty(), shape = Formes.bouton, diff --git a/app/src/main/java/com/filestech/notes_tech/ui/common/EntreeDeMenu.kt b/app/src/main/java/com/filestech/notes_tech/ui/common/EntreeDeMenu.kt index 6cd236f1..e26d3c80 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/common/EntreeDeMenu.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/common/EntreeDeMenu.kt @@ -5,6 +5,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -16,6 +17,10 @@ import androidx.compose.ui.graphics.vector.ImageVector * * Moved out of `FolderDialogs.kt` when the notes got their own action sheet (long press, 3.1.0): * two sheets drawing their entries each in its own way would drift apart. + * + * [destructive]: an entry that removes something — its icon AND its title in the theme's red, as the + * ⋮ menu's "Move to trash" (Patrice, 2026-10-10). [tint] alone colours the icon only: the vault entries + * carry a red padlock without removing anything. */ @Composable internal fun EntreeDeMenu( @@ -25,15 +30,17 @@ internal fun EntreeDeMenu( modifier: Modifier = Modifier, subtitle: String? = null, tint: Color? = null, + destructive: Boolean = false, ) { + val rouge = MaterialTheme.colorScheme.error ListItem( - headlineContent = { Text(title) }, + headlineContent = { Text(title, color = if (destructive) rouge else Color.Unspecified) }, supportingContent = subtitle?.let { { Text(it) } }, leadingContent = { Icon( imageVector = icon, contentDescription = null, - tint = tint ?: LocalContentColor.current, + tint = if (destructive) rouge else tint ?: LocalContentColor.current, ) }, colors = ListItemDefaults.colors(containerColor = Color.Transparent), diff --git a/app/src/main/java/com/filestech/notes_tech/ui/editor/NoteEditorScreen.kt b/app/src/main/java/com/filestech/notes_tech/ui/editor/NoteEditorScreen.kt index accd25d1..cbbff170 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/editor/NoteEditorScreen.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/editor/NoteEditorScreen.kt @@ -5,6 +5,7 @@ import android.content.res.Resources import android.os.SystemClock import androidx.activity.compose.BackHandler import androidx.annotation.StringRes +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column @@ -22,8 +23,10 @@ import androidx.compose.foundation.layout.isImeVisible import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -34,6 +37,7 @@ import androidx.compose.material.icons.filled.PushPin import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.outlined.CloudDone import androidx.compose.material.icons.outlined.ContentCopy +import androidx.compose.material.icons.outlined.DeleteForever import androidx.compose.material.icons.outlined.DeleteOutline import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.ErrorOutline @@ -50,6 +54,7 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -101,14 +106,17 @@ import com.filestech.notes_tech.R import com.filestech.notes_tech.domain.markdown.LinkTarget import com.filestech.notes_tech.ui.common.ActionDeDialogue import com.filestech.notes_tech.ui.common.CorpsDeDialogue +import com.filestech.notes_tech.ui.common.DialogueDestructif import com.filestech.notes_tech.ui.common.EmptyState import com.filestech.notes_tech.ui.common.HoteDeMessages import com.filestech.notes_tech.ui.common.MIME_MARKDOWN import com.filestech.notes_tech.ui.common.displayName import com.filestech.notes_tech.ui.common.ouvrirUnLienExterne import com.filestech.notes_tech.ui.common.partagerUnFichier +import com.filestech.notes_tech.ui.common.rememberNoteDateFormatter import com.filestech.notes_tech.ui.secure.SaisieDeCoffre import com.filestech.notes_tech.ui.secure.SecureWindowGuard +import com.filestech.notes_tech.ui.theme.CouleursDeNote import com.filestech.notes_tech.ui.theme.SemanticColors import com.filestech.notes_tech.ui.vault.UnlockVaultSheet import com.filestech.notes_tech.ui.voice.SurcoucheDeDictee @@ -145,7 +153,8 @@ fun NoteEditorRoute(onBack: () -> Unit, onOpenNote: (String) -> Unit, onInstalle // note has loaded, then decided ONCE: an existing note opens to be read, a blank one — just created, // or emptied — to be written, there being nothing to read. Saved: a rotation, or the app lock taking // the screen away and giving it back, returns to the side the user was on. - var lectureChoisie by rememberSaveable { mutableStateOf(null) } + // ⚠️ Writing at once when the long press's "Edit" opened it: the choice is already made. + var lectureChoisie by rememberSaveable { mutableStateOf(if (viewModel.ouvrirEnEcriture) false else null) } val chargee = !state.loading && state.note != null && state.loadError == null && state.lockedVault == null // Until the choice is stored, the screen already shows the side it will be: no frame of the other. val lecture = lectureChoisie ?: (chargee && state.content.text.isNotBlank()) @@ -165,6 +174,7 @@ fun NoteEditorRoute(onBack: () -> Unit, onOpenNote: (String) -> Unit, onInstalle var deplacementOuvert by rememberSaveable { mutableStateOf(false) } var couleurOuverte by rememberSaveable { mutableStateOf(false) } + var suppressionDemandee by rememberSaveable { mutableStateOf(false) } var infosOuvertes by rememberSaveable { mutableStateOf(false) } // Les deux détours du déplacement, retenus par **identifiant** pour survivre à une rotation et @@ -229,9 +239,14 @@ fun NoteEditorRoute(onBack: () -> Unit, onOpenNote: (String) -> Unit, onInstalle // Closed — not merely hidden — when the note stops being readable: a vault that locks while the sheet // is open would otherwise leave the sealed note's colour shown and changeable (GPT-5.6 review). - val couleurPossible = state.note != null && state.lockedVault == null && state.loadError == null - LaunchedEffect(couleurPossible) { if (!couleurPossible) couleurOuverte = false } - if (couleurOuverte && couleurPossible) { + val noteLisible = state.note != null && state.lockedVault == null && state.loadError == null + LaunchedEffect(noteLisible) { + if (!noteLisible) { + couleurOuverte = false + suppressionDemandee = false + } + } + if (couleurOuverte && noteLisible) { FeuilleDeCouleur( actuelle = state.note?.color, dansUnCoffre = state.isVaultNote, @@ -243,6 +258,20 @@ fun NoteEditorRoute(onBack: () -> Unit, onOpenNote: (String) -> Unit, onInstalle ) } + // The trash's own question, word for word, as from the list's long press. + if (suppressionDemandee && noteLisible) { + DialogueDestructif( + titre = stringResource(R.string.trash_delete_forever_title), + corps = stringResource(R.string.trash_delete_forever_body), + libelleConfirmation = stringResource(R.string.trash_delete_forever), + onConfirmer = { + suppressionDemandee = false + viewModel.deletePermanently() + }, + onAnnuler = { suppressionDemandee = false }, + ) + } + if (deplacementOuvert) { FeuilleDeDeplacement( dossiers = dossiers, @@ -400,6 +429,7 @@ fun NoteEditorRoute(onBack: () -> Unit, onOpenNote: (String) -> Unit, onInstalle // l'observation de `action` ci-dessus. Quitter tout de suite laissait croire à une note // supprimée qui ne l'était pas. onCorbeille = viewModel::moveToTrash, + onSupprimerDefinitivement = { suppressionDemandee = true }, onOuvrirNote = ouvrirUneAutreNote, // ⚠️ Un lien fantôme désigne une note annoncée et pas encore écrite : l'appuyer la crée, avec // le titre du lien, **puis l'ouvre** — le même chemin qu'un `[[Titre]]` touché dans l'aperçu, @@ -465,6 +495,8 @@ fun NoteEditorScreen( onExporter: () -> Unit, onCopier: () -> Unit, onCorbeille: () -> Unit, + /** The ⋮ menu's "Delete permanently" (3.1.0): asks the trash's question first. */ + onSupprimerDefinitivement: () -> Unit, onOuvrirNote: (String) -> Unit, onLienFantome: (String) -> Unit, /** @@ -501,6 +533,8 @@ fun NoteEditorScreen( // with no name, and the editor's sweep caught it (`actionnablesSansNom`). Here the // label is a plain child of the clickable surface, so it merges, as on every button. ExtendedFloatingActionButton( + containerColor = SemanticColors.primaryButton, + contentColor = SemanticColors.onPrimaryButton, onClick = { // The cursor at the END: "Edit" means "go on writing". A loaded note's value // starts at 0, which put the cursor before the first word — measured by the @@ -644,6 +678,7 @@ fun NoteEditorScreen( onExporter = onExporter, onCopier = onCopier, onCorbeille = onCorbeille, + onSupprimerDefinitivement = onSupprimerDefinitivement, ) } }, @@ -742,14 +777,7 @@ fun NoteEditorScreen( modifier = Modifier.fillMaxSize().imePadding(), ) { if (lecture) { - // 🔴 Lazy in preview — see `apercuMarkdown`: a note has no size limit. - LazyColumn( - modifier = Modifier.fillMaxSize(), - state = defilementDeLApercu, - contentPadding = PaddingValues(bottom = 24.dp), - ) { - apercuMarkdown(rendu, onLienDeLApercu) - } + FicheDeLecture(state, rendu, defilementDeLApercu, onLienDeLApercu) } else { TextField( value = state.content, @@ -818,6 +846,69 @@ private fun MiseEnPageDeLEditeur( } } +/** + * A note being read, drawn as a note (3.1.0, Patrice: "que la note ressemble à une note" — the + * "Fiche" he chose): a card on the screen, in the note's own colour — the pastel background and the + * border of its hue, 2 dp — or the plain card and a grey border without one; its title at the top, a + * thin rule, the text, and the date of its last change at the bottom. + * + * ⚠️ The text keeps the colours whose contrast `CouleursDeNoteTest` measures on every background: + * `onSurface`, `onSurfaceVariant`, and the links' `primary`. A vault note's colour shows here: reading + * it means its vault is open. + * + * 🔴 Lazy, as the preview was — see `apercuMarkdown`: a note has no size limit. + */ +@Composable +private fun FicheDeLecture( + state: EditorUiState, + rendu: LectureDeLApercu, + defilement: LazyListState, + onLien: (LinkTarget) -> Unit, +) { + val schema = MaterialTheme.colorScheme + val teinte = state.note?.color?.let { CouleursDeNote.teinte(it, schema) } + val bord = teinte?.bord ?: schema.outlineVariant + val formate = rememberNoteDateFormatter() + Surface( + shape = RoundedCornerShape(16.dp), + color = teinte?.fond ?: schema.surfaceContainerLow, + border = BorderStroke(2.dp, bord), + shadowElevation = 2.dp, + modifier = Modifier.fillMaxSize().padding(horizontal = 12.dp, vertical = 8.dp), + ) { + Column(Modifier.fillMaxSize()) { + // The title the note has; an empty one says so, in italics, as the cards do. + val sansTitre = state.titre.text.isEmpty() + Text( + text = if (sansTitre) stringResource(R.string.note_untitled) else state.titre.text, + style = MaterialTheme.typography.headlineSmall, + fontStyle = if (sansTitre) FontStyle.Italic else null, + color = if (sansTitre) schema.onSurfaceVariant else schema.onSurface, + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, top = 14.dp, bottom = 10.dp) + .semantics { heading() }, + ) + HorizontalDivider(color = bord.copy(alpha = 0.5f), modifier = Modifier.padding(horizontal = 16.dp)) + LazyColumn( + modifier = Modifier.weight(1f).fillMaxWidth(), + state = defilement, + contentPadding = PaddingValues(top = 8.dp, bottom = 8.dp), + ) { + apercuMarkdown(rendu, onLien) + } + state.note?.updatedAt?.let { modifiee -> + Text( + text = stringResource(R.string.note_info_modified) + " " + formate(modifiee), + style = MaterialTheme.typography.labelMedium, + color = schema.onSurfaceVariant, + modifier = Modifier.align(Alignment.End).padding(start = 16.dp, end = 16.dp, bottom = 12.dp), + ) + } + } + } +} + /** About three lines of text under the body field's label. */ internal val HAUTEUR_MINIMALE_DU_CORPS = 120.dp @@ -834,21 +925,8 @@ private val HAUTEUR_DU_BOUTON_MODIFIER = 80.dp private fun EnTeteDeLEditeur(state: EditorUiState, onTitreChange: (TextFieldValue) -> Unit, lecture: Boolean) { Column(Modifier.fillMaxWidth()) { if (state.saveFailed) BanniereEchecEnregistrement(state.saveFailureReason) - if (lecture) { - // The title the note has; an empty one says so, in italics, as the cards do. - val sansTitre = state.titre.text.isEmpty() - Text( - text = if (sansTitre) stringResource(R.string.note_untitled) else state.titre.text, - style = MaterialTheme.typography.headlineSmall, - fontStyle = if (sansTitre) FontStyle.Italic else null, - color = MaterialTheme.colorScheme.run { if (sansTitre) onSurfaceVariant else onSurface }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 12.dp) - .semantics { heading() }, - ) - return@Column - } + // While reading, the title is on the note's card itself — see [FicheDeLecture]. + if (lecture) return@Column // 🔴🔴 **`label` et non `placeholder` : les deux champs n'avaient AUCUN nom // accessible dès qu'ils portaient du texte.** // @@ -1075,7 +1153,7 @@ internal fun IssueDUneAction( onConsommer() } - action.misAlaCorbeille -> { + action.misAlaCorbeille || action.supprimee -> { onConsommer() onBack() } @@ -1113,6 +1191,7 @@ internal fun IssueDUneAction( ActionDEditeur.OrigineDErreur.CREATION -> ressources.getString(R.string.note_editor_link_create_failed, phrase) ActionDEditeur.OrigineDErreur.CORBEILLE, + ActionDEditeur.OrigineDErreur.SUPPRESSION, ActionDEditeur.OrigineDErreur.COPIE, null, -> phrase @@ -1250,6 +1329,7 @@ private fun MenuDeDebordement( onExporter: () -> Unit, onCopier: () -> Unit, onCorbeille: () -> Unit, + onSupprimerDefinitivement: () -> Unit, ) { var ouvert by rememberSaveable { mutableStateOf(false) } @@ -1358,5 +1438,17 @@ private fun MenuDeDebordement( onCorbeille() }, ) + // Last, and in red (Patrice, 2026-10-10): the one gesture of the menu that cannot be undone. The + // trash, which can, stays neutral. `error` is the theme's own red, readable on the menu. + DropdownMenuItem( + text = { Text(stringResource(R.string.trash_delete_forever), color = MaterialTheme.colorScheme.error) }, + leadingIcon = { + Icon(Icons.Outlined.DeleteForever, contentDescription = null, tint = MaterialTheme.colorScheme.error) + }, + onClick = { + ouvert = false + onSupprimerDefinitivement() + }, + ) } } diff --git a/app/src/main/java/com/filestech/notes_tech/ui/editor/NoteEditorViewModel.kt b/app/src/main/java/com/filestech/notes_tech/ui/editor/NoteEditorViewModel.kt index 450ae25f..315590d8 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/editor/NoteEditorViewModel.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/editor/NoteEditorViewModel.kt @@ -149,6 +149,8 @@ data class ActionDEditeur( val export: ExportResult? = null, val deplacee: Boolean = false, val misAlaCorbeille: Boolean = false, + /** Erased for good from the ⋮ menu (3.1.0): the screen leaves, as after the trash. */ + val supprimee: Boolean = false, /** Le contenu est dans le presse-papiers. `false` aussi quand il n'y avait rien à copier. */ val copiee: Boolean = false, /** La note était vide : le presse-papiers n'a **pas** été touché. */ @@ -165,7 +167,7 @@ data class ActionDEditeur( */ val origine: OrigineDErreur? = null, ) { - enum class OrigineDErreur { DEPLACEMENT, EXPORT, CREATION, CORBEILLE, COPIE } + enum class OrigineDErreur { DEPLACEMENT, EXPORT, CREATION, CORBEILLE, SUPPRESSION, COPIE } } /** @@ -197,6 +199,9 @@ class NoteEditorViewModel @Inject constructor( savedState: SavedStateHandle, ) : ViewModel() { + /** Opened by the long press's "Edit" (3.1.0): the note starts on the writing side. */ + val ouvrirEnEcriture: Boolean = savedState[Destination.ARG_ECRIRE] ?: false + private val noteId: String = checkNotNull(savedState[Destination.ARG_NOTE_ID]) { "l'editeur a ete ouvert sans identifiant de note" } @@ -850,6 +855,27 @@ class NoteEditorViewModel @Inject constructor( _action.value = ActionDEditeur(misAlaCorbeille = true) } + /** + * Erases the note for good (3.1.0, the ⋮ menu's "Delete permanently", after a confirmation). + * + * 🔴 The vault is checked again HERE, as `HomeViewModel.executer` does for the list: erasing a sealed + * note needs no key, so a confirmation answered after the automatic lock would otherwise erase a + * note of a closed vault. The screen closes its dialog when the note stops being readable; this is + * the check that does not depend on it. + */ + fun deletePermanently() = tenterUneAction(ActionDEditeur.OrigineDErreur.SUPPRESSION) { + val actuelle = notes.find(noteId) ?: return@tenterUneAction + val protegee = actuelle.isLocked || folders.find(actuelle.folderId)?.isVault == true + if (protegee && !vaults.isUnlocked(actuelle.folderId)) { + _action.value = ActionDEditeur( + erreur = R.string.error_vault_locked, + origine = ActionDEditeur.OrigineDErreur.SUPPRESSION, + ) + return@tenterUneAction + } + if (notes.deletePermanently(noteId)) _action.value = ActionDEditeur(supprimee = true) + } + /** Relance le chargement après un déverrouillage réussi. */ fun retryAfterUnlock() { _state.value = _state.value.copy(lockedVault = null, loading = true) diff --git a/app/src/main/java/com/filestech/notes_tech/ui/folders/FolderDialogs.kt b/app/src/main/java/com/filestech/notes_tech/ui/folders/FolderDialogs.kt index fd54bac5..ad134e2b 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/folders/FolderDialogs.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/folders/FolderDialogs.kt @@ -84,7 +84,7 @@ fun FolderActionSheet(folder: Folder, unlocked: Boolean, onDismiss: () -> Unit, } EntreeDeMenu( icon = Icons.Outlined.DeleteOutline, - tint = couleurs.error, + destructive = true, title = stringResource(R.string.common_delete), onClick = { onAction(FolderAction.DELETE) }, ) diff --git a/app/src/main/java/com/filestech/notes_tech/ui/folders/FoldersDrawer.kt b/app/src/main/java/com/filestech/notes_tech/ui/folders/FoldersDrawer.kt index a8905003..2e9d78fe 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/folders/FoldersDrawer.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/folders/FoldersDrawer.kt @@ -39,6 +39,7 @@ import com.filestech.notes_tech.R import com.filestech.notes_tech.domain.model.Folder import com.filestech.notes_tech.ui.common.displayName import com.filestech.notes_tech.ui.theme.Formes +import com.filestech.notes_tech.ui.theme.couleursDeBoutonPrincipal /** * Le tiroir des dossiers. @@ -189,6 +190,7 @@ fun FoldersDrawer( FilledTonalButton( onClick = onCreateFolder, shape = Formes.bouton, + colors = couleursDeBoutonPrincipal(), modifier = Modifier.fillMaxWidth(), ) { Icon(imageVector = Icons.Outlined.CreateNewFolder, contentDescription = null) diff --git a/app/src/main/java/com/filestech/notes_tech/ui/home/FeuilleDActionsDeNote.kt b/app/src/main/java/com/filestech/notes_tech/ui/home/FeuilleDActionsDeNote.kt index 32276753..bc9e61f6 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/home/FeuilleDActionsDeNote.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/home/FeuilleDActionsDeNote.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.DeleteForever import androidx.compose.material.icons.outlined.DeleteOutline +import androidx.compose.material.icons.outlined.Edit import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SnackbarDuration @@ -48,6 +49,7 @@ import kotlinx.coroutines.launch internal fun FeuilleDActionsDeNote( note: Note, onDismiss: () -> Unit, + onEditer: () -> Unit, onCorbeille: () -> Unit, onSupprimer: () -> Unit, ) { @@ -62,6 +64,13 @@ internal fun FeuilleDActionsDeNote( .padding(horizontal = 16.dp, vertical = 8.dp) .semantics { heading() }, ) + // First, and the only one that keeps the note: it opens it straight on the writing side. + EntreeDeMenu( + icon = Icons.Outlined.Edit, + title = stringResource(R.string.note_editor_mode_edit), + onClick = onEditer, + ) + // Neutral: the trash can be undone. Only the permanent deletion is red, as in the ⋮ menu. EntreeDeMenu( icon = Icons.Outlined.DeleteOutline, title = stringResource(R.string.note_editor_menu_trash), @@ -69,7 +78,7 @@ internal fun FeuilleDActionsDeNote( ) EntreeDeMenu( icon = Icons.Outlined.DeleteForever, - tint = MaterialTheme.colorScheme.error, + destructive = true, title = stringResource(R.string.trash_delete_forever), onClick = onSupprimer, ) @@ -88,6 +97,9 @@ internal fun FeuilleDActionsDeNote( * is open (courtesy; the protection is `HomeViewModel.executer`, at execution); * - [gesteEnAttente]: a gesture refused at execution because the vault closed in the meantime runs * again once it is reopened — accepted, it must not be dropped in silence. + * - [creationEnAttente]: "+" in a closed vault asks for its secret, then creates the note. Until 3.1.0 + * the secret was asked and nothing followed: the user had to tap "+" a second time — an accepted + * gesture dropped in silence (seen on the emulator, 2026-10-10). * * Both are kept with the folder they wait for, and dropped with the unlock sheet if the user gives up. */ @@ -97,6 +109,7 @@ internal class GestesDeNoteEnCours { var noteASupprimer by mutableStateOf(null) private var menuEnAttente by mutableStateOf(null) private var gesteEnAttente by mutableStateOf(null) + private var creationEnAttente by mutableStateOf(null) /** * The long press on [note]. Opens its sheet, or returns the closed vault whose secret must come @@ -117,13 +130,18 @@ internal class GestesDeNoteEnCours { gesteEnAttente = evenement } + /** "+" was tapped in the closed vault [dossier]: the note is created once its secret is given. */ + fun attendreLaCreation(dossier: Folder) { + creationEnAttente = dossier.id + } + /** * [dossier] was just unlocked: what waited for IT resumes, and only that is taken off — what waits * for another vault keeps waiting (GPT-5.6 review, 2026-10-10: clearing everything first dropped a * gesture waiting for vault A when the sheet of vault B closed). Compared by id: the automatic lock * opens an unlock sheet too. */ - fun reprendreApres(dossier: Folder, executer: (GesteSurUneNote) -> Unit) { + fun reprendreApres(dossier: Folder, executer: (GesteSurUneNote) -> Unit, creer: () -> Unit) { val menu = menuEnAttente if (menu != null && menu.folderId == dossier.id) { menuEnAttente = null @@ -134,6 +152,10 @@ internal class GestesDeNoteEnCours { gesteEnAttente = null executer(geste.geste) } + if (creationEnAttente == dossier.id) { + creationEnAttente = null + creer() + } } /** @@ -145,22 +167,34 @@ internal class GestesDeNoteEnCours { fun garderSeulementPour(dossierId: String?) { if (menuEnAttente?.folderId != dossierId) menuEnAttente = null if (gesteEnAttente?.folder?.id != dossierId) gesteEnAttente = null + if (creationEnAttente != dossierId) creationEnAttente = null } /** Giving up the secret is giving up the gesture: what waited falls with the unlock sheet. */ fun abandonner() { menuEnAttente = null gesteEnAttente = null + creationEnAttente = null } } /** The sheet of [etat]'s note, then the confirmation of the irreversible choice. */ @Composable -internal fun FeuillesDesGestesDeNote(etat: GestesDeNoteEnCours, executer: (GesteSurUneNote) -> Unit) { +internal fun FeuillesDesGestesDeNote( + etat: GestesDeNoteEnCours, + executer: (GesteSurUneNote) -> Unit, + ecrire: (Note) -> Unit, +) { etat.noteEnMenu?.let { note -> FeuilleDActionsDeNote( note = note, onDismiss = { etat.noteEnMenu = null }, + // A note of a closed vault gets this sheet only once its vault is open (`appuiLong`), and + // the editor asks for the secret again if it closed meanwhile: nothing to check here. + onEditer = { + etat.noteEnMenu = null + ecrire(note) + }, onCorbeille = { etat.noteEnMenu = null executer(GesteSurUneNote.MettreALaCorbeille(note)) diff --git a/app/src/main/java/com/filestech/notes_tech/ui/home/HomeRoute.kt b/app/src/main/java/com/filestech/notes_tech/ui/home/HomeRoute.kt index 1f62e103..d6df7604 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/home/HomeRoute.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/home/HomeRoute.kt @@ -55,6 +55,8 @@ import kotlinx.coroutines.launch @Composable fun HomeRoute( onOpenNote: (Note) -> Unit, + /** The long press's "Edit": the note opens to be written (3.1.0). */ + onEditNote: (Note) -> Unit, onOpenSearch: () -> Unit, onOpenTrash: () -> Unit, onOpenSettings: () -> Unit, @@ -145,7 +147,11 @@ fun HomeRoute( if (evenement.inInbox) portee.launch { snackbars.showSnackbar(messageNoteEnBoiteDeReception) } } - is HomeEvent.VaultLocked -> dossierAOuvrir = evenement.folder + // The creation waits for the secret, then happens: one "+" is enough. + is HomeEvent.VaultLocked -> { + gestes.attendreLaCreation(evenement.folder) + dossierAOuvrir = evenement.folder + } // ⚠️ La chaine est formatee ICI, avec son argument reel, et pas par un gabarit // « %s » construit a l'avance : ce dernier casserait en silence le jour ou la // chaine gagnerait un second placeholder. Releve par l'audit i18n du 2026-08-14. @@ -341,12 +347,12 @@ fun HomeRoute( val geste = gesteEnAttente gesteEnAttente = null if (geste != null && geste.dossier.id == dossier.id) executerLeGeste(geste) - gestes.reprendreApres(dossier, homeViewModel::executer) + gestes.reprendreApres(dossier, homeViewModel::executer, homeViewModel::createNote) }, ) } - FeuillesDesGestesDeNote(gestes, homeViewModel::executer) + FeuillesDesGestesDeNote(gestes, homeViewModel::executer, ecrire = onEditNote) dossierAProteger?.let { dossier -> val mode = modeChoisi diff --git a/app/src/main/java/com/filestech/notes_tech/ui/home/HomeScreen.kt b/app/src/main/java/com/filestech/notes_tech/ui/home/HomeScreen.kt index 53d0cbc1..eafd996e 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/home/HomeScreen.kt @@ -65,6 +65,7 @@ import com.filestech.notes_tech.ui.common.displayName import com.filestech.notes_tech.ui.common.libelleDeTri import com.filestech.notes_tech.ui.common.rememberFolderDisplayName import com.filestech.notes_tech.ui.theme.Formes +import com.filestech.notes_tech.ui.theme.SemanticColors /** * L'écran d'accueil : la liste des notes, filtrée par dossier ou par recherche. @@ -229,6 +230,8 @@ fun HomeScreen( ExtendedFloatingActionButton( onClick = onNewNote, shape = Formes.bouton, + containerColor = SemanticColors.primaryButton, + contentColor = SemanticColors.onPrimaryButton, modifier = Modifier.semantics { contentDescription = nomDeLaNouvelleNote }, icon = { Icon(Icons.Outlined.EditNote, contentDescription = null) }, text = { Text(nomDeLaNouvelleNote) }, diff --git a/app/src/main/java/com/filestech/notes_tech/ui/home/NoteCard.kt b/app/src/main/java/com/filestech/notes_tech/ui/home/NoteCard.kt index 0bbba40c..a640f1c9 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/home/NoteCard.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/home/NoteCard.kt @@ -260,10 +260,15 @@ internal fun titreAffiche(note: Note): String = when { @Composable private fun FolderChip(label: String, modifier: Modifier = Modifier) { val couleurs = MaterialTheme.colorScheme + // A white chip with a grey border (Patrice, 2026-10-10), not the grey fill it had: it reads the same + // on a plain card and on a coloured one. `background` is white in the light theme and the screen's + // own dark in the dark one, where a white chip would glare; the folder name keeps its contrast on + // both (onSurfaceVariant: 5.4:1 on white, 6.1:1 on the dark background). Surface( modifier = modifier, shape = RoundedCornerShape(8.dp), - color = couleurs.surfaceContainerHighest, + color = couleurs.background, + border = BorderStroke(1.dp, couleurs.outlineVariant), ) { Row( modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp), diff --git a/app/src/main/java/com/filestech/notes_tech/ui/navigation/Destination.kt b/app/src/main/java/com/filestech/notes_tech/ui/navigation/Destination.kt index cf1b342d..d34c2efd 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/navigation/Destination.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/navigation/Destination.kt @@ -62,15 +62,20 @@ sealed interface Destination { * réservé — mais une route est une URI, et se reposer sur la forme actuelle des identifiants * pour ne pas encoder revient à faire dépendre la navigation d'un détail du modèle de données. */ - data class Editor(val noteId: String) : Destination { - override val route = "$EDITOR_PREFIX/${Uri.encode(noteId)}" + /** + * [ecrire]: open the note to be written rather than read (3.1.0) — the long press's "Edit". Absent + * from the route when false, so every other way into the editor keeps the route it had. + */ + data class Editor(val noteId: String, val ecrire: Boolean = false) : Destination { + override val route = "$EDITOR_PREFIX/${Uri.encode(noteId)}" + if (ecrire) "?$ARG_ECRIRE=true" else "" } companion object { const val EDITOR_PREFIX = "editor" const val ARG_NOTE_ID = "noteId" + const val ARG_ECRIRE = "ecrire" /** Le motif déclaré au `NavHost`. Une seule déclaration, ici. */ - const val EDITOR_PATTERN = "$EDITOR_PREFIX/{$ARG_NOTE_ID}" + const val EDITOR_PATTERN = "$EDITOR_PREFIX/{$ARG_NOTE_ID}?$ARG_ECRIRE={$ARG_ECRIRE}" } } diff --git a/app/src/main/java/com/filestech/notes_tech/ui/panic/PanicScreens.kt b/app/src/main/java/com/filestech/notes_tech/ui/panic/PanicScreens.kt index e38ee239..95584fcf 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/panic/PanicScreens.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/panic/PanicScreens.kt @@ -46,6 +46,7 @@ import com.filestech.notes_tech.security.panic.PanicReport import com.filestech.notes_tech.ui.common.ActionDeDialogue import com.filestech.notes_tech.ui.secure.SecureWindowGuard import com.filestech.notes_tech.ui.theme.Formes +import com.filestech.notes_tech.ui.theme.couleursDeBoutonPrincipal import java.util.Locale import kotlin.system.exitProcess @@ -365,7 +366,9 @@ fun PanicOverlay(running: Boolean, report: PanicReport?, onClose: () -> Unit) { ) } Spacer(Modifier.height(24.dp)) - Button(onClick = onClose, shape = Formes.bouton) { Text(stringResource(R.string.panic_complete_close)) } + Button(onClick = onClose, shape = Formes.bouton, colors = couleursDeBoutonPrincipal()) { + Text(stringResource(R.string.panic_complete_close)) + } } } } diff --git a/app/src/main/java/com/filestech/notes_tech/ui/startup/StartupFailureScreen.kt b/app/src/main/java/com/filestech/notes_tech/ui/startup/StartupFailureScreen.kt index d0b445fe..071d74b2 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/startup/StartupFailureScreen.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/startup/StartupFailureScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.filestech.notes_tech.R import com.filestech.notes_tech.ui.theme.Formes +import com.filestech.notes_tech.ui.theme.couleursDeBoutonPrincipal /** * Ce que voit un utilisateur dont la base ne s'ouvre pas. @@ -64,7 +65,7 @@ fun StartupFailureScreen(reason: FailureReason, onRetry: () -> Unit, modifier: M text = stringResource(reason.messageRes), style = MaterialTheme.typography.bodyMedium, ) - Button(onClick = onRetry, shape = Formes.bouton) { + Button(onClick = onRetry, shape = Formes.bouton, colors = couleursDeBoutonPrincipal()) { Text(stringResource(R.string.startup_failure_retry)) } } diff --git a/app/src/main/java/com/filestech/notes_tech/ui/theme/CouleursDeNote.kt b/app/src/main/java/com/filestech/notes_tech/ui/theme/CouleursDeNote.kt index f0b3c1cb..2c59f376 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/theme/CouleursDeNote.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/theme/CouleursDeNote.kt @@ -25,12 +25,14 @@ data class TeinteDeNote(val fond: Color, val bord: Color) * | | light theme | dark theme | * |---|---|---| * | body text (`onSurface`) | 13.5 – 14.7:1 | 11.9 – 12.7:1 | - * | date, excerpt, tags (`onSurfaceVariant`, small text, AA = 4.5) | 4.51 – 4.87:1 | 4.57 – 4.89:1 | + * | date, excerpt, tags (`onSurfaceVariant`, small text, AA = 4.5) | 4.56 – 4.87:1 | 4.57 – 4.89:1 | + * | links of the reading card (`primary`) | 4.51 – 4.82:1 | 5.56 – 5.96:1 | * | lock icon (`error`, AA non-text = 3) | 4.8 – 5.2:1 | 4.2 – 4.5:1 | * | favourite star | 4.8 – 5.2:1 | 10.9 – 11.6:1 | * * ⚠️ The light backgrounds are paler than a "pastel" picked by eye on purpose: the first, more - * saturated set left the secondary text at 4.1 – 4.4:1, under AA. The hue is carried by the border, + * saturated set left the secondary text at 4.1 – 4.4:1, under AA; and the pink was paled once more when + * the reading card put the links on it (4.47:1, caught by `CouleursDeNoteTest`). The hue is carried by the border, * which is what tells the notes apart at a glance — and what Patrice asked for ("une bordure un peu * plus prononcée"). Retouching a value means measuring again: the contrast is the constraint. * @@ -52,7 +54,7 @@ object CouleursDeNote { NoteColor.TEAL to TeinteDeNote(Color(0xFFDFF4F1), Color(0xFF1F8A7E)), NoteColor.BLUE to TeinteDeNote(Color(0xFFE6F0FD), Color(0xFF3D7BD0)), NoteColor.PURPLE to TeinteDeNote(Color(0xFFF4EEFB), Color(0xFF8459C4)), - NoteColor.PINK to TeinteDeNote(Color(0xFFFCE9F1), Color(0xFFC8528A)), + NoteColor.PINK to TeinteDeNote(Color(0xFFFDEDF4), Color(0xFFC8528A)), NoteColor.GRAY to TeinteDeNote(Color(0xFFEEF0F3), Color(0xFF7D8691)), ) diff --git a/app/src/main/java/com/filestech/notes_tech/ui/theme/SemanticColors.kt b/app/src/main/java/com/filestech/notes_tech/ui/theme/SemanticColors.kt index 17ee2496..bb95ec59 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/theme/SemanticColors.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/theme/SemanticColors.kt @@ -1,5 +1,7 @@ package com.filestech.notes_tech.ui.theme +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -105,6 +107,20 @@ object SemanticColors { */ val messageForeground: Color = Color.White + /** + * The app's main buttons — "New note", "Edit", "New folder", "Check for updates" — on the blue of + * the checkerboard, with white text (Patrice, 2026-10-10). The same pair as the messages: one blue + * for the signature of the app, defined once. + * + * Fixed in both themes, like the logo. White on it: 6.0:1 (AA 4.5, `SemanticColorsTest`). The + * button against the screen's background: 5.65:1 light, 3.15:1 dark (WCAG 1.4.11 asks 3). On the + * drawer's dark surface it is 2.88:1 — there the white label, at 6:1, is what identifies it. + */ + val primaryButton: Color = BLEU_DAMIER + + /** The text and icon of [primaryButton]. */ + val onPrimaryButton: Color = Color.White + private val AMBER_200 = Color(0xFFFFCC80) /** @@ -139,3 +155,15 @@ object SemanticColors { */ internal const val DARK_SURFACE_THRESHOLD = 0.35f } + +/** + * The colours of every filled button of the app that is not destructive ([SemanticColors.primaryButton]): + * one blue across the app (Patrice, 2026-10-10: "que toute l'appli soit harmonisée"). The destructive + * ones — erasing after a forgotten PIN, confirming the panic mode — keep the theme's `error` red: there + * the colour says the danger. Disabled, a button keeps Material's grey, as before. + */ +@Composable +fun couleursDeBoutonPrincipal(): ButtonColors = ButtonDefaults.buttonColors( + containerColor = SemanticColors.primaryButton, + contentColor = SemanticColors.onPrimaryButton, +) diff --git a/app/src/main/java/com/filestech/notes_tech/ui/vault/VaultSheets.kt b/app/src/main/java/com/filestech/notes_tech/ui/vault/VaultSheets.kt index 228753bb..2c35ccff 100644 --- a/app/src/main/java/com/filestech/notes_tech/ui/vault/VaultSheets.kt +++ b/app/src/main/java/com/filestech/notes_tech/ui/vault/VaultSheets.kt @@ -74,6 +74,7 @@ import com.filestech.notes_tech.ui.common.retryWaitMessage import com.filestech.notes_tech.ui.secure.ProprietesDeFeuilleSecrete import com.filestech.notes_tech.ui.secure.SecureWindowGuard import com.filestech.notes_tech.ui.theme.Formes +import com.filestech.notes_tech.ui.theme.couleursDeBoutonPrincipal /** * La feuille qui déverrouille un coffre, **du bon mode**. @@ -506,6 +507,7 @@ internal fun FeuilleDePhraseSecrete( } else { Button( shape = Formes.bouton, + colors = couleursDeBoutonPrincipal(), onClick = { erreurLocale = when { secret.length < VaultParams.PASSPHRASE_MIN_LENGTH -> tropCourte @@ -804,6 +806,7 @@ internal fun FeuilleDeCode( Button( shape = Formes.bouton, + colors = couleursDeBoutonPrincipal(), onClick = { if (saisi.length !in VaultParams.PIN_MIN_LENGTH..VaultParams.PIN_MAX_LENGTH) { erreurLocale = tropCourt @@ -1182,7 +1185,12 @@ private fun ResultatDeTentative(attempt: VaultAttempt?, onSuccess: (chiffrees: I */ @Composable private fun BoutonDeFermeture(onDone: () -> Unit) { - Button(onClick = onDone, shape = Formes.bouton, modifier = Modifier.fillMaxWidth()) { + Button( + onClick = onDone, + shape = Formes.bouton, + colors = couleursDeBoutonPrincipal(), + modifier = Modifier.fillMaxWidth(), + ) { Text(stringResource(R.string.common_close)) } } diff --git a/app/src/test/java/com/filestech/notes_tech/ui/home/GestesDeNoteEnCoursTest.kt b/app/src/test/java/com/filestech/notes_tech/ui/home/GestesDeNoteEnCoursTest.kt index 900d0d66..30e140af 100644 --- a/app/src/test/java/com/filestech/notes_tech/ui/home/GestesDeNoteEnCoursTest.kt +++ b/app/src/test/java/com/filestech/notes_tech/ui/home/GestesDeNoteEnCoursTest.kt @@ -34,7 +34,7 @@ class GestesDeNoteEnCoursTest { assertThat(etat.appuiLong(note, dossiers, coffresOuverts = emptySet())).isEqualTo(coffreA) assertThat(etat.noteEnMenu).isNull() - etat.reprendreApres(coffreA) { error("no gesture waits") } + etat.reprendreApres(coffreA, executer = { error("no gesture waits") }, creer = { error("no creation waits") }) assertThat(etat.noteEnMenu).isEqualTo(note) } @@ -55,8 +55,8 @@ class GestesDeNoteEnCoursTest { val executes = mutableListOf() etat.attendreLeCoffre(HomeEvent.GesteEnAttenteDuCoffre(coffreA, geste)) - etat.reprendreApres(coffreA) { executes += it } - etat.reprendreApres(coffreA) { executes += it } + etat.reprendreApres(coffreA, executer = { executes += it }, creer = AUCUNE_CREATION) + etat.reprendreApres(coffreA, executer = { executes += it }, creer = AUCUNE_CREATION) assertThat(executes).containsExactly(geste) } @@ -71,11 +71,11 @@ class GestesDeNoteEnCoursTest { etat.appuiLong(note, dossiers, emptySet()) etat.attendreLeCoffre(HomeEvent.GesteEnAttenteDuCoffre(coffreA, geste)) - etat.reprendreApres(coffreB) { executes += it } + etat.reprendreApres(coffreB, executer = { executes += it }, creer = AUCUNE_CREATION) assertThat(executes).isEmpty() assertThat(etat.noteEnMenu).isNull() - etat.reprendreApres(coffreA) { executes += it } + etat.reprendreApres(coffreA, executer = { executes += it }, creer = AUCUNE_CREATION) assertThat(executes).containsExactly(geste) assertThat(etat.noteEnMenu).isEqualTo(note) } @@ -93,7 +93,7 @@ class GestesDeNoteEnCoursTest { etat.garderSeulementPour(coffreA.id) etat.garderSeulementPour(coffreB.id) - etat.reprendreApres(coffreA) { error("the deletion outlived its sheet") } + etat.reprendreApres(coffreA, executer = { error("the deletion outlived its sheet") }, creer = AUCUNE_CREATION) assertThat(etat.noteEnMenu).isNull() } @@ -106,7 +106,7 @@ class GestesDeNoteEnCoursTest { etat.attendreLeCoffre(HomeEvent.GesteEnAttenteDuCoffre(coffreA, geste)) etat.garderSeulementPour(coffreA.id) - etat.reprendreApres(coffreA) { executes += it } + etat.reprendreApres(coffreA, executer = { executes += it }, creer = AUCUNE_CREATION) assertThat(executes).containsExactly(geste) } @@ -119,11 +119,38 @@ class GestesDeNoteEnCoursTest { etat.attendreLeCoffre(HomeEvent.GesteEnAttenteDuCoffre(coffreA, GesteSurUneNote.MettreALaCorbeille(note))) etat.abandonner() - etat.reprendreApres(coffreA) { error("the gesture was given up") } + etat.reprendreApres(coffreA, executer = { error("the gesture was given up") }, creer = AUCUNE_CREATION) assertThat(etat.noteEnMenu).isNull() } + /** "+" in a closed vault (3.1.0): the note is created once the vault is open — once, and only for it. */ + @Test + fun `a note asked for in a closed vault is created once it is open, once only`() { + val etat = GestesDeNoteEnCours() + var creations = 0 + etat.attendreLaCreation(coffreA) + + etat.reprendreApres(coffreB, executer = {}, creer = { creations++ }) + assertThat(creations).isEqualTo(0) + etat.reprendreApres(coffreA, executer = {}, creer = { creations++ }) + etat.reprendreApres(coffreA, executer = {}, creer = { creations++ }) + + assertThat(creations).isEqualTo(1) + } + + @Test + fun `a creation waiting for a vault dies with its sheet, or when the secret is given up`() { + val etat = GestesDeNoteEnCours() + etat.attendreLaCreation(coffreA) + etat.garderSeulementPour(coffreB.id) + etat.reprendreApres(coffreA, executer = {}, creer = { error("the creation outlived its sheet") }) + + etat.attendreLaCreation(coffreA) + etat.abandonner() + etat.reprendreApres(coffreA, executer = {}, creer = { error("the creation was given up") }) + } + private fun dossier(id: String, coffre: Boolean) = Folder( id = id, name = "Dossier $id", @@ -153,5 +180,6 @@ class GestesDeNoteEnCoursTest { private companion object { val INSTANT: Instant = Instant.ofEpochMilli(1_760_000_000_000L) + val AUCUNE_CREATION: () -> Unit = { error("no creation waits") } } } diff --git a/app/src/test/java/com/filestech/notes_tech/ui/theme/CouleursDeNoteTest.kt b/app/src/test/java/com/filestech/notes_tech/ui/theme/CouleursDeNoteTest.kt index 17cb2410..ad789465 100644 --- a/app/src/test/java/com/filestech/notes_tech/ui/theme/CouleursDeNoteTest.kt +++ b/app/src/test/java/com/filestech/notes_tech/ui/theme/CouleursDeNoteTest.kt @@ -30,6 +30,8 @@ class CouleursDeNoteTest { verifier("date, extrait, étiquettes (onSurfaceVariant)", schema.onSurfaceVariant, AA_TEXTE) verifier("cadenas (error)", schema.error, AA_NON_TEXTE) verifier("étoile des favoris", SemanticColors.favoriteIconOn(schema), AA_NON_TEXTE) + // The reading card (3.1.0) draws the note's links in `primary` on its colour. + verifier("liens de la fiche de lecture (primary)", schema.primary, AA_TEXTE) } } diff --git a/app/src/test/java/com/filestech/notes_tech/ui/theme/SemanticColorsTest.kt b/app/src/test/java/com/filestech/notes_tech/ui/theme/SemanticColorsTest.kt index 29c477b3..432fa2ad 100644 --- a/app/src/test/java/com/filestech/notes_tech/ui/theme/SemanticColorsTest.kt +++ b/app/src/test/java/com/filestech/notes_tech/ui/theme/SemanticColorsTest.kt @@ -77,6 +77,17 @@ class SemanticColorsTest { assertThat(ratio).isGreaterThan(SEUIL_TEXTE) } + /** + * The main buttons (3.1.0): white on the checkerboard blue, readable; and the button itself seen + * against both themes' background (WCAG 1.4.11, 3:1). + */ + @Test + fun `the main buttons are readable and stand out on both themes`() { + assertThat(contraste(SemanticColors.onPrimaryButton, SemanticColors.primaryButton)).isGreaterThan(SEUIL_TEXTE) + assertThat(contraste(SemanticColors.primaryButton, schemeClair().background)).isAtLeast(3f) + assertThat(contraste(SemanticColors.primaryButton, schemeSombre().background)).isAtLeast(3f) + } + /** * 🔴 **Le cas témoin.** Sans lui, les trois tests ci-dessus passeraient tout aussi bien si * [contraste] rendait une constante. Deux teintes proches doivent échouer au seuil. diff --git a/fastlane/metadata/android/de-DE/changelogs/5011.txt b/fastlane/metadata/android/de-DE/changelogs/5011.txt index abc45f2b..d462e8db 100644 --- a/fastlane/metadata/android/de-DE/changelogs/5011.txt +++ b/fastlane/metadata/android/de-DE/changelogs/5011.txt @@ -1 +1 @@ -Ein Tipp auf eine Notiz öffnet sie jetzt zum Lesen; „Bearbeiten“ zum Schreiben. Langes Drücken auf eine Notiz: in den Papierkorb (mit Rückgängig) oder endgültig löschen. Eine Pastellfarbe pro Notiz, sichtbar in den Listen (bei einer Tresornotiz nur, solange der Tresor entsperrt ist). Rechtstexte aktualisiert. Build-Werkzeuge aktualisiert (AGP 9). +Eine Notiz öffnet sich jetzt zum Lesen, als Karte in ihrer Farbe; „Bearbeiten“ zum Schreiben. Langes Drücken: bearbeiten, in den Papierkorb (mit Rückgängig) oder endgültig löschen. Eine Pastellfarbe pro Notiz (bei einer Tresornotiz nur bei entsperrtem Tresor). Schaltflächen im Blau des Schachbretts. „+“ in einem gesperrten Tresor legt die Notiz nach dem Entsperren an. Rechtstexte aktualisiert. AGP 9. diff --git a/fastlane/metadata/android/de-DE/changelogs/5012.txt b/fastlane/metadata/android/de-DE/changelogs/5012.txt index abc45f2b..d462e8db 100644 --- a/fastlane/metadata/android/de-DE/changelogs/5012.txt +++ b/fastlane/metadata/android/de-DE/changelogs/5012.txt @@ -1 +1 @@ -Ein Tipp auf eine Notiz öffnet sie jetzt zum Lesen; „Bearbeiten“ zum Schreiben. Langes Drücken auf eine Notiz: in den Papierkorb (mit Rückgängig) oder endgültig löschen. Eine Pastellfarbe pro Notiz, sichtbar in den Listen (bei einer Tresornotiz nur, solange der Tresor entsperrt ist). Rechtstexte aktualisiert. Build-Werkzeuge aktualisiert (AGP 9). +Eine Notiz öffnet sich jetzt zum Lesen, als Karte in ihrer Farbe; „Bearbeiten“ zum Schreiben. Langes Drücken: bearbeiten, in den Papierkorb (mit Rückgängig) oder endgültig löschen. Eine Pastellfarbe pro Notiz (bei einer Tresornotiz nur bei entsperrtem Tresor). Schaltflächen im Blau des Schachbretts. „+“ in einem gesperrten Tresor legt die Notiz nach dem Entsperren an. Rechtstexte aktualisiert. AGP 9. diff --git a/fastlane/metadata/android/de-DE/changelogs/5013.txt b/fastlane/metadata/android/de-DE/changelogs/5013.txt index abc45f2b..d462e8db 100644 --- a/fastlane/metadata/android/de-DE/changelogs/5013.txt +++ b/fastlane/metadata/android/de-DE/changelogs/5013.txt @@ -1 +1 @@ -Ein Tipp auf eine Notiz öffnet sie jetzt zum Lesen; „Bearbeiten“ zum Schreiben. Langes Drücken auf eine Notiz: in den Papierkorb (mit Rückgängig) oder endgültig löschen. Eine Pastellfarbe pro Notiz, sichtbar in den Listen (bei einer Tresornotiz nur, solange der Tresor entsperrt ist). Rechtstexte aktualisiert. Build-Werkzeuge aktualisiert (AGP 9). +Eine Notiz öffnet sich jetzt zum Lesen, als Karte in ihrer Farbe; „Bearbeiten“ zum Schreiben. Langes Drücken: bearbeiten, in den Papierkorb (mit Rückgängig) oder endgültig löschen. Eine Pastellfarbe pro Notiz (bei einer Tresornotiz nur bei entsperrtem Tresor). Schaltflächen im Blau des Schachbretts. „+“ in einem gesperrten Tresor legt die Notiz nach dem Entsperren an. Rechtstexte aktualisiert. AGP 9. diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt index c36d9823..f70f079b 100644 --- a/fastlane/metadata/android/de-DE/full_description.txt +++ b/fastlane/metadata/android/de-DE/full_description.txt @@ -20,7 +20,7 @@ Notes Tech: Markdown-Notizen, 100 % lokal und im Ruhezustand verschlüsselt. In Bearbeiten • Notizen öffnen sich zum Lesen (Markdown: Überschriften, Listen, Links); „Bearbeiten“ zum Schreiben, automatisches Speichern -• Langes Drücken auf eine Notiz: in den Papierkorb, mit Rückgängig, oder endgültig löschen +• Langes Drücken auf eine Notiz: bearbeiten, in den Papierkorb mit Rückgängig oder endgültig löschen • Eine Pastellfarbe pro Notiz in den Listen (bei einer Tresornotiz nur bei entsperrtem Tresor) • Rückverweise [[Titel]] mit Autovervollständigung und Bereich „Notizen, die hierher verlinken“ • Sofortige FTS5-Suche, einstellbare Sortierung diff --git a/fastlane/metadata/android/en-US/changelogs/5011.txt b/fastlane/metadata/android/en-US/changelogs/5011.txt index 867f64f3..7294fa58 100644 --- a/fastlane/metadata/android/en-US/changelogs/5011.txt +++ b/fastlane/metadata/android/en-US/changelogs/5011.txt @@ -1 +1 @@ -Tapping a note now opens it to be read; “Edit” to write. Long press on a note: move to trash (with Undo) or delete permanently. A pastel colour per note, shown in the lists (for a vault note, only while its vault is open). Legal texts updated. Build toolchain updated (AGP 9). +A note now opens to be read, as a card in its colour; “Edit” to write. Long press: edit, move to trash (with Undo) or delete permanently. A pastel colour per note (a vault note's only while its vault is open). Buttons in the checkerboard blue. “+” in a closed vault creates the note once unlocked. Legal texts updated. Build toolchain updated (AGP 9). diff --git a/fastlane/metadata/android/en-US/changelogs/5012.txt b/fastlane/metadata/android/en-US/changelogs/5012.txt index 867f64f3..7294fa58 100644 --- a/fastlane/metadata/android/en-US/changelogs/5012.txt +++ b/fastlane/metadata/android/en-US/changelogs/5012.txt @@ -1 +1 @@ -Tapping a note now opens it to be read; “Edit” to write. Long press on a note: move to trash (with Undo) or delete permanently. A pastel colour per note, shown in the lists (for a vault note, only while its vault is open). Legal texts updated. Build toolchain updated (AGP 9). +A note now opens to be read, as a card in its colour; “Edit” to write. Long press: edit, move to trash (with Undo) or delete permanently. A pastel colour per note (a vault note's only while its vault is open). Buttons in the checkerboard blue. “+” in a closed vault creates the note once unlocked. Legal texts updated. Build toolchain updated (AGP 9). diff --git a/fastlane/metadata/android/en-US/changelogs/5013.txt b/fastlane/metadata/android/en-US/changelogs/5013.txt index 867f64f3..7294fa58 100644 --- a/fastlane/metadata/android/en-US/changelogs/5013.txt +++ b/fastlane/metadata/android/en-US/changelogs/5013.txt @@ -1 +1 @@ -Tapping a note now opens it to be read; “Edit” to write. Long press on a note: move to trash (with Undo) or delete permanently. A pastel colour per note, shown in the lists (for a vault note, only while its vault is open). Legal texts updated. Build toolchain updated (AGP 9). +A note now opens to be read, as a card in its colour; “Edit” to write. Long press: edit, move to trash (with Undo) or delete permanently. A pastel colour per note (a vault note's only while its vault is open). Buttons in the checkerboard blue. “+” in a closed vault creates the note once unlocked. Legal texts updated. Build toolchain updated (AGP 9). diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 5185dfa9..5948a2f1 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -20,7 +20,7 @@ Notes Tech: 100% local Markdown note-taking, encrypted at rest. Written in Kotli Editing • Notes open to be read (Markdown: headings, lists, links); “Edit” to write, autosave -• Long press on a note: move to trash, with Undo, or delete permanently +• Long press on a note: edit, move to trash with Undo, or delete permanently • A pastel colour per note in the lists (a vault note's only while its vault is open) • [[Title]] backlinks with autocomplete + "notes linking here" panel • Instant FTS5 search, configurable sort diff --git a/fastlane/metadata/android/es-ES/changelogs/5011.txt b/fastlane/metadata/android/es-ES/changelogs/5011.txt index 3d52abd7..08402cd3 100644 --- a/fastlane/metadata/android/es-ES/changelogs/5011.txt +++ b/fastlane/metadata/android/es-ES/changelogs/5011.txt @@ -1 +1 @@ -Tocar una nota ahora la abre para leerla; «Editar» para escribir. Pulsación larga en una nota: moverla a la papelera (con Deshacer) o eliminarla definitivamente. Un color pastel por nota, visible en las listas (para una nota de caja fuerte, solo mientras está desbloqueada). Textos legales actualizados. Herramientas de compilación actualizadas (AGP 9). +Una nota ahora se abre para leerla, como una ficha en su color; «Editar» para escribir. Pulsación larga: editar, mover a la papelera (con Deshacer) o eliminar definitivamente. Un color pastel por nota (el de una nota de caja fuerte, solo desbloqueada). Botones en el azul del damero. «+» en una caja fuerte bloqueada crea la nota tras desbloquearla. Textos legales actualizados. AGP 9. diff --git a/fastlane/metadata/android/es-ES/changelogs/5012.txt b/fastlane/metadata/android/es-ES/changelogs/5012.txt index 3d52abd7..08402cd3 100644 --- a/fastlane/metadata/android/es-ES/changelogs/5012.txt +++ b/fastlane/metadata/android/es-ES/changelogs/5012.txt @@ -1 +1 @@ -Tocar una nota ahora la abre para leerla; «Editar» para escribir. Pulsación larga en una nota: moverla a la papelera (con Deshacer) o eliminarla definitivamente. Un color pastel por nota, visible en las listas (para una nota de caja fuerte, solo mientras está desbloqueada). Textos legales actualizados. Herramientas de compilación actualizadas (AGP 9). +Una nota ahora se abre para leerla, como una ficha en su color; «Editar» para escribir. Pulsación larga: editar, mover a la papelera (con Deshacer) o eliminar definitivamente. Un color pastel por nota (el de una nota de caja fuerte, solo desbloqueada). Botones en el azul del damero. «+» en una caja fuerte bloqueada crea la nota tras desbloquearla. Textos legales actualizados. AGP 9. diff --git a/fastlane/metadata/android/es-ES/changelogs/5013.txt b/fastlane/metadata/android/es-ES/changelogs/5013.txt index 3d52abd7..08402cd3 100644 --- a/fastlane/metadata/android/es-ES/changelogs/5013.txt +++ b/fastlane/metadata/android/es-ES/changelogs/5013.txt @@ -1 +1 @@ -Tocar una nota ahora la abre para leerla; «Editar» para escribir. Pulsación larga en una nota: moverla a la papelera (con Deshacer) o eliminarla definitivamente. Un color pastel por nota, visible en las listas (para una nota de caja fuerte, solo mientras está desbloqueada). Textos legales actualizados. Herramientas de compilación actualizadas (AGP 9). +Una nota ahora se abre para leerla, como una ficha en su color; «Editar» para escribir. Pulsación larga: editar, mover a la papelera (con Deshacer) o eliminar definitivamente. Un color pastel por nota (el de una nota de caja fuerte, solo desbloqueada). Botones en el azul del damero. «+» en una caja fuerte bloqueada crea la nota tras desbloquearla. Textos legales actualizados. AGP 9. diff --git a/fastlane/metadata/android/es-ES/full_description.txt b/fastlane/metadata/android/es-ES/full_description.txt index ad0b40c6..bf5d420c 100644 --- a/fastlane/metadata/android/es-ES/full_description.txt +++ b/fastlane/metadata/android/es-ES/full_description.txt @@ -20,7 +20,7 @@ Notes Tech: notas Markdown 100 % locales, cifradas en reposo. Escrita en Kotlin. Edición • Las notas se abren para leer (Markdown: títulos, listas, enlaces); «Editar» para escribir, guardado automático -• Pulsación larga en una nota: moverla a la papelera, con Deshacer, o eliminarla definitivamente +• Pulsación larga en una nota: editarla, moverla a la papelera con Deshacer o eliminarla definitivamente • Un color pastel por nota en las listas (el de una nota de caja fuerte, solo desbloqueada) • Retroenlaces [[Título]] con autocompletado y panel «notas que enlazan aquí» • Búsqueda FTS5 instantánea, orden configurable diff --git a/fastlane/metadata/android/fr-FR/changelogs/5011.txt b/fastlane/metadata/android/fr-FR/changelogs/5011.txt index 821073fd..e9ea29b8 100644 --- a/fastlane/metadata/android/fr-FR/changelogs/5011.txt +++ b/fastlane/metadata/android/fr-FR/changelogs/5011.txt @@ -1 +1 @@ -Toucher une note l'ouvre en lecture ; « Éditer » pour écrire. Appui long sur une note : mettre à la corbeille (avec Annuler) ou supprimer définitivement. Une couleur pastel par note, visible dans les listes (pour une note de coffre, seulement coffre ouvert). Textes légaux mis à jour. Chaîne de build modernisée (AGP 9). +Une note s'ouvre en lecture, en fiche à sa couleur ; « Éditer » pour écrire. Appui long : éditer, mettre à la corbeille (avec Annuler) ou supprimer définitivement. Une couleur pastel par note (celle d'une note de coffre, seulement coffre ouvert). Boutons au bleu du damier. « + » dans un coffre fermé crée la note après déverrouillage. Textes légaux mis à jour. Chaîne de build modernisée (AGP 9). diff --git a/fastlane/metadata/android/fr-FR/changelogs/5012.txt b/fastlane/metadata/android/fr-FR/changelogs/5012.txt index 821073fd..e9ea29b8 100644 --- a/fastlane/metadata/android/fr-FR/changelogs/5012.txt +++ b/fastlane/metadata/android/fr-FR/changelogs/5012.txt @@ -1 +1 @@ -Toucher une note l'ouvre en lecture ; « Éditer » pour écrire. Appui long sur une note : mettre à la corbeille (avec Annuler) ou supprimer définitivement. Une couleur pastel par note, visible dans les listes (pour une note de coffre, seulement coffre ouvert). Textes légaux mis à jour. Chaîne de build modernisée (AGP 9). +Une note s'ouvre en lecture, en fiche à sa couleur ; « Éditer » pour écrire. Appui long : éditer, mettre à la corbeille (avec Annuler) ou supprimer définitivement. Une couleur pastel par note (celle d'une note de coffre, seulement coffre ouvert). Boutons au bleu du damier. « + » dans un coffre fermé crée la note après déverrouillage. Textes légaux mis à jour. Chaîne de build modernisée (AGP 9). diff --git a/fastlane/metadata/android/fr-FR/changelogs/5013.txt b/fastlane/metadata/android/fr-FR/changelogs/5013.txt index 821073fd..e9ea29b8 100644 --- a/fastlane/metadata/android/fr-FR/changelogs/5013.txt +++ b/fastlane/metadata/android/fr-FR/changelogs/5013.txt @@ -1 +1 @@ -Toucher une note l'ouvre en lecture ; « Éditer » pour écrire. Appui long sur une note : mettre à la corbeille (avec Annuler) ou supprimer définitivement. Une couleur pastel par note, visible dans les listes (pour une note de coffre, seulement coffre ouvert). Textes légaux mis à jour. Chaîne de build modernisée (AGP 9). +Une note s'ouvre en lecture, en fiche à sa couleur ; « Éditer » pour écrire. Appui long : éditer, mettre à la corbeille (avec Annuler) ou supprimer définitivement. Une couleur pastel par note (celle d'une note de coffre, seulement coffre ouvert). Boutons au bleu du damier. « + » dans un coffre fermé crée la note après déverrouillage. Textes légaux mis à jour. Chaîne de build modernisée (AGP 9). diff --git a/fastlane/metadata/android/fr-FR/full_description.txt b/fastlane/metadata/android/fr-FR/full_description.txt index 14805fc7..69ae9684 100644 --- a/fastlane/metadata/android/fr-FR/full_description.txt +++ b/fastlane/metadata/android/fr-FR/full_description.txt @@ -20,7 +20,7 @@ Notes Tech : prise de notes Markdown 100 % locale, chiffrée au repos. Écrite e Édition • Les notes s'ouvrent en lecture (Markdown : titres, listes, liens) ; « Éditer » pour écrire, enregistrement automatique -• Appui long sur une note : mettre à la corbeille, avec Annuler, ou supprimer définitivement +• Appui long sur une note : éditer, mettre à la corbeille avec Annuler, ou supprimer définitivement • Une couleur pastel par note dans les listes (celle d'une note de coffre, seulement coffre ouvert) • Rétroliens [[Titre]] avec autocomplétion + panneau « notes qui mentionnent celle-ci » • Recherche FTS5 instantanée, tri réglable diff --git a/fastlane/metadata/android/it-IT/changelogs/5011.txt b/fastlane/metadata/android/it-IT/changelogs/5011.txt index 02e588c4..c14dc5de 100644 --- a/fastlane/metadata/android/it-IT/changelogs/5011.txt +++ b/fastlane/metadata/android/it-IT/changelogs/5011.txt @@ -1 +1 @@ -Toccare una nota ora la apre in lettura; «Modifica» per scrivere. Pressione prolungata su una nota: spostarla nel cestino (con Annulla) o eliminarla definitivamente. Un colore pastello per nota, visibile negli elenchi (per una nota in cassaforte, solo mentre è sbloccata). Testi legali aggiornati. Strumenti di build aggiornati (AGP 9). +Una nota ora si apre in lettura, come una scheda nel suo colore; «Modifica» per scrivere. Pressione prolungata: modificare, spostare nel cestino (con Annulla) o eliminare definitivamente. Un colore pastello per nota (per una nota in cassaforte, solo a cassaforte sbloccata). Pulsanti nel blu della scacchiera. «+» in una cassaforte bloccata crea la nota dopo lo sblocco. Testi legali aggiornati. AGP 9. diff --git a/fastlane/metadata/android/it-IT/changelogs/5012.txt b/fastlane/metadata/android/it-IT/changelogs/5012.txt index 02e588c4..c14dc5de 100644 --- a/fastlane/metadata/android/it-IT/changelogs/5012.txt +++ b/fastlane/metadata/android/it-IT/changelogs/5012.txt @@ -1 +1 @@ -Toccare una nota ora la apre in lettura; «Modifica» per scrivere. Pressione prolungata su una nota: spostarla nel cestino (con Annulla) o eliminarla definitivamente. Un colore pastello per nota, visibile negli elenchi (per una nota in cassaforte, solo mentre è sbloccata). Testi legali aggiornati. Strumenti di build aggiornati (AGP 9). +Una nota ora si apre in lettura, come una scheda nel suo colore; «Modifica» per scrivere. Pressione prolungata: modificare, spostare nel cestino (con Annulla) o eliminare definitivamente. Un colore pastello per nota (per una nota in cassaforte, solo a cassaforte sbloccata). Pulsanti nel blu della scacchiera. «+» in una cassaforte bloccata crea la nota dopo lo sblocco. Testi legali aggiornati. AGP 9. diff --git a/fastlane/metadata/android/it-IT/changelogs/5013.txt b/fastlane/metadata/android/it-IT/changelogs/5013.txt index 02e588c4..c14dc5de 100644 --- a/fastlane/metadata/android/it-IT/changelogs/5013.txt +++ b/fastlane/metadata/android/it-IT/changelogs/5013.txt @@ -1 +1 @@ -Toccare una nota ora la apre in lettura; «Modifica» per scrivere. Pressione prolungata su una nota: spostarla nel cestino (con Annulla) o eliminarla definitivamente. Un colore pastello per nota, visibile negli elenchi (per una nota in cassaforte, solo mentre è sbloccata). Testi legali aggiornati. Strumenti di build aggiornati (AGP 9). +Una nota ora si apre in lettura, come una scheda nel suo colore; «Modifica» per scrivere. Pressione prolungata: modificare, spostare nel cestino (con Annulla) o eliminare definitivamente. Un colore pastello per nota (per una nota in cassaforte, solo a cassaforte sbloccata). Pulsanti nel blu della scacchiera. «+» in una cassaforte bloccata crea la nota dopo lo sblocco. Testi legali aggiornati. AGP 9. diff --git a/fastlane/metadata/android/it-IT/full_description.txt b/fastlane/metadata/android/it-IT/full_description.txt index fea6c315..a5d21f1c 100644 --- a/fastlane/metadata/android/it-IT/full_description.txt +++ b/fastlane/metadata/android/it-IT/full_description.txt @@ -20,7 +20,7 @@ Notes Tech: note Markdown 100% locali, cifrate a riposo. Scritta in Kotlin. Modifica • Le note si aprono in lettura (Markdown: titoli, elenchi, link); «Modifica» per scrivere, salvataggio automatico -• Pressione prolungata su una nota: spostarla nel cestino, con Annulla, o eliminarla definitivamente +• Pressione prolungata su una nota: modificarla, spostarla nel cestino con Annulla o eliminarla definitivamente • Un colore pastello per nota negli elenchi (per una nota in cassaforte, solo a cassaforte sbloccata) • Collegamenti inversi [[Titolo]] con completamento automatico e pannello «note che rimandano qui» • Ricerca FTS5 istantanea, ordinamento regolabile