feat(android): a selection bar in the list, and Duplicate everywhere
The floating menu was wrong for a list. It closed on the next tap, so opening an entry afterwards took two gestures and felt like a button had got stuck. Android's contextual bar instead: a long press selects, the top bar takes the place of search, says how many rows are held, and offers duplicate and delete. It goes away when nothing is held. One does not search while choosing what to delete, and two rows of controls would fight over the same thumb. Selection is multiple, because the cost is in the bar, not in the work: the patch closure loops, and ten deletions become ONE publish. The container goes up whole on every write - deleting ten entries one by one would pay that trip ten times, and give nine chances to fail halfway. A tap during selection picks rather than opens, which is also the way out: unpicking the last row closes the bar. The hardware back cancels the selection before locking the vault - locking because someone wanted to cancel a choice is the opposite of what the gesture means. Both menus stay, with distinct jobs: the bar acts on rows chosen in the list, the detail's three dots act on the entry in front of you. Duplicate copies everything, attachments included, as the desktop does - a copy that silently lost the attached document would be a surprise discovered too late.
This commit is contained in:
@@ -65,6 +65,14 @@ function AddEntry(const APlainJson, ATitle, ASite, AUsername, APassword,
|
||||
/// une suppression qui s'y arreterait ne se propagerait donc nulle part.
|
||||
function DeleteEntry(const APlainJson, AUuid: string): string;
|
||||
|
||||
/// Duplique une entree : meme contenu, uuid neuf, nom suffixe.
|
||||
///
|
||||
/// COPIE INTEGRALE, pieces jointes comprises. Le poste fait de meme, et une
|
||||
/// copie qui perdrait le document attache serait une surprise le jour ou l'on
|
||||
/// s'en apercevrait - c'est-a-dire trop tard. Le prix est un instantane plus
|
||||
/// lourd a publier ; il se voit tout de suite, lui.
|
||||
function DuplicateEntry(const APlainJson, AUuid: string): string;
|
||||
|
||||
/// Fond l'instantane LOCAL dans l'instantane DISTANT et rend le resultat.
|
||||
///
|
||||
/// LE DISTANT SERT DE BASE, jamais le local. Il porte ce que le telephone ne
|
||||
@@ -258,6 +266,58 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function DuplicateEntry(const APlainJson, AUuid: string): string;
|
||||
var
|
||||
LRoot, LEntry, LCopy: TJSONObject;
|
||||
LEntries: TJSONArray;
|
||||
I: Integer;
|
||||
LNow, LTitle: string;
|
||||
begin
|
||||
if AUuid = '' then
|
||||
raise EPMAPatch.Create('Entree sans uuid : rien a dupliquer');
|
||||
|
||||
LRoot := TJSONObject.ParseJSONValue(APlainJson) as TJSONObject;
|
||||
if LRoot = nil then
|
||||
raise EPMAPatch.Create('Instantane illisible');
|
||||
try
|
||||
if not LRoot.TryGetValue<TJSONArray>('entries', LEntries) then
|
||||
raise EPMAPatch.Create('Instantane sans tableau "entries"');
|
||||
|
||||
LCopy := nil;
|
||||
for I := 0 to LEntries.Count - 1 do
|
||||
begin
|
||||
if not (LEntries.Items[I] is TJSONObject) then Continue;
|
||||
LEntry := LEntries.Items[I] as TJSONObject;
|
||||
if LEntry.GetValue<string>('uuid', '') <> AUuid then Continue;
|
||||
// Reparser plutot que recopier champ par champ : c'est le seul moyen qui
|
||||
// n'oublie rien de ce que le telephone ne comprend pas.
|
||||
LCopy := CloneJson(LEntry);
|
||||
Break;
|
||||
end;
|
||||
if LCopy = nil then
|
||||
raise EPMAPatch.CreateFmt('Entree %s introuvable', [AUuid]);
|
||||
|
||||
LNow := NowUtcIso;
|
||||
LCopy.RemovePair('uuid').Free;
|
||||
LCopy.AddPair('uuid', LowerCase(Copy(GUIDToString(TGUID.NewGuid), 2, 36)));
|
||||
LCopy.RemovePair('created_at').Free;
|
||||
LCopy.AddPair('created_at', LNow);
|
||||
LCopy.RemovePair('updated_at').Free;
|
||||
LCopy.AddPair('updated_at', LNow);
|
||||
// Le nom change, sinon les deux lignes sont indiscernables dans la liste.
|
||||
// Retirer PUIS ajouter, comme partout ici : AddPair seul laisserait deux
|
||||
// paires 'title', et le lecteur prendrait la premiere - donc l'ancienne.
|
||||
LTitle := Trim(LCopy.GetValue<string>('title', '') + ' (copy)');
|
||||
LCopy.RemovePair('title').Free;
|
||||
LCopy.AddPair('title', LTitle);
|
||||
LEntries.AddElement(LCopy);
|
||||
|
||||
Result := LRoot.ToJSON;
|
||||
finally
|
||||
LRoot.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function DeleteEntry(const APlainJson, AUuid: string): string;
|
||||
var
|
||||
LRoot, LEntry, LStone: TJSONObject;
|
||||
|
||||
Reference in New Issue
Block a user