diff --git a/android-client/PMA.WebUI.pas b/android-client/PMA.WebUI.pas
index b0b3726..2bee99a 100644
--- a/android-client/PMA.WebUI.pas
+++ b/android-client/PMA.WebUI.pas
@@ -131,6 +131,8 @@ begin
LObj.AddPair('initial', Initial(E.Display));
LObj.AddPair('hasUser', TJSONBool.Create(E.Username <> ''));
LObj.AddPair('isNote', TJSONBool.Create(E.IsNote));
+ // Un booleen, jamais le secret : le code se calcule chez Delphi a la copie.
+ LObj.AddPair('hasTotp', TJSONBool.Create(E.Totp <> ''));
LEntries.AddElement(LObj);
end;
diff --git a/android-client/web/app.html b/android-client/web/app.html
index a28d7ab..671188b 100644
--- a/android-client/web/app.html
+++ b/android-client/web/app.html
@@ -919,6 +919,10 @@ function renderRows() {
+ (e.sub ? '' + esc(e.sub) + '' : '') + ''
+ (e.hasUser ? '' : '')
+ // LE CODE 2FA A PORTEE DE POUCE, comme l'identifiant et le mot de
+ // passe : il fallait ouvrir l'entree pour le copier.
+ + (e.hasTotp ? '' : '')
+ '';
@@ -1001,6 +1005,7 @@ var ICONS = {
dots: ''
+ '',
check: '',
+ clock: '',
dice: ''
+ ''
+ '',
diff --git a/mainApk.pas b/mainApk.pas
index 158ecc8..d0a1ab2 100644
--- a/mainApk.pas
+++ b/mainApk.pas
@@ -2032,6 +2032,16 @@ begin
begin
E := FVault.Entry(LIdx);
if Param('f') = 'user' then CopyToClipboard(E.Username, 'Username')
+ else if Param('f') = 'rtotp' then
+ begin
+ // Calcule a l'instant de la copie : un code se perime en 30 s.
+ var LLeft, LPeriod: Integer;
+ var LCode := TotpNow(E.Totp, LLeft, LPeriod);
+ if LCode <> '' then
+ CopyToClipboard(LCode, Format('2FA code (%d s left)', [LLeft]))
+ else
+ WebCall('PM.toast("Unreadable 2FA secret")');
+ end
else if E.IsNote then CopyToClipboard(E.Password, 'Content')
else CopyToClipboard(E.Password, 'Password');
end;