fix(android): the page stops sliding, and locking comes back

A swipe with nothing to scroll dragged the whole document, showing the body gradient sliding under the screen gradient - two gradients stacked and no content at all. overflow:hidden does not prevent that in an Android WebView; position:fixed does.

The lock button went missing when the list moved to HTML: it lived in the old FMX bottom bar and had no counterpart in the page. It returns as a padlock next to the search field, no label - it is the one icon universal enough to stand alone.

Icons on the unlock screen too, as code points since this source is read as ANSI. All four are basic-plane and common, so a font without them would show an empty box rather than a wrong glyph; checked on the device, they render.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-08-17 00:18:33 +01:00
parent ec8b5b3410
commit d1d5d29879
2 changed files with 57 additions and 10 deletions
+40 -4
View File
@@ -31,8 +31,15 @@
} }
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { html, body {
margin: 0; height: 100%; overflow: hidden; /* CLOUE AU CADRE. overflow:hidden ne suffit pas dans un WebView Android :
background: linear-gradient(var(--bg-top), var(--bg-bot)) fixed; un glissement qui ne trouve rien a faire defiler fait quand meme
glisser le document entier, et on voit alors le degrade du corps passer
sous celui de l'ecran - deux degrades empiles, sans le moindre contenu.
position:fixed lui retire cette liberte. */
position: fixed; inset: 0;
margin: 0; overflow: hidden;
overscroll-behavior: none;
background: var(--bg-bot);
color: var(--text); color: var(--text);
font: 15px/1.4 system-ui, "Roboto", sans-serif; font: 15px/1.4 system-ui, "Roboto", sans-serif;
/* Le texte de l'interface ne se selectionne pas - un appui maintenu sur /* Le texte de l'interface ne se selectionne pas - un appui maintenu sur
@@ -56,7 +63,7 @@
body.on-detail #detail { transform: translateX(0); } body.on-detail #detail { transform: translateX(0); }
.scroll { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; .scroll { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch;
padding: 0 12px 16px; } overscroll-behavior: contain; padding: 0 12px 16px; }
/* --- barre de recherche ------------------------------------------------ */ /* --- barre de recherche ------------------------------------------------ */
.top { padding: 10px 12px 6px; display: flex; gap: 8px; } .top { padding: 10px 12px 6px; display: flex; gap: 8px; }
@@ -74,6 +81,7 @@
color: var(--muted); font-size: 17px; color: var(--muted); font-size: 17px;
} }
.glass { left: 2px; } .glass { left: 2px; }
.glass svg { width: 19px; height: 19px; }
.clear { right: 2px; display: none; font-size: 20px; } .clear { right: 2px; display: none; font-size: 20px; }
body.searching .glass { display: none; } body.searching .glass { display: none; }
body.searching .clear { display: flex; } body.searching .clear { display: flex; }
@@ -122,6 +130,18 @@
.empty { text-align: center; color: var(--muted); padding: 48px 16px; } .empty { text-align: center; color: var(--muted); padding: 48px 16px; }
/* --- boutons a icone ---------------------------------------------------- */
/* Une icone seule ne dit ce qu'elle fait que si elle est archi-connue. Le
cadenas l'est ; c'est pourquoi il est le seul a se passer d'intitule. */
.ico {
flex: 0 0 46px; width: 46px; height: 46px; border: 0; padding: 0;
border-radius: var(--radius-s); background: rgba(255,255,255,.10);
color: var(--text); display: flex; align-items: center;
justify-content: center;
}
.ico:active { opacity: .62; }
.ico svg { width: 21px; height: 21px; }
/* --- detail ------------------------------------------------------------ */ /* --- detail ------------------------------------------------------------ */
.head { display: flex; align-items: center; gap: 10px; padding: 10px 12px; } .head { display: flex; align-items: center; gap: 10px; padding: 10px 12px; }
.head h1 { flex: 1; margin: 0; font-size: 19px; font-weight: 600; .head h1 { flex: 1; margin: 0; font-size: 19px; font-weight: 600;
@@ -156,11 +176,24 @@
<div class="screen" id="list"> <div class="screen" id="list">
<div class="top"> <div class="top">
<div class="search"> <div class="search">
<div class="glass">&#9906;</div> <div class="glass">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="6.5" /><path d="M16 16l4.5 4.5" />
</svg>
</div>
<input id="q" placeholder="Search" autocomplete="off" autocorrect="off" <input id="q" placeholder="Search" autocomplete="off" autocorrect="off"
autocapitalize="none" spellcheck="false"> autocapitalize="none" spellcheck="false">
<div class="clear" id="qx">&#215;</div> <div class="clear" id="qx">&#215;</div>
</div> </div>
<button class="ico" id="lock" aria-label="Lock vault">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round">
<path d="M7.5 10.5V7a4.5 4.5 0 0 1 9 0v3.5" />
<rect x="4" y="10.5" width="16" height="10.5" rx="3" fill="currentColor"
stroke="none" />
</svg>
</button>
</div> </div>
<div class="folders" id="folders"></div> <div class="folders" id="folders"></div>
<div class="scroll"><div id="rows"></div></div> <div class="scroll"><div id="rows"></div></div>
@@ -286,6 +319,9 @@ q.addEventListener('input', function () {
renderRows(); renderRows();
send('touch'); send('touch');
}); });
document.getElementById('lock').addEventListener('click', function () {
send('lock');
});
document.getElementById('qx').addEventListener('click', function () { document.getElementById('qx').addEventListener('click', function () {
q.value = ''; query = ''; q.value = ''; query = '';
document.body.classList.remove('searching'); document.body.classList.remove('searching');
+17 -6
View File
@@ -187,6 +187,17 @@ uses
{$R *.fmx} {$R *.fmx}
const
// Pictogrammes des boutons, EN POINTS DE CODE : ce fichier n'a pas de marque
// d'ordre d'octets, Delphi le lit en ANSI, et un caractere ecrit tel quel y
// deviendrait deux ou trois glyphes. Tous choisis dans le plan de base et
// parmi les plus courants - une police systeme qui n'en aurait pas
// afficherait un rectangle vide, ce qui se verrait tout de suite.
GLYPH_OPEN = #$279C; // fleche pleine
GLYPH_SYNC = #$21BB; // fleche circulaire
GLYPH_GEAR = #$2699; // engrenage
GLYPH_LOG = #$2261; // trois traits
{ ---------------------------------------------------------------------------- { ----------------------------------------------------------------------------
Construction Construction
---------------------------------------------------------------------------- } ---------------------------------------------------------------------------- }
@@ -334,17 +345,17 @@ begin
LRow.Height := 48; LRow.Height := 48;
LRow.Margins.Rect := RectF(16, 8, 16, 0); LRow.Margins.Rect := RectF(16, 8, 16, 0);
FBtnLog := FlatButton(LRow, 'Log', OnToggleLog); FBtnLog := FlatButton(LRow, GLYPH_LOG + ' Log', OnToggleLog);
FBtnLog.Align := TAlignLayout.Right; FBtnLog.Align := TAlignLayout.Right;
FBtnLog.Width := 120; FBtnLog.Width := 120;
FBtnLog.Margins.Left := 8; FBtnLog.Margins.Left := 8;
FBtnSettings := FlatButton(LRow, 'Server settings', OnToggleSettings); FBtnSettings := FlatButton(LRow, GLYPH_GEAR + ' Server settings', OnToggleSettings);
FBtnSettings.Align := TAlignLayout.Client; FBtnSettings.Align := TAlignLayout.Client;
FBtnSync := TopButton(FLayUnlock, 'Sync', DoSync); FBtnSync := TopButton(FLayUnlock, GLYPH_SYNC + ' Sync', DoSync);
// Accentue : c'est l'action attendue quand aucun coffre n'est encore la. // Accentue : c'est l'action attendue quand aucun coffre n'est encore la.
FBtnOpen := TopButton(FLayUnlock, 'Open vault', DoOpenLocal, 52, True); FBtnOpen := TopButton(FLayUnlock, GLYPH_OPEN + ' Open vault', DoOpenLocal, 52, True);
FStatus := TLabel.Create(Self); FStatus := TLabel.Create(Self);
FStatus.Parent := FLayUnlock; FStatus.Parent := FLayUnlock;
@@ -373,12 +384,12 @@ begin
Status('Vault on this device - protected') Status('Vault on this device - protected')
else else
Status('Vault on this device'); Status('Vault on this device');
SetButtonText(FBtnSync, 'Sync'); SetButtonText(FBtnSync, GLYPH_SYNC + ' Sync');
end end
else else
begin begin
Status('No local vault - first download'); Status('No local vault - first download');
SetButtonText(FBtnSync, 'Download vault'); SetButtonText(FBtnSync, GLYPH_SYNC + ' Download vault');
end; end;
end; end;