refactor(android): the unlock screen moves to HTML, Skia and the style go
The caret in an Android TEdit does not land where you touch, which makes a long bearer token impossible to correct - the reason the last FMX screen had to go. A field in the engine inherits the system's text handling (caret, selection, handles, paste) for free. With no FMX control left, two things became dead weight and are gone: the vendored Dark.style, which had nothing left to style, and Skia - 25 MB carried solely so FMX labels would shape Arabic. The engine does its own shaping; checked on the device, an Arabic filename still reads correctly. The APK drops from 22.0 to 12.7 MB. Delphi stays the owner of the server settings; the page only edits them and hands them back when a field loses focus, one navigation per changed field rather than one per keystroke. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+119
-4
@@ -58,7 +58,12 @@
|
||||
detail - dont les blocs sont eux-memes du blanc translucide. */
|
||||
background: linear-gradient(var(--bg-top), var(--bg-bot));
|
||||
}
|
||||
#detail { transform: translateX(100%); }
|
||||
/* Trois vues sur un rail. Le deverrouillage est a gauche, la liste au
|
||||
milieu, le detail a droite ; deux classes sur le corps disent ou on est. */
|
||||
#list, #detail { transform: translateX(100%); }
|
||||
body.on-list #unlock { transform: translateX(-100%); }
|
||||
body.on-list #list { transform: translateX(0); }
|
||||
body.on-detail #unlock { transform: translateX(-100%); }
|
||||
body.on-detail #list { transform: translateX(-22%); }
|
||||
body.on-detail #detail { transform: translateX(0); }
|
||||
|
||||
@@ -163,6 +168,26 @@
|
||||
.bar i { display: block; height: 100%; background: var(--text);
|
||||
transition: width 1s linear; }
|
||||
|
||||
/* --- deverrouillage ----------------------------------------------------- */
|
||||
.status { color: var(--muted); margin: 22px 4px 14px; min-height: 20px; }
|
||||
.stack { display: flex; flex-direction: column; gap: 8px; }
|
||||
.pair { display: flex; gap: 8px; }
|
||||
.pair .btn { flex: 1; }
|
||||
.field-in {
|
||||
width: 100%; height: 46px; padding: 0 14px; margin-top: 8px;
|
||||
border: 0; border-radius: var(--radius-s);
|
||||
background: rgba(255,255,255,.10); color: var(--text);
|
||||
font: inherit; outline: none;
|
||||
}
|
||||
.field-in::placeholder { color: var(--muted); }
|
||||
#log {
|
||||
margin: 10px 0 0; padding: 10px 12px; max-height: 34vh; overflow: auto;
|
||||
background: rgba(0,0,0,.20); border-radius: var(--radius-s);
|
||||
font: 11px/1.45 monospace; white-space: pre-wrap; word-break: break-all;
|
||||
-webkit-user-select: text; user-select: text;
|
||||
}
|
||||
[hidden] { display: none !important; }
|
||||
|
||||
.toast {
|
||||
position: fixed; left: 12px; right: 12px; bottom: 14px;
|
||||
background: rgba(10,20,40,.92); color: var(--text);
|
||||
@@ -173,6 +198,31 @@
|
||||
.toast.on { opacity: 1; }
|
||||
</style>
|
||||
|
||||
<div class="screen" id="unlock">
|
||||
<div class="scroll">
|
||||
<div class="status" id="status"></div>
|
||||
<div class="stack">
|
||||
<button class="btn wide main" id="btnOpen">➜ Open vault</button>
|
||||
<button class="btn wide" id="btnSync">↻ Sync</button>
|
||||
<div class="pair">
|
||||
<button class="btn" id="btnCfg">⚙ Server settings</button>
|
||||
<button class="btn" id="btnLog">≡ Log</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cfg" hidden>
|
||||
<input class="field-in" id="url" placeholder="endpoint URL"
|
||||
autocomplete="off" autocorrect="off" autocapitalize="none"
|
||||
spellcheck="false" inputmode="url">
|
||||
<input class="field-in" id="tok" placeholder="bearer token"
|
||||
autocomplete="off" autocorrect="off" autocapitalize="none"
|
||||
spellcheck="false">
|
||||
<input class="field-in" id="pwd" placeholder="sync password"
|
||||
type="password" autocomplete="off">
|
||||
</div>
|
||||
<pre id="log" hidden></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="screen" id="list">
|
||||
<div class="top">
|
||||
<div class="search">
|
||||
@@ -240,7 +290,41 @@ function drain() {
|
||||
}, 60);
|
||||
}
|
||||
|
||||
var entries = [], folders = [], folder = '', query = '', view = 'list';
|
||||
var entries = [], folders = [], folder = '', query = '', view = 'unlock';
|
||||
|
||||
// --- deverrouillage --------------------------------------------------------
|
||||
var elUrl = document.getElementById('url'),
|
||||
elTok = document.getElementById('tok'),
|
||||
elPwd = document.getElementById('pwd');
|
||||
|
||||
// Les trois valeurs remontent A LA PERTE DU FOCUS, pas a chaque touche : c'est
|
||||
// une navigation par champ modifie au lieu d'une par caractere. Delphi les
|
||||
// garde, la page n'est qu'un editeur.
|
||||
function pushConfig() {
|
||||
send('cfg?u=' + encodeURIComponent(elUrl.value.trim())
|
||||
+ '&t=' + encodeURIComponent(elTok.value.trim())
|
||||
+ '&p=' + encodeURIComponent(elPwd.value));
|
||||
}
|
||||
[elUrl, elTok, elPwd].forEach(function (el) {
|
||||
el.addEventListener('change', pushConfig);
|
||||
el.addEventListener('blur', pushConfig);
|
||||
});
|
||||
|
||||
document.getElementById('btnOpen').addEventListener('click', function () {
|
||||
send('openvault');
|
||||
});
|
||||
document.getElementById('btnSync').addEventListener('click', function () {
|
||||
pushConfig(); // ce qui est tape mais pas encore quitte compte aussi
|
||||
send('sync');
|
||||
});
|
||||
document.getElementById('btnCfg').addEventListener('click', function () {
|
||||
var c = document.getElementById('cfg');
|
||||
c.hidden = !c.hidden;
|
||||
});
|
||||
document.getElementById('btnLog').addEventListener('click', function () {
|
||||
var l = document.getElementById('log');
|
||||
l.hidden = !l.hidden;
|
||||
});
|
||||
|
||||
function esc(s) {
|
||||
return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) {
|
||||
@@ -376,6 +460,7 @@ document.getElementById('fields').addEventListener('click', function (ev) {
|
||||
|
||||
function goList() {
|
||||
document.body.classList.remove('on-detail');
|
||||
document.body.classList.add('on-list');
|
||||
view = 'list';
|
||||
// Les valeurs en clair partent avec la vue, pas seulement de l'ecran.
|
||||
detailRows = [];
|
||||
@@ -393,10 +478,40 @@ var PM = {
|
||||
folder = '';
|
||||
q.value = ''; query = '';
|
||||
document.body.classList.remove('searching', 'on-detail');
|
||||
document.body.classList.add('on-list');
|
||||
view = 'list';
|
||||
renderFolders();
|
||||
renderRows();
|
||||
},
|
||||
|
||||
showUnlock: function () {
|
||||
document.body.classList.remove('on-list', 'on-detail', 'searching');
|
||||
view = 'unlock';
|
||||
},
|
||||
setStatus: function (text) {
|
||||
document.getElementById('status').textContent = text;
|
||||
},
|
||||
/// Etat des deux actions : ouvrir n'a de sens qu'avec un coffre local
|
||||
/// lisible, et synchroniser s'appelle autrement quand il n'y en a pas.
|
||||
setActions: function (canOpen, syncLabel, busy) {
|
||||
var o = document.getElementById('btnOpen'), s = document.getElementById('btnSync');
|
||||
o.hidden = !canOpen;
|
||||
s.innerHTML = '↻ ' + syncLabel;
|
||||
o.disabled = busy; s.disabled = busy;
|
||||
o.style.opacity = busy ? .4 : 1;
|
||||
s.style.opacity = busy ? .4 : 1;
|
||||
},
|
||||
setConfig: function (url, tok, pwd, show) {
|
||||
elUrl.value = url; elTok.value = tok; elPwd.value = pwd;
|
||||
// Replier des champs vides revient a cacher la seule chose a faire.
|
||||
document.getElementById('cfg').hidden = !show;
|
||||
},
|
||||
log: function (line) {
|
||||
var l = document.getElementById('log');
|
||||
l.textContent += line + '\n';
|
||||
l.scrollTop = l.scrollHeight;
|
||||
},
|
||||
clearLog: function () { document.getElementById('log').textContent = ''; },
|
||||
setDetail: function (json) { renderDetail(JSON.parse(json)); },
|
||||
setTotp: function (code, left, period) {
|
||||
var v = document.getElementById('totpv');
|
||||
@@ -424,8 +539,8 @@ var PM = {
|
||||
document.getElementById('fields').innerHTML = '';
|
||||
document.getElementById('folders').innerHTML = '';
|
||||
q.value = '';
|
||||
document.body.classList.remove('on-detail', 'searching');
|
||||
view = 'list';
|
||||
document.body.classList.remove('on-list', 'on-detail', 'searching');
|
||||
view = 'unlock';
|
||||
}
|
||||
};
|
||||
window.PM = PM;
|
||||
|
||||
Reference in New Issue
Block a user