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:
@@ -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">⚲</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">×</div>
|
<div class="clear" id="qx">×</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
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user