From e64d13d474570806a74b2cd80e78dbbbf7ccbd74 Mon Sep 17 00:00:00 2001 From: Patrice Haltaya Date: Sat, 10 Oct 2026 17:50:20 +0200 Subject: [PATCH 1/6] Home: "+" in a closed vault creates the note once the secret is given MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since 3.0.0, "+" in a closed vault asked for its secret, then did nothing: the vault opened and the user had to tap "+" again — an accepted gesture dropped in silence, the very thing HomeRoute's comments refuse everywhere else. Seen on the emulator while testing 3.1.0; Patrice: "corrige-le". The creation now waits for the vault like the long-press gestures do, in GestesDeNoteEnCours: it runs once that vault is unlocked, once only, and is given up with its unlock sheet — dismissed, or moved to another vault. The protection does not move: HomeViewModel.createNote still seals the note in the same transaction as its insert, and still refuses a vault that closed in between. Tests: GestesDeNoteEnCoursTest +2 (created once the vault is open and once only, not for another vault; given up with its sheet or with the secret). Unit tests 464/0, detekt, ktlint. Co-Authored-By: Claude Opus 5.5 --- .../ui/home/FeuilleDActionsDeNote.kt | 17 ++++++- .../filestech/notes_tech/ui/home/HomeRoute.kt | 8 +++- .../ui/home/GestesDeNoteEnCoursTest.kt | 44 +++++++++++++++---- 3 files changed, 58 insertions(+), 11 deletions(-) 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 3227675..19c5395 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 @@ -88,6 +88,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 +100,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 +121,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 +143,10 @@ internal class GestesDeNoteEnCours { gesteEnAttente = null executer(geste.geste) } + if (creationEnAttente == dossier.id) { + creationEnAttente = null + creer() + } } /** @@ -145,12 +158,14 @@ 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 } } 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 1f62e10..30c7fd3 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 @@ -145,7 +145,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,7 +345,7 @@ 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) }, ) } 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 900d0d6..30e140a 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") } } } From 8af9b899657866fd34e7388328e6e86491fdfeee Mon Sep 17 00:00:00 2001 From: Patrice Haltaya Date: Sat, 10 Oct 2026 17:54:46 +0200 Subject: [PATCH 2/6] UI: one blue for every main button, the checkerboard's, with white text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patrice, 2026-10-10: the "New note", "Edit", "New folder" and "Check for updates" buttons on the blue of the checkerboard with white text — "que toute l'appli soit harmonisée". So every filled button that is not destructive now takes it: also Unlock and Validate (app lock), Close (end of panic mode), Retry (start-up failure) and the three buttons of the vault sheets. The blue is SemanticColors' BLEU_DAMIER (#0B60C5, read from the logo), already the messages' background: named once more as primaryButton/onPrimaryButton, not defined twice. couleursDeBoutonPrincipal() gives the filled buttons their colours; the two floating ones take the pair directly. Disabled buttons keep Material's grey. Kept red, on purpose: erasing after a forgotten PIN and confirming the panic mode — there the colour says the danger. Contrast (SemanticColorsTest, new case): white on the blue 6.0:1 (AA 4.5); the button against the background 5.65:1 light, 3.15:1 dark (WCAG 1.4.11, 3:1). Co-Authored-By: Claude Opus 5.5 --- .../notes_tech/ui/about/AboutScreen.kt | 2 ++ .../notes_tech/ui/applock/AppLockScreen.kt | 2 ++ .../notes_tech/ui/applock/AppLockSettings.kt | 2 ++ .../notes_tech/ui/editor/NoteEditorScreen.kt | 2 ++ .../notes_tech/ui/folders/FoldersDrawer.kt | 2 ++ .../notes_tech/ui/home/HomeScreen.kt | 3 ++ .../notes_tech/ui/panic/PanicScreens.kt | 5 +++- .../ui/startup/StartupFailureScreen.kt | 3 +- .../notes_tech/ui/theme/SemanticColors.kt | 28 +++++++++++++++++++ .../notes_tech/ui/vault/VaultSheets.kt | 10 ++++++- .../notes_tech/ui/theme/SemanticColorsTest.kt | 11 ++++++++ 11 files changed, 67 insertions(+), 3 deletions(-) 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 4e4f0c6..d8cf70b 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 222fb58..822ec5b 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 e2e03d1..f29655e 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/editor/NoteEditorScreen.kt b/app/src/main/java/com/filestech/notes_tech/ui/editor/NoteEditorScreen.kt index accd25d..8d80c33 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 @@ -501,6 +501,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 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 a890500..2e9d78f 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/HomeScreen.kt b/app/src/main/java/com/filestech/notes_tech/ui/home/HomeScreen.kt index 53d0cbc..eafd996 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/panic/PanicScreens.kt b/app/src/main/java/com/filestech/notes_tech/ui/panic/PanicScreens.kt index e38ee23..95584fc 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 d0b445f..071d74b 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/SemanticColors.kt b/app/src/main/java/com/filestech/notes_tech/ui/theme/SemanticColors.kt index 17ee249..bb95ec5 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 228753b..2c35ccf 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/theme/SemanticColorsTest.kt b/app/src/test/java/com/filestech/notes_tech/ui/theme/SemanticColorsTest.kt index 29c477b..432fa2a 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. From fbfa7b2706f4659c29d59375a242b0b08cc07e57 Mon Sep 17 00:00:00 2001 From: Patrice Haltaya Date: Sat, 10 Oct 2026 18:03:18 +0200 Subject: [PATCH 3/6] Reading: the note drawn as a note card; "Edit" in the long press; trash in red MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Asked by Patrice on 2026-10-10 while testing 3.1.0 on his phone. - Reading card ("Fiche", his choice among three sketches): the note sits on the screen as a card, rounded, with its pastel background and a 2 dp border of its colour — the plain card and a grey border without one —, a slight shadow, its title at the top, a thin rule, the text, and "Last modified" with its date at the bottom (the Info panel's own string, five languages). The date is the saved note's: the editor takes back the row it saved. - Long press: "Edit" first in the sheet, opening the note straight on the writing side. The editor route takes an optional `ecrire` argument (Destination.Editor), absent from every other route; a closed vault's note gets the sheet only once its vault is open, and the editor asks for its secret again if it closed meanwhile. - "Move to trash" in red in the ⋮ menu — and in the long-press sheet, so that the same gesture has the same colour everywhere. EntreeDeMenu gains `destructive` (title and icon red); `tint` alone still colours the icon only, for the vault entries' red padlock. Deleting a folder takes it too. Found by the palette test, fixed: the reading card puts the links (`primary`) on the note's colour, and the light pink left them at 4.47:1, under AA. The pink is paler (#FDEDF4: links 4.60:1, secondary text 4.65:1); CouleursDeNoteTest now measures the links on every colour, both themes. Tests: unit 465/0; on the emulator EditeurTest 33/33, end to end 3/3, home 16/16. detekt, ktlint. Co-Authored-By: Claude Opus 5.5 --- .../notes_tech/ui/NotesTechNavHost.kt | 9 +- .../notes_tech/ui/common/EntreeDeMenu.kt | 11 +- .../notes_tech/ui/editor/NoteEditorScreen.kt | 106 +++++++++++++----- .../ui/editor/NoteEditorViewModel.kt | 3 + .../notes_tech/ui/folders/FolderDialogs.kt | 2 +- .../ui/home/FeuilleDActionsDeNote.kt | 24 +++- .../filestech/notes_tech/ui/home/HomeRoute.kt | 4 +- .../notes_tech/ui/navigation/Destination.kt | 11 +- .../notes_tech/ui/theme/CouleursDeNote.kt | 8 +- .../notes_tech/ui/theme/CouleursDeNoteTest.kt | 2 + 10 files changed, 141 insertions(+), 39 deletions(-) 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 dd01a84..177cb07 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/common/EntreeDeMenu.kt b/app/src/main/java/com/filestech/notes_tech/ui/common/EntreeDeMenu.kt index 6cd236f..e26d3c8 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 8d80c33..f508aad 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 @@ -50,6 +53,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 @@ -107,8 +111,10 @@ 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 +151,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()) @@ -744,14 +751,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, @@ -820,6 +820,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 @@ -836,21 +899,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.** // @@ -1352,9 +1402,13 @@ private fun MenuDeDebordement( onCopier() }, ) + // In red, as every gesture that removes a note (Patrice, 2026-10-10): `error`, the theme's own red, + // readable on both menus' surfaces. DropdownMenuItem( - text = { Text(stringResource(R.string.note_editor_menu_trash)) }, - leadingIcon = { Icon(Icons.Outlined.DeleteOutline, contentDescription = null) }, + text = { Text(stringResource(R.string.note_editor_menu_trash), color = MaterialTheme.colorScheme.error) }, + leadingIcon = { + Icon(Icons.Outlined.DeleteOutline, contentDescription = null, tint = MaterialTheme.colorScheme.error) + }, onClick = { ouvert = false onCorbeille() 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 450ae25..dbf3b46 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 @@ -197,6 +197,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" } 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 fd54bac..ad134e2 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/home/FeuilleDActionsDeNote.kt b/app/src/main/java/com/filestech/notes_tech/ui/home/FeuilleDActionsDeNote.kt index 19c5395..b4792b0 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,14 +64,22 @@ 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, + ) + // Red like the ⋮ menu's same entry: the same gesture has the same colour everywhere. EntreeDeMenu( icon = Icons.Outlined.DeleteOutline, + destructive = true, title = stringResource(R.string.note_editor_menu_trash), onClick = onCorbeille, ) EntreeDeMenu( icon = Icons.Outlined.DeleteForever, - tint = MaterialTheme.colorScheme.error, + destructive = true, title = stringResource(R.string.trash_delete_forever), onClick = onSupprimer, ) @@ -171,11 +181,21 @@ internal class GestesDeNoteEnCours { /** 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 30c7fd3..d6df760 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, @@ -350,7 +352,7 @@ fun HomeRoute( ) } - 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/navigation/Destination.kt b/app/src/main/java/com/filestech/notes_tech/ui/navigation/Destination.kt index cf1b342..d34c2ef 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/theme/CouleursDeNote.kt b/app/src/main/java/com/filestech/notes_tech/ui/theme/CouleursDeNote.kt index f0b3c1c..2c59f37 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/test/java/com/filestech/notes_tech/ui/theme/CouleursDeNoteTest.kt b/app/src/test/java/com/filestech/notes_tech/ui/theme/CouleursDeNoteTest.kt index 17cb241..ad78946 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) } } From 7f01aab111a99354526a4a7ca7ba73b0475cd244 Mon Sep 17 00:00:00 2001 From: Patrice Haltaya Date: Sat, 10 Oct 2026 18:10:17 +0200 Subject: [PATCH 4/6] Editor menu: "Delete permanently" in red; the trash neutral again; a white folder chip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patrice, 2026-10-10, after testing on his phone: - "Move to trash" is neutral again, in the ⋮ menu and in the long-press sheet — it can be undone. - The ⋮ menu gains "Delete permanently", last and in red, after the trash's own confirmation, as from the list. The editor leaves once the note is gone, as after the trash. The confirmation closes when the note stops being readable (vault locked), and NoteEditorViewModel.deletePermanently checks the vault again at execution — erasing a sealed note needs no key, so this check is the protection. - The folder chip on the cards: a white background and a grey border instead of the grey fill. `background` is white in the light theme; in the dark one it is the screen's dark — kept so, on his word ("laisse comme ça pour le thème sombre"). Tests on the emulator: EditeurTest 33/33 (the menu test presses the new entry), AccueilTest 16/16. Unit tests, detekt, ktlint green. Co-Authored-By: Claude Opus 5.5 --- .../notes_tech/ui/editor/EditeurTest.kt | 4 ++ .../notes_tech/ui/editor/NoteEditorScreen.kt | 54 +++++++++++++++---- .../ui/editor/NoteEditorViewModel.kt | 25 ++++++++- .../ui/home/FeuilleDActionsDeNote.kt | 3 +- .../filestech/notes_tech/ui/home/NoteCard.kt | 7 ++- 5 files changed, 80 insertions(+), 13 deletions(-) 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 9fdd0fb..df54578 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/editor/NoteEditorScreen.kt b/app/src/main/java/com/filestech/notes_tech/ui/editor/NoteEditorScreen.kt index f508aad..cbbff17 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 @@ -37,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 @@ -105,6 +106,7 @@ 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 @@ -172,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 @@ -236,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, @@ -250,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, @@ -407,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, @@ -472,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, /** @@ -653,6 +678,7 @@ fun NoteEditorScreen( onExporter = onExporter, onCopier = onCopier, onCorbeille = onCorbeille, + onSupprimerDefinitivement = onSupprimerDefinitivement, ) } }, @@ -1127,7 +1153,7 @@ internal fun IssueDUneAction( onConsommer() } - action.misAlaCorbeille -> { + action.misAlaCorbeille || action.supprimee -> { onConsommer() onBack() } @@ -1165,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 @@ -1302,6 +1329,7 @@ private fun MenuDeDebordement( onExporter: () -> Unit, onCopier: () -> Unit, onCorbeille: () -> Unit, + onSupprimerDefinitivement: () -> Unit, ) { var ouvert by rememberSaveable { mutableStateOf(false) } @@ -1402,16 +1430,24 @@ private fun MenuDeDebordement( onCopier() }, ) - // In red, as every gesture that removes a note (Patrice, 2026-10-10): `error`, the theme's own red, - // readable on both menus' surfaces. DropdownMenuItem( - text = { Text(stringResource(R.string.note_editor_menu_trash), color = MaterialTheme.colorScheme.error) }, + text = { Text(stringResource(R.string.note_editor_menu_trash)) }, + leadingIcon = { Icon(Icons.Outlined.DeleteOutline, contentDescription = null) }, + onClick = { + ouvert = false + 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.DeleteOutline, contentDescription = null, tint = MaterialTheme.colorScheme.error) + Icon(Icons.Outlined.DeleteForever, contentDescription = null, tint = MaterialTheme.colorScheme.error) }, onClick = { ouvert = false - onCorbeille() + 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 dbf3b46..315590d 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 } } /** @@ -853,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/home/FeuilleDActionsDeNote.kt b/app/src/main/java/com/filestech/notes_tech/ui/home/FeuilleDActionsDeNote.kt index b4792b0..bc9e61f 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 @@ -70,10 +70,9 @@ internal fun FeuilleDActionsDeNote( title = stringResource(R.string.note_editor_mode_edit), onClick = onEditer, ) - // Red like the ⋮ menu's same entry: the same gesture has the same colour everywhere. + // Neutral: the trash can be undone. Only the permanent deletion is red, as in the ⋮ menu. EntreeDeMenu( icon = Icons.Outlined.DeleteOutline, - destructive = true, title = stringResource(R.string.note_editor_menu_trash), onClick = onCorbeille, ) 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 0bbba40..a640f1c 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), From 8a2905e4c5fe798c281bc68457fcf5a8f435ff28 Mon Sep 17 00:00:00 2001 From: Patrice Haltaya Date: Sat, 10 Oct 2026 18:12:22 +0200 Subject: [PATCH 5/6] 3.1.0: changelogs, store descriptions, READMEs and security policy brought up to date MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patrice: "mets tout à jour", before the release. - Changelogs 5011-5013, five languages (351-403 characters, under F-Droid's 500): the reading card, "Edit" in the long press, the checkerboard blue, "+" in a closed vault were missing. - Store descriptions: the long press offers "Edit" too. - README (English and French): version 3.1.0, a "What's new in 3.1.0" above the 3.0.0 one (kept), "Edit / Preview" replaced by reading and writing, colours in the features, the universal APK's name. - SECURITY.md / SECURITY.fr.md: current version 3.1.0, and what 3.1.0 changes for security — the vault checked at execution before a keyless trash or erase, the colour outside the vault's envelope like the tags, the reading mode's protections, schema 10, the reviews and audit. Co-Authored-By: Claude Opus 5.5 --- README.fr.md | 23 +++++++++++++++---- README.md | 23 +++++++++++++++---- SECURITY.fr.md | 22 +++++++++++++++++- SECURITY.md | 18 ++++++++++++++- .../android/de-DE/changelogs/5011.txt | 2 +- .../android/de-DE/changelogs/5012.txt | 2 +- .../android/de-DE/changelogs/5013.txt | 2 +- .../android/de-DE/full_description.txt | 2 +- .../android/en-US/changelogs/5011.txt | 2 +- .../android/en-US/changelogs/5012.txt | 2 +- .../android/en-US/changelogs/5013.txt | 2 +- .../android/en-US/full_description.txt | 2 +- .../android/es-ES/changelogs/5011.txt | 2 +- .../android/es-ES/changelogs/5012.txt | 2 +- .../android/es-ES/changelogs/5013.txt | 2 +- .../android/es-ES/full_description.txt | 2 +- .../android/fr-FR/changelogs/5011.txt | 2 +- .../android/fr-FR/changelogs/5012.txt | 2 +- .../android/fr-FR/changelogs/5013.txt | 2 +- .../android/fr-FR/full_description.txt | 2 +- .../android/it-IT/changelogs/5011.txt | 2 +- .../android/it-IT/changelogs/5012.txt | 2 +- .../android/it-IT/changelogs/5013.txt | 2 +- .../android/it-IT/full_description.txt | 2 +- 24 files changed, 96 insertions(+), 30 deletions(-) diff --git a/README.fr.md b/README.fr.md index d31ae09..f44c228 100644 --- a/README.fr.md +++ b/README.fr.md @@ -4,7 +4,7 @@ 🇬🇧 [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, @@ -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 124779b..f131a74 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ 🇫🇷 [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 @@ -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 257ff4a..58d8d62 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 f3d1ebf..a9aeffc 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/fastlane/metadata/android/de-DE/changelogs/5011.txt b/fastlane/metadata/android/de-DE/changelogs/5011.txt index abc45f2..d462e8d 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 abc45f2..d462e8d 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 abc45f2..d462e8d 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 c36d982..f70f079 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 867f64f..7294fa5 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 867f64f..7294fa5 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 867f64f..7294fa5 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 5185dfa..5948a2f 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 3d52abd..08402cd 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 3d52abd..08402cd 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 3d52abd..08402cd 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 ad0b40c..bf5d420 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 821073f..e9ea29b 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 821073f..e9ea29b 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 821073f..e9ea29b 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 14805fc..69ae968 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 02e588c..c14dc5d 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 02e588c..c14dc5d 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 02e588c..c14dc5d 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 fea6c31..a5d21f1 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 From feb7e970e65209d6f726da5517d96c3984916a49 Mon Sep 17 00:00:00 2001 From: Patrice Haltaya Date: Sat, 10 Oct 2026 18:16:00 +0200 Subject: [PATCH 6/6] Docs aligned with 3.1.0: third-party notices and README intros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patrice: "que tout soit aligné", README, descriptions, legal texts. - THIRD_PARTY_NOTICES.md: read again from the resolved releaseRuntimeClasspath of 3.1.0 — Compose BOM 2026.09.00 (UI 1.12.1, Material 3 1.4.0), Hilt 2.60.1 / Hilt Navigation 1.4.0, Kotlin 2.4.10; the rest unchanged (Room 2.8.5, SQLCipher 4.16.0, Bouncy Castle 1.86, markdown 0.7.14, Okio 3.4.0…). - README (English and French): the intro named "Markdown preview", gone with 3.1.0 — now the notes read as cards in their colour. Also, outside the repository: its GitHub description and topics. The topics still said `flutter`, two versions after the rewrite in Kotlin — now `kotlin` and `jetpack-compose`. Co-Authored-By: Claude Opus 5.5 --- README.fr.md | 2 +- README.md | 2 +- THIRD_PARTY_NOTICES.md | 15 +++++++-------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/README.fr.md b/README.fr.md index f44c228..cd86c42 100644 --- a/README.fr.md +++ b/README.fr.md @@ -10,7 +10,7 @@ Application de prise de notes Markdown chiffrée pour Android, écrite en **Kotl 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. diff --git a/README.md b/README.md index f131a74..79359a9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Encrypted Markdown note-taking app for Android, written in **Kotlin** with Jetpa **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. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index c42bc51..c968c05 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 |