feat(android): every field of an entry can be changed, not just the password

PatchEntryField already took a field name; what was missing was carrying it
from the row. It travels in the detail row itself rather than being guessed
from the label - a caption is prose, and the day it changes the page would
send a field name that does not exist, or quietly stop offering the edit.

Delphi does not take it on trust either: four names accepted, the rest
refused. The snapshot also carries tombstones, attachments and timestamps
that PatchEntryField would overwrite without understanding them.

An empty row now shows IF it can be filled - a missing username you cannot
add is a dead end. Emptying is allowed, except on a password, where it would
almost always be a slip; the refusal says so rather than doing nothing.

Show only appears on a masked field: on a site in clear it has nothing to
reveal, and would in fact hide it. A note keeps only Name and Content - a
site and a username mean nothing there.
This commit is contained in:
r-zakarya
2026-08-21 16:13:35 +01:00
parent 4785dc116a
commit 732c52a64e
4 changed files with 88 additions and 36 deletions
+33 -22
View File
@@ -149,14 +149,17 @@ type
TDetailRow = record
Caption, Value: string;
Secret: Boolean;
/// Modifiable depuis le telephone. UN SEUL champ l'est pour l'instant - le
/// mot de passe - et c'est deliberé : creer ou supprimer une entree
/// demanderait des tombstones et la generation d'uuid, alors que changer un
/// mot de passe couvre l'essentiel du besoin sans rien de tout cela.
/// Marque ICI plutot que reconnu au libelle cote page : un intitule est de
/// la prose, et le jour ou il change la page cesserait silencieusement de
/// proposer la modification.
Editable: Boolean;
/// LE NOM DU CHAMP DANS L'INSTANTANE, vide si la ligne ne se modifie pas.
///
/// Il porte les deux choses a la fois : qu'on puisse editer, et quoi
/// editer. Marque ICI plutot que devine du libelle cote page : un intitule
/// est de la prose, et le jour ou il change la page enverrait un nom de
/// champ inexistant - ou, pire, cesserait silencieusement de proposer la
/// modification.
///
/// Supprimer une entree n'y figure pas : cela demande des tombstones, et
/// c'est l'etape 3 de la §7.
Field: string;
end;
function BuildRows(const E: TVaultEntry): TArray<TDetailRow>;
@@ -166,26 +169,33 @@ var
LMeta: string;
procedure Add(const ACaption, AValue: string; ASecret: Boolean;
AEditable: Boolean = False);
const AField: string = '');
begin
if AValue = '' then Exit; // une ligne vide n'apprend rien
// Une ligne vide n'apprend rien - SAUF si elle se remplit. Un identifiant
// absent qu'on ne peut pas ajouter serait une impasse.
if (AValue = '') and (AField = '') then Exit;
SetLength(LRows, Length(LRows) + 1);
LRows[High(LRows)].Caption := ACaption;
LRows[High(LRows)].Value := AValue;
LRows[High(LRows)].Secret := ASecret;
LRows[High(LRows)].Editable := AEditable;
LRows[High(LRows)].Caption := ACaption;
LRows[High(LRows)].Value := AValue;
LRows[High(LRows)].Secret := ASecret;
LRows[High(LRows)].Field := AField;
end;
begin
Add('Site', E.Site, False);
Add('Username', E.Username, False);
Add('Name', E.Title, False, 'title');
// Le corps d'une note est range dans le champ mot de passe : meme donnee,
// autre intitule, et pas de raison de la masquer comme un secret.
// Seule la ligne du mot de passe est modifiable. Le corps d'une note le
// serait en principe, mais il se saisit sur plusieurs lignes - un autre
// controle, une autre fois.
if E.IsNote then Add('Content', E.Password, False)
else Add('Password', E.Password, True, True);
//
// Un site et un identifiant n'ont aucun sens pour une note. Les proposer
// vides reviendrait a inviter a remplir ce que rien n'affichera jamais.
if E.IsNote then
Add('Content', E.Password, False, 'password')
else
begin
Add('Site', E.Site, False, 'site');
Add('Username', E.Username, False, 'username');
Add('Password', E.Password, True, 'password');
end;
for I := 0 to High(E.Fields) do
Add(E.Fields[I].Caption, E.Fields[I].Value, E.Fields[I].Secret);
@@ -233,7 +243,8 @@ begin
LObj.AddPair('c', LRows[I].Caption);
LObj.AddPair('v', LRows[I].Value);
LObj.AddPair('s', TJSONBool.Create(LRows[I].Secret));
if LRows[I].Editable then LObj.AddPair('e', TJSONBool.Create(True));
// 'e' porte le nom du champ, et sa presence vaut "modifiable".
if LRows[I].Field <> '' then LObj.AddPair('e', LRows[I].Field);
LArr.AddElement(LObj);
end;
Result := LRoot.ToJSON;