From e85f46f50128f0df0e060ab27297cce48f1627b7 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:44:52 +0100 Subject: [PATCH] feat(android): copy from the list, framed entries, one radius everywhere The cross moves inside the search field, where it belonged. My first attempt failed with a TLabel, whose HitTest is false by default, and I concluded too quickly that no child of a TEdit can take a touch. A TRectangle takes it. Buttons drop the pill and take the fields' radius: a stadium beside a 14-radius field put two shape vocabularies on one line, and consistency is worth more here than fidelity to the reference. List entries get their own block, like the detail fields - on a gradient a bare line of text has no readable edge. And each row carries User and Pass buttons, so the commonest gesture no longer requires opening the entry. Co-Authored-By: Claude Opus 5 --- android-client/PMA.Theme.pas | 28 +++------ mainApk.pas | 114 +++++++++++++++++++++++------------ 2 files changed, 84 insertions(+), 58 deletions(-) diff --git a/android-client/PMA.Theme.pas b/android-client/PMA.Theme.pas index c21cafd..049cafa 100644 --- a/android-client/PMA.Theme.pas +++ b/android-client/PMA.Theme.pas @@ -43,6 +43,10 @@ const COL_MUTED = TAlphaColor($B3E8F0FA); COL_ON_ACC = TAlphaColor($FFFFFFFF); // le blanc reste lisible sur 30 % + /// Un seul arrondi pour tout ce qui se touche - champs comme boutons. Il + /// doit suivre celui de make-style.ps1, qui l'applique aux champs natifs. + RADIUS = 14; + /// A appeler une fois, avant la creation de la forme. procedure ApplyDarkTheme; @@ -105,10 +109,6 @@ type procedure Up(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single); procedure Leave(Sender: TObject); - /// L'arrondi se recalcule a chaque changement de taille : la hauteur est - /// posee APRES la creation, et un arrondi fixe superieur a la moitie de - /// celle-ci transforme le bouton en ellipse. - procedure Resized(Sender: TObject); end; procedure TPressFeedback.Down(Sender: TObject; Button: TMouseButton; @@ -128,16 +128,6 @@ begin if Sender is TRectangle then TRectangle(Sender).Opacity := 1; end; -procedure TPressFeedback.Resized(Sender: TObject); -var - LR: TRectangle; -begin - if not (Sender is TRectangle) then Exit; - LR := TRectangle(Sender); - LR.XRadius := LR.Height / 2; - LR.YRadius := LR.Height / 2; -end; - function Surface(AParent: TFmxObject): TRectangle; begin Result := TRectangle.Create(AParent); @@ -178,9 +168,11 @@ var begin Result := TRectangle.Create(AParent); Result.Parent := AParent; - // Pilule : l'arrondi vaut exactement la moitie de la hauteur, et se - // recalcule quand elle change (voir TPressFeedback.Resized). Tout ce qui se - // touche est arrondi ainsi dans la reference. + // MEME ARRONDI QUE LES CHAMPS. Un bouton en pilule a cote d'un champ a 14 + // donnait deux vocabulaires de forme sur la meme ligne ; la coherence vaut + // mieux que la fidelite a la reference sur ce point. + Result.XRadius := RADIUS; + Result.YRadius := RADIUS; Result.Cursor := crHandPoint; Result.Stroke.Kind := TBrushKind.None; // La difference entre principal et secondaire tient a l'opacite du blanc, @@ -208,8 +200,6 @@ begin Result.OnMouseDown := LFeedback.Down; Result.OnMouseUp := LFeedback.Up; Result.OnMouseLeave := LFeedback.Leave; - Result.OnResize := LFeedback.Resized; - LFeedback.Resized(Result); end; procedure SetCornerRadius(AButton: TRectangle; AValue: Single); diff --git a/mainApk.pas b/mainApk.pas index 416908c..3a4cc02 100644 --- a/mainApk.pas +++ b/mainApk.pas @@ -185,6 +185,8 @@ type procedure OnSearchChanged(Sender: TObject); procedure OnSearchClear(Sender: TObject); + procedure OnRowCopyUser(Sender: TObject); + procedure OnRowCopyPwd(Sender: TObject); procedure OnFolderTap(Sender: TObject); procedure OnFolderPick(Sender: TObject); procedure PopulateFolders; @@ -395,9 +397,8 @@ end; procedure TForm8.BuildList; var - LBar, LTop, LSearchWrap: TLayout; + LBar, LTop: TLayout; LBtnLock: TRectangle; - LClearMark: TLabel; LGlass: TCircle; LHandle: TLine; begin @@ -452,41 +453,11 @@ begin FBtnFolder.Width := 170; FBtnFolder.Margins.Left := 8; - // La croix vit A COTE du champ, pas dedans. Un enfant place dans un TEdit - // ne recoit pas les touches de facon fiable : le controle natif les capte - // sur toute sa surface. Un conteneur avec un Right et un Client leve en - // plus toute question d'ordre. - LSearchWrap := TLayout.Create(Self); - LSearchWrap.Parent := LTop; - LSearchWrap.Align := TAlignLayout.Client; - - // La place de la croix est RESERVEE en permanence : la montrer et la cacher - // faisait changer la largeur du champ a chaque frappe, et la ligne sautait. - // Seule la croix elle-meme apparait et disparait. - FSearchClear := TRectangle.Create(Self); - FSearchClear.Parent := LSearchWrap; - FSearchClear.Align := TAlignLayout.Right; - FSearchClear.Width := 46; - FSearchClear.Fill.Kind := TBrushKind.None; // invisible, mais cliquable - FSearchClear.Stroke.Kind := TBrushKind.None; - FSearchClear.OnClick := OnSearchClear; - - LClearMark := TLabel.Create(FSearchClear); - LClearMark.Parent := FSearchClear; - LClearMark.Align := TAlignLayout.Client; - LClearMark.HitTest := False; // le clic va au rectangle - LClearMark.Text := 'X'; - LClearMark.TextSettings.HorzAlign := TTextAlign.Center; - LClearMark.TextSettings.VertAlign := TTextAlign.Center; - LClearMark.Visible := False; - Tint(LClearMark, 17, True); - FSearchClearMark := LClearMark; - // Le champ reste natif - il faut un clavier - et son fond vient desormais du // style, recolore a la palette par make-style.ps1. PAS de rectangle derriere // lui : il en resultait deux cadres arrondis emboites. FSearch := TEdit.Create(Self); - FSearch.Parent := LSearchWrap; + FSearch.Parent := LTop; FSearch.Align := TAlignLayout.Client; // Le texte saisi commence apres la loupe. FSearch.Padding.Left := 32; @@ -527,6 +498,29 @@ begin FSearchHint.Opacity := 0.45; FSearchHint.Text := 'Search'; + // Croix DANS le champ, et non plus a cote. Le premier essai avait echoue + // avec un TLabel, dont HitTest vaut faux par defaut ; j'en avais conclu trop + // vite qu'un enfant de TEdit ne recoit jamais les touches. Un TRectangle les + // prend. Sa place est occupee en permanence pour que la largeur du champ ne + // bouge pas ; seule la croix apparait. + FSearchClear := TRectangle.Create(FSearch); + FSearchClear.Parent := FSearch; + FSearchClear.Align := TAlignLayout.Right; + FSearchClear.Width := 46; + FSearchClear.Fill.Kind := TBrushKind.None; // invisible, mais cliquable + FSearchClear.Stroke.Kind := TBrushKind.None; + FSearchClear.OnClick := OnSearchClear; + + FSearchClearMark := TLabel.Create(FSearchClear); + FSearchClearMark.Parent := FSearchClear; + FSearchClearMark.Align := TAlignLayout.Client; + FSearchClearMark.HitTest := False; // le clic va au rectangle + FSearchClearMark.Text := 'X'; + FSearchClearMark.TextSettings.HorzAlign := TTextAlign.Center; + FSearchClearMark.TextSettings.VertAlign := TTextAlign.Center; + FSearchClearMark.Visible := False; + Tint(FSearchClearMark, 17, True); + // Croix d'effacement, montree seulement quand il y a quelque chose a // effacer. Un TLabel et non un bouton : dans un champ, une croix se touche, // elle ne se dessine pas comme une commande. @@ -1363,6 +1357,22 @@ begin SetButtonText(FBtnFolder, FFolderNames[FFolderSel]); end; +/// Copie depuis la liste. Le Tag du bouton porte l'indice dans le coffre, pas +/// dans la liste affichee - la recherche renumerote les lignes, pas le coffre. +procedure TForm8.OnRowCopyUser(Sender: TObject); +begin + Touch; + if (Sender is TRectangle) and (TRectangle(Sender).Tag < FVault.Count) then + CopyToClipboard(FVault.Entry(TRectangle(Sender).Tag).Username, 'Username'); +end; + +procedure TForm8.OnRowCopyPwd(Sender: TObject); +begin + Touch; + if (Sender is TRectangle) and (TRectangle(Sender).Tag < FVault.Count) then + CopyToClipboard(FVault.Entry(TRectangle(Sender).Tag).Password, 'Password'); +end; + procedure TForm8.OnSearchClear(Sender: TObject); begin FSearch.Text := ''; @@ -1441,7 +1451,8 @@ var LFolder: string; LBmp: TBitmap; LImg: TImage; - LBody: TLayout; + LBody, LActions: TLayout; + LCard, LBtnUser, LBtnPwd: TRectangle; LTitle, LSub: TLabel; begin // ponytail: refonte complete de la liste a chaque frappe. Sur un coffre de @@ -1467,8 +1478,33 @@ begin E := FVault.Entry(FFiltered[I]); LItem := TListBoxItem.Create(FList); LItem.Parent := FList; - LItem.Height := 56; + LItem.Height := 76; LItem.Tag := FFiltered[I]; // indice dans le coffre, pas dans la liste + + // Chaque entree dans son bloc, comme les champs du detail : sur un fond + // en degrade, une simple ligne de texte flotte sans limite lisible. + LCard := Surface(LItem); + LCard.Align := TAlignLayout.Client; + LCard.Margins.Rect := RectF(4, 3, 4, 3); + + // Copier sans ouvrir l'entree - le geste le plus frequent. Les deux + // boutons sont poses a la coordonnee dans leur propre conteneur : deux + // controles alignes a droite laisseraient leur ordre au hasard. + LActions := TLayout.Create(LCard); + LActions.Parent := LCard; + LActions.Align := TAlignLayout.Right; + LActions.Width := 150; + LActions.Margins.Rect := RectF(0, 8, 8, 8); + + LBtnUser := FlatButton(LActions, 'User', OnRowCopyUser); + LBtnUser.Align := TAlignLayout.None; + LBtnUser.SetBounds(0, 0, 70, 54); + LBtnUser.Tag := FFiltered[I]; + + LBtnPwd := FlatButton(LActions, 'Pass', OnRowCopyPwd); + LBtnPwd.Align := TAlignLayout.None; + LBtnPwd.SetBounds(76, 0, 70, 54); + 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 : // avec Dark.style, 'listboxitembottomdetail' disparait et emporte @@ -1476,8 +1512,8 @@ begin // alignes Left et Client - il n'y a alors pas d'ordre a deviner. if FIcons.TryGetValue(FFiltered[I], LBmp) then begin - LImg := TImage.Create(LItem); - LImg.Parent := LItem; + LImg := TImage.Create(LCard); + LImg.Parent := LCard; LImg.Align := TAlignLayout.Left; LImg.Width := 40; LImg.Margins.Rect := RectF(4, 8, 8, 8); @@ -1485,8 +1521,8 @@ begin LImg.Bitmap.Assign(LBmp); end; - LBody := TLayout.Create(LItem); - LBody.Parent := LItem; + LBody := TLayout.Create(LCard); + LBody.Parent := LCard; LBody.Align := TAlignLayout.Client; LBody.Margins.Rect := RectF(8, 4, 8, 4); LBody.HitTest := False;