feat(android): copy from the list, server params grouped, HTML probe

The card no longer swallows the tap: it covered the whole row and OnItemClick never fired. It paints, nothing more - HitTest false - and its children keep theirs. The two buttons take a Left and a Right rather than fixed coordinates, which put them outside the frame.

The sync password joins the URL and the token: it is a server parameter, and it filled the home screen although it is typed once. The panel opens by itself when one of the three is missing - folding away empty fields hides the only thing left to do.

And an HTML probe, to answer a question rather than argue it: the same list painted by the system engine. It renders well, Arabic included. Two lessons: FMX passes a nil encoding to loadDataWithBaseURL, and this source file carries no byte-order mark, so Delphi reads it as ANSI and an Arabic literal is already broken at compile time - hence the numeric references. Neither is a weakness of the engine, which is what I first suspected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-08-16 23:16:18 +01:00
parent e85f46f501
commit 7218df2f6f
+120 -11
View File
@@ -29,6 +29,7 @@ uses
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Platform,
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Memo, FMX.Memo.Types,
FMX.ScrollBox, FMX.Layouts, FMX.Edit, FMX.ListBox, FMX.Objects,
FMX.WebBrowser,
PMA.Vault;
const
@@ -100,7 +101,11 @@ type
// --- ecran 1 : deverrouillage
FLayUnlock: TLayout;
FUrl, FToken, FPwd: TEdit;
FBtnOpen, FBtnSync, FBtnSettings, FBtnLog: TRectangle;
FBtnOpen, FBtnSync, FBtnSettings, FBtnLog, FBtnHtml: TRectangle;
/// Sondage HTML, cree au premier appui seulement : un moteur de rendu
/// entier ne se paie pas au lancement pour un essai.
FLayWeb: TLayout;
FWeb: TWebBrowser;
FLaySettings: TLayout;
FStatus: TLabel;
FLog: TMemo;
@@ -197,6 +202,8 @@ type
procedure OnBackTap(Sender: TObject);
procedure OnToggleSettings(Sender: TObject);
procedure OnToggleLog(Sender: TObject);
procedure OnHtmlTest(Sender: TObject);
procedure OnHtmlClose(Sender: TObject);
procedure OnClipTimer(Sender: TObject);
procedure OnHardwareBack(Sender: TObject; var Key: Word;
var KeyChar: WideChar; Shift: TShiftState);
@@ -350,10 +357,15 @@ begin
FLaySettings := TLayout.Create(Self);
FLaySettings.Parent := FLayUnlock;
FLaySettings.Align := TAlignLayout.Top;
FLaySettings.Height := 116;
FLaySettings.Visible := False;
FLaySettings.Height := 174;
// Les trois vont ensemble : ce sont les coordonnees du serveur, dont le mot
// de passe fait partie. Il occupait l'ecran d'accueil en permanence alors
// qu'on n'y touche qu'une fois. TopEdit empile du bas vers le haut, d'ou
// l'ordre inverse ici.
FPwd := TopEdit(FLaySettings, 'sync password', TEST_PWD, True);
FToken := TopEdit(FLaySettings, 'bearer token', FIRSTRUN_TOKEN);
FUrl := TopEdit(FLaySettings, 'endpoint URL', FIRSTRUN_URL);
FPwd.Margins.Rect := RectF(16, 8, 16, 0);
FToken.Margins.Rect := RectF(16, 8, 16, 0);
FUrl.Margins.Rect := RectF(16, 8, 16, 0);
// Ce que l'appareil a retenu l'emporte sur les valeurs de premier lancement.
@@ -362,6 +374,9 @@ begin
FUrl.Text := LUrl;
if LToken <> '' then FToken.Text := LToken;
end;
// Replier des champs vides revient a cacher la seule chose a faire.
FLaySettings.Visible := (FUrl.Text.Trim = '') or (FToken.Text.Trim = '')
or (FPwd.Text = '');
// Deux commandes discretes cote a cote : elles ne servent qu'au reglage et
// au diagnostic, et n'ont pas a peser autant que les actions principales.
@@ -379,14 +394,18 @@ begin
FBtnSettings := FlatButton(LRow, 'Server settings', OnToggleSettings);
FBtnSettings.Align := TAlignLayout.Client;
// Sondage, pas une fonction : il repond a une seule question, celle de
// savoir si une page HTML peinte par le moteur du systeme rend mieux que
// ces controles-ci. A retirer des que la reponse est prise.
FBtnHtml := FlatButton(LRow, 'HTML', OnHtmlTest);
FBtnHtml.Align := TAlignLayout.Left;
FBtnHtml.Width := 90;
FBtnHtml.Margins.Right := 8;
FBtnSync := TopButton(FLayUnlock, 'Sync', DoSync);
// Accentue : c'est l'action attendue quand aucun coffre n'est encore la.
FBtnOpen := TopButton(FLayUnlock, 'Open vault', DoOpenLocal, 52, True);
// Pre-rempli depuis le fichier local, jamais depuis ce fichier-ci.
FPwd := TopEdit(FLayUnlock, 'sync password', TEST_PWD, True);
FPwd.Margins.Rect := RectF(16, 16, 16, 0);
FStatus := TLabel.Create(Self);
FStatus.Parent := FLayUnlock;
FStatus.Align := TAlignLayout.Top;
@@ -892,6 +911,89 @@ begin
FLog.Visible := not FLog.Visible;
end;
/// Sondage HTML : la meme liste, les memes blocs, mais peints par le moteur du
/// systeme au lieu de FMX. Ce qu'il faut regarder : le rendu de l'arabe, la
/// fluidite du defilement, et si la page se fond dans le degrade ou flotte
/// dans un rectangle blanc. Contenu factice - rien du coffre ne passe ici.
procedure TForm8.OnHtmlTest(Sender: TObject);
const
PAGE =
// SANS LA DECLARATION DE JEU DE CARACTERES l'arabe sort en charabia : le
// moteur recoit de l'UTF-8 et le lit en latin-1 faute d'indication.
'<!doctype html><meta charset="utf-8">' +
'<meta name="viewport" content="width=device-width,initial-scale=1">' +
'<style>' +
'html,body{margin:0;height:100%;background:linear-gradient(#3E6394,#1B3563);' +
'color:#fff;font:15px system-ui,sans-serif;-webkit-tap-highlight-color:transparent}' +
'.wrap{padding:16px}' +
'h1{font-size:19px;font-weight:600;margin:4px 0 14px}' +
'.s{display:flex;align-items:center;background:rgba(255,255,255,.10);' +
'border-radius:14px;padding:12px 14px;margin-bottom:14px;color:#E8F0FA}' +
'.row{display:flex;align-items:center;gap:10px;background:rgba(255,255,255,.12);' +
'border-radius:20px;padding:12px;margin-bottom:8px}' +
'.av{width:40px;height:40px;border-radius:20px;background:rgba(255,255,255,.18);' +
'display:flex;align-items:center;justify-content:center;font-weight:600}' +
'.t{flex:1;min-width:0}.t b{display:block;font-weight:500}' +
'.t span{color:#B3E8F0FA;font-size:13px}' +
'.b{background:rgba(255,255,255,.18);border:0;color:#fff;border-radius:14px;' +
'padding:10px 12px;font-size:13px}' +
'.b:active{opacity:.62}' +
'</style><div class=wrap><h1>Rendu HTML</h1>' +
'<div class=s>Rechercher</div>' +
'<div class=row><div class=av>G</div><div class=t><b>Gitea</b>' +
'<span>git.example.com</span></div><button class=b>User</button>' +
'<button class=b>Pass</button></div>' +
// L'ARABE EN REFERENCES NUMERIQUES, pas en clair : ce fichier n'a pas de
// marque d'ordre d'octets, Delphi le lit donc en ANSI et abimerait le
// litteral bien avant que le moteur de rendu le voie. Le detail affiche
// de l'arabe correct parce que celui-la vient du JSON, a l'execution.
'<div class=row><div class=av>&#1605;</div>' +
'<div class=t><b>&#1605;&#1585;&#1581;&#1576;&#1575; &#1576;&#1575;&#1604;&#1593;&#1575;&#1604;&#1605;</b>' +
'<span>&#1575;&#1582;&#1578;&#1576;&#1575;&#1585; &#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;</span></div>' +
'<button class=b>User</button><button class=b>Pass</button></div>' +
'<div class=row><div class=av>N</div><div class=t><b>Nextcloud</b>' +
'<span>cloud.example.com</span></div><button class=b>User</button>' +
'<button class=b>Pass</button></div></div>';
var
LBar: TLayout;
LBtn: TRectangle;
begin
if FLayWeb = nil then
begin
FLayWeb := TLayout.Create(Self);
FLayWeb.Parent := Self;
FLayWeb.Align := TAlignLayout.Client;
// La barre est POSEE AVANT le navigateur et alignee en bas : sur Android
// le moteur est une vraie vue du systeme, qui se peint par-dessus tout ce
// que FMX dessine. Elle ne survivrait pas sous lui.
LBar := TLayout.Create(FLayWeb);
LBar.Parent := FLayWeb;
LBar.Align := TAlignLayout.Bottom;
LBar.Height := 60;
LBtn := FlatButton(LBar, 'Close', OnHtmlClose, True);
LBtn.Align := TAlignLayout.Client;
LBtn.Margins.Rect := RectF(16, 4, 16, 10);
FWeb := TWebBrowser.Create(Self);
FWeb.Parent := FLayWeb;
FWeb.Align := TAlignLayout.Client;
// La page est en ASCII pur, d'ou les references numeriques plus haut :
// FMX appelle loadDataWithBaseURL en laissant l'encodage a nil, et une
// URL « data: », qui le porterait, est refusee par Navigate.
// ponytail: sonde. Une vraie page serait servie en HTTP, comme sur le
// poste, et la question de l'encodage ne se poserait plus.
FWeb.LoadFromStrings(PAGE, '');
end;
FLayWeb.Visible := True;
FLayWeb.BringToFront;
end;
procedure TForm8.OnHtmlClose(Sender: TObject);
begin
FLayWeb.Visible := False;
end;
{ ----------------------------------------------------------------------------
Ouverture, synchronisation, verrouillage
---------------------------------------------------------------------------- }
@@ -1486,6 +1588,10 @@ begin
LCard := Surface(LItem);
LCard.Align := TAlignLayout.Client;
LCard.Margins.Rect := RectF(4, 3, 4, 3);
// SANS CECI L'ENTREE N'EST PLUS CLIQUABLE : le bloc couvre toute la
// ligne et absorbe le tap, donc OnItemClick ne part jamais. Il ne sert
// qu'a peindre. Ses enfants, eux, gardent leur propre HitTest.
LCard.HitTest := False;
// Copier sans ouvrir l'entree - le geste le plus frequent. Les deux
// boutons sont poses a la coordonnee dans leur propre conteneur : deux
@@ -1496,14 +1602,17 @@ begin
LActions.Width := 150;
LActions.Margins.Rect := RectF(0, 8, 8, 8);
// Un Left et un Right, jamais deux fois le meme cote : leur ordre serait
// alors laisse au hasard. Ils prennent la hauteur du conteneur, ce qui
// evite de poser des coordonnees qui debordaient du bloc.
LBtnUser := FlatButton(LActions, 'User', OnRowCopyUser);
LBtnUser.Align := TAlignLayout.None;
LBtnUser.SetBounds(0, 0, 70, 54);
LBtnUser.Align := TAlignLayout.Left;
LBtnUser.Width := 70;
LBtnUser.Tag := FFiltered[I];
LBtnPwd := FlatButton(LActions, 'Pass', OnRowCopyPwd);
LBtnPwd.Align := TAlignLayout.None;
LBtnPwd.SetBounds(76, 0, 70, 54);
LBtnPwd.Align := TAlignLayout.Right;
LBtnPwd.Width := 70;
LBtnPwd.Tag := FFiltered[I];
// CONTENU CONSTRUIT A LA MAIN, sans passer par Text/Detail/Bitmap ni par
// un StyleLookup. Ces gabarits n'existent pas dans tous les styles :