feat(android): folder filter, icons, sort, template, updated_at, FLAG_SECURE
Works through the plan's §2 and §3 items that need no hardware and no platform decision. - allowBackup="false". The private directory holds vault.bin; a cloud backup has no business in a password manager's path, even though what would leave is encrypted by a key that stays in the chip. - FLAG_SECURE, Release builds only - in debug it also blocks development screenshots. Complementary to hiding on WillBecomeInactive rather than redundant: that one covers the recents preview, the flag also covers manual captures, screen recording and casting to an external display. - List sorted by name (snapshot order is DB order, arbitrary to the eye), entry icons decoded once per vault rather than on every keystroke, and a folder dropdown whose selection survives a background refresh. No tag filter: text search already matches tags. - template and updated_at read and shown in the detail. updated_at is also step 1 of the write path - nothing is arbitrable without it. Android code is now type-checked without deploying: dccaarm64 reaches the link step and fails only on the NDK's ldandroid.exe. A JNI declaration that does not compile no longer waits for an APK to say so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,14 @@ type
|
||||
|
||||
TVaultEntry = record
|
||||
Uuid, Title, Site, Username, Password, Folder, Tags, Kind, Totp: string;
|
||||
/// Sous-type (carte, cle SSH, serveur...). Vide pour un identifiant ou
|
||||
/// une note ordinaire.
|
||||
Template: string;
|
||||
/// Icone en data URI. Non chiffree, purement cosmetique.
|
||||
IconB64: string;
|
||||
/// ISO 8601 UTC. Sert d'abord a l'affichage ; ce sera l'arbitre de la
|
||||
/// fusion le jour ou le client ecrira.
|
||||
UpdatedAt: string;
|
||||
Fields: TArray<TVaultField>;
|
||||
Deleted: Boolean;
|
||||
/// Le corps d'une note EST son mot de passe cote stockage : il n'y a pas
|
||||
@@ -39,6 +47,7 @@ type
|
||||
TVault = class
|
||||
private
|
||||
FEntries: TArray<TVaultEntry>;
|
||||
FFolders: TArray<string>;
|
||||
FUsername: string;
|
||||
FExportedAt: string;
|
||||
public
|
||||
@@ -47,12 +56,22 @@ type
|
||||
/// Efface le contenu. Appele au verrouillage.
|
||||
procedure Clear;
|
||||
|
||||
/// Indices des entrees correspondant a AQuery, dans l'ordre d'affichage.
|
||||
/// Indices des entrees correspondant a AQuery, TRIES par nom affiche -
|
||||
/// l'ordre de l'instantane est celui de la base, donc arbitraire a l'oeil.
|
||||
/// Requete vide = tout ce qui n'est pas en corbeille.
|
||||
///
|
||||
/// La recherche porte sur le nom, le site, l'identifiant et les tags -
|
||||
/// PAS sur le mot de passe : personne ne cherche par mot de passe, et le
|
||||
/// comparer allongerait inutilement son sejour en memoire de travail.
|
||||
function Search(const AQuery: string; AIncludeTrash: Boolean = False): TArray<Integer>;
|
||||
/// Comme les etiquettes sont couvertes par la recherche texte, il n'y a
|
||||
/// pas de filtre d'etiquette separe : taper son nom suffit.
|
||||
///
|
||||
/// AFolder vide = tous les dossiers.
|
||||
function Search(const AQuery: string; const AFolder: string = '';
|
||||
AIncludeTrash: Boolean = False): TArray<Integer>;
|
||||
|
||||
/// Dossiers declares par l'instantane, dans son ordre.
|
||||
property Folders: TArray<string> read FFolders;
|
||||
|
||||
function Count: Integer;
|
||||
function LiveCount: Integer;
|
||||
@@ -66,7 +85,7 @@ type
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Character;
|
||||
System.Character, System.Generics.Defaults;
|
||||
|
||||
{ TVaultEntry }
|
||||
|
||||
@@ -88,6 +107,7 @@ end;
|
||||
procedure TVault.Clear;
|
||||
begin
|
||||
SetLength(FEntries, 0);
|
||||
SetLength(FFolders, 0);
|
||||
FUsername := '';
|
||||
FExportedAt := '';
|
||||
end;
|
||||
@@ -155,6 +175,21 @@ begin
|
||||
FExportedAt := LRoot.GetValue<string>('exported_at',
|
||||
LRoot.GetValue<string>('snapshot_at', ''));
|
||||
|
||||
// Dossiers : l'instantane porte {name, color, icon}, seul le nom sert ici.
|
||||
// Un instantane ancien peut ne pas avoir le tableau du tout.
|
||||
if LRoot.TryGetValue<TJSONArray>('folders', LArr) then
|
||||
begin
|
||||
SetLength(FFolders, LArr.Count);
|
||||
N := 0;
|
||||
for I := 0 to LArr.Count - 1 do
|
||||
begin
|
||||
if not (LArr.Items[I] is TJSONObject) then Continue;
|
||||
FFolders[N] := (LArr.Items[I] as TJSONObject).GetValue<string>('name', '');
|
||||
if FFolders[N] <> '' then Inc(N);
|
||||
end;
|
||||
SetLength(FFolders, N);
|
||||
end;
|
||||
|
||||
if not LRoot.TryGetValue<TJSONArray>('entries', LArr) then
|
||||
raise Exception.Create('Instantane sans tableau "entries"');
|
||||
|
||||
@@ -174,6 +209,9 @@ begin
|
||||
FEntries[N].Totp := LObj.GetValue<string>('totp_secret', '');
|
||||
FEntries[N].Tags := TagsToString(LObj.GetValue('tags'));
|
||||
FEntries[N].Fields := ParseFields(LObj.GetValue('custom_fields'));
|
||||
FEntries[N].Template := LObj.GetValue<string>('template', '');
|
||||
FEntries[N].IconB64 := LObj.GetValue<string>('icon_b64', '');
|
||||
FEntries[N].UpdatedAt := LObj.GetValue<string>('updated_at', '');
|
||||
// deleted arrive en 0/1 depuis nos conteneurs ; absent = vivant.
|
||||
FEntries[N].Deleted := LObj.GetValue<Integer>('deleted', 0) = 1;
|
||||
Inc(N);
|
||||
@@ -206,7 +244,8 @@ begin
|
||||
Result := FEntries[AIndex];
|
||||
end;
|
||||
|
||||
function TVault.Search(const AQuery: string; AIncludeTrash: Boolean): TArray<Integer>;
|
||||
function TVault.Search(const AQuery: string; const AFolder: string;
|
||||
AIncludeTrash: Boolean): TArray<Integer>;
|
||||
var
|
||||
LQ: string;
|
||||
LRes: TList<Integer>;
|
||||
@@ -230,12 +269,21 @@ begin
|
||||
begin
|
||||
E := FEntries[I];
|
||||
if E.Deleted and not AIncludeTrash then Continue;
|
||||
if (AFolder <> '') and not SameText(E.Folder, AFolder) then Continue;
|
||||
if Matches(E) then LRes.Add(I);
|
||||
end;
|
||||
Result := LRes.ToArray;
|
||||
finally
|
||||
LRes.Free;
|
||||
end;
|
||||
|
||||
// Tri sur le nom affiche, insensible a la casse et a l'accentuation faible
|
||||
// de CompareText - suffisant pour une liste qu'on parcourt a l'oeil.
|
||||
TArray.Sort<Integer>(Result, TComparer<Integer>.Construct(
|
||||
function(const A, B: Integer): Integer
|
||||
begin
|
||||
Result := CompareText(FEntries[A].Display, FEntries[B].Display);
|
||||
end));
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user