feat(android): an entry can be deleted from the phone

Both, or nothing: the row leaves the snapshot AND a tombstone takes its
place. Removing the row alone does not delete - the desktop still holds its
own copy, and the next pull would bring it back as an entry we never had.
The time matters too: the desktop's arbitration keeps an entry edited AFTER
the deletion rather than killing it a second time.

No trash on the phone, deliberately. The desktop's is a `deleted` flag that
does not travel in sync, so a deletion stopping there would propagate
nowhere. Hence a button at the very bottom, in two taps, that disarms itself
after four seconds - a button left armed gets touched by accident much
later, and this is the one action with no way back.

The confirmation is in the page because the engine cannot offer one: a
JavaScript prompt needs a WebChromeClient FMX does not provide.

Not exercised on the device: it would have destroyed a real entry.
This commit is contained in:
r-zakarya
2026-08-21 16:21:45 +01:00
parent 7a0ae6398b
commit 5107ebf7b2
4 changed files with 140 additions and 1 deletions
+29
View File
@@ -154,6 +154,9 @@
.btn:active { opacity: .62; }
.btn.wide { padding: 13px 18px; font-size: 15px; }
.btn.main { background: var(--accent); }
/* Rouge sourd : assez pour qu'on ne le confonde pas avec Copy, pas assez
pour attirer l'oeil sur la seule action irreversible de l'ecran. */
.btn.danger { background: rgba(220, 80, 80, .30); }
.empty { text-align: center; color: var(--muted); padding: 48px 16px; }
@@ -593,6 +596,14 @@ function renderDetail(d) {
+ (r.e ? '<button class="btn" data-edit="' + i + '">Edit</button>' : '')
+ '</div></div>';
}
// 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 += '<div class="field"><div class="line">'
+ '<button class="btn danger" id="del" style="flex:1">Delete entry'
+ '</button></div></div>';
document.getElementById('fields').innerHTML = html;
document.body.classList.add('on-detail');
view = 'detail';
@@ -601,6 +612,24 @@ function renderDetail(d) {
document.getElementById('fields').addEventListener('click', function (ev) {
var t = ev.target, 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');