fix(android): the tap lands on the drawing, not on the button

Copying a password or a username opened the entry instead - about half the
time, which is the worst kind of failure to diagnose. Since those buttons
carry an icon, the tap target is the <svg> or one of its strokes, so reading
the attribute off the target returned null and the handler fell through to
"open". A finger landing sometimes on the glyph and sometimes on the margin
made it look random. Both handlers now resolve to the button first.

The new-entry form gets a folder, like the editor: an entry filed from the
start saves having to move it.

And a folder can now be created, not only chosen. Filing an entry under a
name is not enough - it is `folders` that fills the desktop's column, and a
name missing from it leaves the entry in a folder nothing displays, looking
lost. The two now go together, in one place, for both editing and creation.
'All' is never registered: it is the default, not a folder.
This commit is contained in:
r-zakarya
2026-08-21 22:11:14 +01:00
parent 0badc5fcfc
commit 1dba5d392f
3 changed files with 96 additions and 11 deletions
+37 -3
View File
@@ -49,7 +49,7 @@ function NewEmptySnapshot(const AUsername: string): string;
///
/// Un uuid neuf, et lui seul : pas de tombstone, on ne supprime pas encore.
function AddEntry(const APlainJson, ATitle, ASite, AUsername, APassword,
AKind: string): string;
AKind, AFolder: string): string;
/// Retire une entree de l'instantane et laisse une PIERRE TOMBALE a sa place.
///
@@ -142,6 +142,37 @@ end;
/// Copie profonde d'un objet JSON : le reparser est le moyen le plus court, et
/// le seul qui n'oublie aucun champ.
/// Inscrit un dossier dans la liste du coffre s'il n'y figure pas deja.
///
/// Ranger une entree dans un dossier ne suffit pas : c'est `folders` qui peuple
/// la colonne du poste, et un nom qui n'y figure pas donne une entree rangee
/// dans un dossier que rien n'affiche - elle semble disparue. Les deux vont
/// donc ensemble, et 'All' ne s'inscrit jamais : c'est le defaut, pas un
/// dossier.
procedure EnsureFolder(const ARoot: TJSONObject; const AName: string);
var
LFolders: TJSONArray;
LObj: TJSONObject;
I: Integer;
begin
if (AName = '') or (AName = 'All') then Exit;
if not ARoot.TryGetValue<TJSONArray>('folders', LFolders) then
begin
LFolders := TJSONArray.Create;
ARoot.AddPair('folders', LFolders);
end;
for I := 0 to LFolders.Count - 1 do
if (LFolders.Items[I] is TJSONObject) and
((LFolders.Items[I] as TJSONObject).GetValue<string>('name', '') = AName) then
Exit;
LObj := TJSONObject.Create;
LObj.AddPair('name', AName);
// Le poste lit aussi 'color' et 'icon' ; vides, il met ses defauts.
LObj.AddPair('color', '');
LObj.AddPair('icon', '');
LFolders.AddElement(LObj);
end;
function CloneJson(const AObj: TJSONObject): TJSONObject;
begin
Result := TJSONObject.ParseJSONValue(AObj.ToJSON) as TJSONObject;
@@ -279,7 +310,7 @@ begin
end;
function AddEntry(const APlainJson, ATitle, ASite, AUsername, APassword,
AKind: string): string;
AKind, AFolder: string): string;
var
LRoot, LEntry: TJSONObject;
LEntries: TJSONArray;
@@ -303,7 +334,9 @@ begin
// Le corps d'une note EST son mot de passe cote stockage - il n'y a pas de
// colonne separee, ni ici ni sur le poste.
LEntry.AddPair('password', APassword);
LEntry.AddPair('folder', 'All');
if AFolder <> '' then LEntry.AddPair('folder', AFolder)
else LEntry.AddPair('folder', 'All');
EnsureFolder(LRoot, AFolder);
LEntry.AddPair('tags', TJSONArray.Create);
LEntry.AddPair('totp_secret', '');
LEntry.AddPair('kind', AKind);
@@ -356,6 +389,7 @@ begin
// reste plus ancien que le leur.
LEntry.RemovePair('updated_at').Free;
LEntry.AddPair('updated_at', NowUtcIso);
if AField = 'folder' then EnsureFolder(LRoot, AValue);
LFound := True;
Break;