diff --git a/android-client/PLAN.md b/android-client/PLAN.md index 4c57c79..60a8e84 100644 --- a/android-client/PLAN.md +++ b/android-client/PLAN.md @@ -776,10 +776,20 @@ téléphone qu'on voudra ensuite ouvrir sur le poste demandera un import. **Sans retour depuis le téléphone**, et c'est délibéré : la corbeille du poste est un drapeau `deleted` qui ne voyage pas dans la synchronisation, donc - une suppression qui s'y arrêterait ne se propagerait nulle part. D'où le - bouton en bas d'écran, en deux temps, qui se désarme seul au bout de quatre - secondes — un bouton armé indéfiniment se fait toucher par mégarde bien plus - tard. + une suppression qui s'y arrêterait ne se propagerait nulle part. + + **Et elle ne va pas non plus dans la corbeille du poste** — vérifié dans le + code serveur : recevoir une pierre tombale déclenche + `DELETE FROM vault_entries`, un effacement dur. Supprimer depuis le téléphone + retire donc la ligne des deux côtés, en contournant la corbeille. **Le seul + filet est la sauvegarde du poste.** Il n'y a rien à afficher comme corbeille + ici : le téléphone n'en reçoit jamais le contenu. + + L'action vit dans le **menu de l'entrée** (les trois points, en tête de + détail), et non plus en bouton pleine largeur au bas de chaque écran : rouge + et présent en permanence pour un geste qu'on fait trois fois par an. Deux + gestes délibérés — ouvrir, puis confirmer — et rien qui traîne sous les yeux + le reste du temps. 4. **Réconcilier avec la synchronisation** — fait le 2026-08-21. Jusqu'ici les deux situations ne se rencontraient pas : un coffre local diff --git a/android-client/web/app.html b/android-client/web/app.html index b9fd75d..5d6ef63 100644 --- a/android-client/web/app.html +++ b/android-client/web/app.html @@ -201,6 +201,20 @@ padding: 10px 10px 12px 14px; margin-bottom: 8px; } .field .cap { color: var(--muted); font-size: 12px; margin-bottom: 4px; } + /* LE MENU DE L'ENTREE. Il ne contient qu'une action, et c'est voulu : elle + est sans retour, et une action sans retour ne doit pas etre a portee d'un + appui distrait. Deux gestes deliberes - ouvrir, puis confirmer - valent + mieux qu'un bouton rouge en permanence sous les yeux. */ + .menu { position: absolute; right: 12px; top: 58px; z-index: 60; + background: var(--bg-top); border-radius: var(--radius-s); + border: 1px solid rgba(255,255,255,.14); + box-shadow: 0 8px 24px rgba(0,0,0,.35); + min-width: 190px; overflow: hidden; } + .menu button { display: block; width: 100%; text-align: left; border: 0; + background: none; color: var(--text); font: inherit; + padding: 14px 16px; } + .menu button:active { background: rgba(255,255,255,.10); } + .menu button.danger { color: #ffb4b4; } .field .line { display: flex; align-items: center; gap: 8px; } .field .val { flex: 1; min-width: 0; word-break: break-all; @@ -396,8 +410,10 @@

+
+
@@ -630,6 +646,8 @@ var ICONS = { edit: '', eye: '' + '', + dots: '' + + '', check: '', close: '', // Un arc, pas un cercle : c'est l'ouverture qui rend la rotation visible. @@ -684,15 +702,8 @@ function renderDetail(d) { + ico('edit', 'Edit') + '' : '') + ''; } - // SUPPRIMER, tout en bas et en deux temps. Sans retour possible depuis le - // telephone - il n'y a pas de corbeille ici - donc ni en tete d'ecran ni a - // portee d'un appui distrait. Le premier appui ne fait qu'armer ; c'est la - // confirmation, faute de pouvoir en demander une au moteur (une invite - // JavaScript exige un WebChromeClient que FMX ne fournit pas). - html += '
' - + '
'; document.getElementById('fields').innerHTML = html; + closeMenu(); document.body.classList.add('on-detail'); view = 'detail'; send('state?view=detail'); @@ -703,24 +714,6 @@ document.getElementById('fields').addEventListener('click', function (ev) { // l'intention. Tout ce qui suit lit et ecrit sur le bouton. var t = ev.target.closest('button') || ev.target; var k = t.getAttribute('data-show'); - if (t.id === 'del') { - if (t.getAttribute('data-armed') === null) { - t.setAttribute('data-armed', '1'); - t.textContent = 'Really delete - tap again'; - // Desarme tout seul : un bouton reste arme indefiniment se fait toucher - // par megarde bien plus tard, et la suppression est sans retour. - setTimeout(function () { - if (!t.parentNode) return; - t.removeAttribute('data-armed'); - t.textContent = 'Delete entry'; - }, 4000); - return; - } - t.disabled = true; - t.textContent = 'Deleting...'; - send('delentry'); - return; - } if (k !== null) { var cell = document.querySelector('[data-v="' + k + '"]'); var hidden = cell.classList.contains('mask'); @@ -1041,7 +1034,61 @@ document.getElementById('fields').addEventListener('click', function (ev) { }); document.getElementById('new').addEventListener('click', openNewEntry); +// LE MENU DE L'ENTREE, et la suppression qui y vit. +// +// Elle etait un bouton pleine largeur au bas de chaque detail : rouge, present +// en permanence, pour une action qu'on fait trois fois par an. Ici il faut +// l'ouvrir, puis confirmer - deux gestes deliberes, et rien qui traine sous +// les yeux le reste du temps. +// +// SANS RETOUR, toujours : il n'y a pas de corbeille sur le telephone, et la +// pierre tombale fait disparaitre l'entree du poste aussi, sans passer par la +// sienne. Le seul filet est la sauvegarde du poste. +function closeMenu() { + var m = document.getElementById('menu'); + m.hidden = true; + m.innerHTML = ''; +} + +function openMenu() { + var m = document.getElementById('menu'); + m.innerHTML = ''; + m.hidden = false; +} + +document.getElementById('kebab').innerHTML = ico('dots', 'Entry menu'); +document.getElementById('kebab').addEventListener('click', function (ev) { + ev.stopPropagation(); + if (document.getElementById('menu').hidden) openMenu(); else closeMenu(); +}); + +document.getElementById('menu').addEventListener('click', function (ev) { + var b = ev.target.closest('button'); + if (!b) return; + if (b.getAttribute('data-del') !== null) { + // Le premier appui n'arme que la confirmation. Ouvrir le menu etait deja + // delibere ; ceci ferme la porte a l'appui qui derape dans la liste. + b.removeAttribute('data-del'); + b.setAttribute('data-delconfirm', '1'); + b.textContent = 'Confirm delete'; + return; + } + if (b.getAttribute('data-delconfirm') === null) return; + b.disabled = true; + b.textContent = 'Deleting...'; + send('delentry'); +}); + +// Un appui n'importe ou ailleurs referme le menu - y compris sur le contenu, +// sinon il resterait ouvert pendant qu'on lit l'entree. +document.addEventListener('click', function (ev) { + if (document.getElementById('menu').hidden) return; + if (ev.target.closest('#menu, #kebab')) return; + closeMenu(); +}); + function goList() { + closeMenu(); document.body.classList.remove('on-detail'); document.body.classList.add('on-list'); view = 'list';