fix(android): sync went through the door it should have knocked on, and rows kept flipping

Synchroniser rebuilt the vault from the server and displayed it without ever asking for the pattern - the same result as Ouvrir le coffre, one button away, with the protection bypassed. It now passes the same gate, conditioned on a protected cache existing on this device, since that is what expresses the user's choice.

Row order was not merely inverted, it alternated: right on first open, upside down after a lock. FMX's stacking conventions already differ between TLayout and TVertScrollBox; after rows are destroyed and rebuilt they are not even constant. Each row now states its Index explicitly rather than trusting insertion order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-08-15 15:15:56 +01:00
parent 4c6376edbf
commit bbb44a3787
+40 -10
View File
@@ -395,13 +395,21 @@ end;
/// Intitule discret au-dessus d'une valeur. Meme forme pour toutes les /// Intitule discret au-dessus d'une valeur. Meme forme pour toutes les
/// lignes, y compris celle du TOTP. /// lignes, y compris celle du TOTP.
function MakeRow(AParent: TFmxObject; const ACaption: string; /// AOrder fixe la place de la ligne dans la liste d'enfants du parent.
///
/// SANS LUI L'ORDRE N'EST PAS STABLE : observe sur appareil, le formulaire
/// s'affichait dans le bon ordre a la premiere ouverture puis a l'envers apres
/// un verrouillage. Les conventions d'empilement de FMX different deja entre
/// TLayout et TVertScrollBox ; apres destruction et recreation des lignes,
/// elles ne sont meme plus constantes. On ne s'y fie donc pas du tout.
function MakeRow(AParent: TFmxObject; const ACaption: string; AOrder: Integer;
out AValue: TLabel): TLayout; out AValue: TLabel): TLayout;
var var
LCap: TLabel; LCap: TLabel;
begin begin
Result := TLayout.Create(nil); // detruite a la main, voir ClearDetailRows Result := TLayout.Create(nil); // detruite a la main, voir ClearDetailRows
Result.Parent := AParent; Result.Parent := AParent;
Result.Index := AOrder;
Result.Align := TAlignLayout.Top; Result.Align := TAlignLayout.Top;
Result.Height := 62; Result.Height := 62;
Result.Margins.Rect := RectF(12, 10, 12, 0); Result.Margins.Rect := RectF(12, 10, 12, 0);
@@ -546,7 +554,7 @@ end;
procedure TForm8.ShowDetail(AVaultIndex: Integer); procedure TForm8.ShowDetail(AVaultIndex: Integer);
var var
LMeta, LPwdCaption: string; LMeta, LPwdCaption: string;
I: Integer; I, LOrder: Integer;
LRow: TLayout; LRow: TLayout;
LValue: TLabel; LValue: TLabel;
@@ -602,11 +610,13 @@ begin
SetLength(FRowShown, Length(FRows)); SetLength(FRowShown, Length(FRows));
SetLength(FRowCtls, Length(FRows)); SetLength(FRowCtls, Length(FRows));
// Le TOTP EN PREMIER, donc en tete d'ecran : c'est ce qu'on vient chercher // Le TOTP en tete d'ecran : c'est ce qu'on vient chercher sur un telephone.
// sur un telephone. Seule ligne vivante, rafraichie chaque seconde. // Seule ligne vivante, rafraichie chaque seconde.
LOrder := 0;
if FCurrent.Totp <> '' then if FCurrent.Totp <> '' then
begin begin
LRow := MakeRow(FDetBody, 'Code 2FA', FTotpValue); LRow := MakeRow(FDetBody, 'Code 2FA', LOrder, FTotpValue);
Inc(LOrder);
SetLength(FRowCtls, Length(FRowCtls) + 1); SetLength(FRowCtls, Length(FRowCtls) + 1);
FRowCtls[High(FRowCtls)] := LRow; FRowCtls[High(FRowCtls)] := LRow;
FTotpValue.TextSettings.Font.Size := 26; FTotpValue.TextSettings.Font.Size := 26;
@@ -615,13 +625,10 @@ begin
FTotpTimer.Enabled := True; FTotpTimer.Enabled := True;
end; end;
// DANS L'ORDRE, ici : au sein d'un TVertScrollBox les enfants alignes en
// haut s'empilent dans l'ordre de creation - l'inverse d'un TLayout, ou le
// dernier cree se retrouve en tete. Se tromper de convention renvoie le
// formulaire a l'envers, sans rien casser d'autre.
for I := 0 to High(FRows) do for I := 0 to High(FRows) do
begin begin
LRow := MakeRow(FDetBody, FRows[I].Caption, LValue); LRow := MakeRow(FDetBody, FRows[I].Caption, LOrder, LValue);
Inc(LOrder);
FRowCtls[I] := LRow; FRowCtls[I] := LRow;
FRowValues[I] := LValue; FRowValues[I] := LValue;
FRowShown[I] := False; FRowShown[I] := False;
@@ -851,6 +858,29 @@ begin
Exit; Exit;
end; end;
FLog.Lines.Clear; FLog.Lines.Clear;
// MEME PORTE QUE « Ouvrir le coffre ». Sans cela la protection ne tenait
// qu'a un bouton pres : synchroniser reconstruit le coffre depuis le serveur
// et l'affiche, ce qui revient exactement au meme resultat sans passer par
// l'authentification. La condition est la presence d'un cache protege sur
// cet appareil - c'est lui qui exprime le choix de l'utilisateur.
if VaultNeedsAuth and (FVault.Count = 0) then
begin
Status('Authentification...');
AuthenticateThen('Acceder au coffre',
procedure(AOutcome: TBioOutcome)
begin
if AOutcome = boOK then
FetchAndDecrypt(LUrl, LTok, LPwd)
else
begin
Log('Synchronisation refusee : ' + LastBiometricError);
Status('Authentification requise');
end;
end);
Exit;
end;
FetchAndDecrypt(LUrl, LTok, LPwd); FetchAndDecrypt(LUrl, LTok, LPwd);
end; end;