feat: implement biometrics for app lock

This commit is contained in:
Christian Kußowski
2026-06-28 08:46:42 +02:00
parent 8386fa8f14
commit 20ee668b4c
68 changed files with 320 additions and 339 deletions
-5
View File
@@ -1768,11 +1768,6 @@
"placeholders": {}
},
"oneClientLoggedOut": "أُ خرج أحد العملاء الذي تسختدمها",
"pleaseEnter4Digits": "أدخل 4 أرقام أو أتركه فارغ لتعطيل القفل.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"redactMessage": "احذف رسالة",
"@redactMessage": {
"type": "String",
-5
View File
@@ -1807,11 +1807,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Калі ласка, увядзіце 4 лічбы ці пакіньце пустым, каб адключыць код-пароль.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Калі ласка, увядзіце ваш пароль",
"@pleaseEnterYourPassword": {
"type": "String",
-4
View File
@@ -1805,10 +1805,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@changePassword": {
"type": "String",
"placeholders": {}
-4
View File
@@ -1792,10 +1792,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@changePassword": {
"type": "String",
"placeholders": {}
-5
View File
@@ -1004,11 +1004,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Introdueix 4 dígits o deixa-ho buit per desactivar el bloqueig.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Introdueix la teva contrasenya",
"@pleaseEnterYourPassword": {
"type": "String",
-5
View File
@@ -1245,11 +1245,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Chcete-li deaktivovat zámek aplikace, zadejte 4 číslice nebo nechte prázdné.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Zadejte prosím své heslo",
"@pleaseEnterYourPassword": {
"type": "String",
-4
View File
@@ -1839,10 +1839,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@changePassword": {
"type": "String",
"placeholders": {}
+1 -6
View File
@@ -1228,11 +1228,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Bitte 4 Ziffern eingeben oder leer lassen, um die Anwendungssperre zu deaktivieren.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Bitte dein Passwort eingeben",
"@pleaseEnterYourPassword": {
"type": "String",
@@ -2908,4 +2903,4 @@
"couldNotBeSent": "Konnte nicht gesendet werden",
"yesterday": "Gestern",
"resetRecoveryKey": "Wiederherstellungsschlüssel zurücksetzen"
}
}
-4
View File
@@ -1865,10 +1865,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@devices": {
"type": "String",
"placeholders": {}
+10 -6
View File
@@ -1380,11 +1380,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Please enter 4 digits or leave empty to disable app lock.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Please enter your password",
"@pleaseEnterYourPassword": {
"type": "String",
@@ -2888,5 +2883,14 @@
"edited": "(edited)",
"couldNotBeSent": "Could not be sent",
"yesterday": "Yesterday",
"resetRecoveryKey": "Reset recovery key"
"resetRecoveryKey": "Reset recovery key",
"pleaseEnter6Digits": "Please enter 6 digits to enable the app lock.",
"disableAppLock": "Disable app lock",
"disableAppLockAreYouSure": "Are you sure you want to disable the app lock?",
"enableBiometrics": "Enable biometric authentication",
"disableBiometrics": "Disable biometric authentication",
"biometricsDescription": "With biometric authentication you can unlock the app with your face or your fingerprint. The capabilities depend on your device.",
"useAppLock": "Use app lock",
"unlockWithBiometrics": "Unlock with biometric authentication",
"resetPin": "Reset pin"
}
-5
View File
@@ -1207,11 +1207,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Bonvolu enigi 4 ciferojn, aŭ nenion por malŝalti ŝlosadon de la aplikaĵo.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Bonvolu enigi vian pasvorton",
"@pleaseEnterYourPassword": {
"type": "String",
+1 -6
View File
@@ -1820,11 +1820,6 @@
},
"removeFromBundle": "Quitar de este paquete",
"link": "Link",
"pleaseEnter4Digits": "Ingrese 4 dígitos o déjelo en blanco para deshabilitar el bloqueo de la aplicación.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseChooseAPasscode": "Elija un código de acceso",
"@pleaseChooseAPasscode": {
"type": "String",
@@ -2747,4 +2742,4 @@
}
}
}
}
}
+1 -6
View File
@@ -1245,11 +1245,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Rakenduse luku jaoks sisesta 4 numbrit või kui sa sellist võimalust ei soovi kasutada, siis jäta nad tühjaks.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Palun sisesta oma salasõna",
"@pleaseEnterYourPassword": {
"type": "String",
@@ -2900,4 +2895,4 @@
"couldNotBeSent": "Saatmine ei õnnestunud",
"yesterday": "Eile",
"resetRecoveryKey": "Lähtesta taastevõti"
}
}
-5
View File
@@ -1616,11 +1616,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Sartu lau zenbaki edo utzi hutsik aplikazioa babestu nahi ez baduzu.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"replaceRoomWithNewerVersion": "Ordezkatu gela bertsio berriago batekin",
"@replaceRoomWithNewerVersion": {
"type": "String",
-5
View File
@@ -1144,11 +1144,6 @@
}
}
},
"pleaseEnter4Digits": "لطفاً ۴ رقم وارد کنید یا خالی بگذارید تا قفل برنامه غیرفعال شود.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPin": "لطفاً رمز کوتاه خود را وارد کنید",
"@pleaseEnterYourPin": {
"type": "String",
-5
View File
@@ -1244,11 +1244,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Syötä 4 numeroa tai jätä tyhjäksi poistaaksesi sovelluksen lukituksen.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPin": "Syötä PIN-koodisi",
"@pleaseEnterYourPin": {
"type": "String",
-4
View File
@@ -2020,10 +2020,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@unknownEvent": {
"type": "String",
"placeholders": {
-5
View File
@@ -1246,11 +1246,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Veuillez saisir 4 chiffres ou laisser vide pour désactiver le verrouillage de lapplication.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Renseignez votre mot de passe",
"@pleaseEnterYourPassword": {
"type": "String",
+1 -6
View File
@@ -1656,11 +1656,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Iontráil 4 dhigit le do thoil nó fág folamh chun glas aipe a dhíchumasú.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseClickOnLink": "Cliceáil ar an nasc sa ríomhphost agus ansin lean ar aghaidh.",
"@pleaseClickOnLink": {
"type": "String",
@@ -2906,4 +2901,4 @@
"couldNotBeSent": "Níorbh fhéidir a sheoladh",
"yesterday": "Inné",
"resetRecoveryKey": "Athshocraigh eochair aisghabhála"
}
}
+1 -6
View File
@@ -1245,11 +1245,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Escribe 4 díxitos ou deíxao baleiro para non activar o bloqueo.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Escribe o teu contrasinal",
"@pleaseEnterYourPassword": {
"type": "String",
@@ -2900,4 +2895,4 @@
"couldNotBeSent": "Non se puido enviar",
"yesterday": "Onte",
"resetRecoveryKey": "Restablecer clave de recuperación"
}
}
-5
View File
@@ -1101,11 +1101,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "אנא הזן 4 ספרות או השאר ריק כדי להשבית את נעילת האפליקציה.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"online": "מחובר/ת",
"@online": {
"type": "String",
-4
View File
@@ -1814,10 +1814,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@changePassword": {
"type": "String",
"placeholders": {}
-5
View File
@@ -1189,11 +1189,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Upiši 4 znamenke ili ostavi prazno, za deaktiviranje zaključavanja programa.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Upiši svoju lozinku",
"@pleaseEnterYourPassword": {
"type": "String",
-5
View File
@@ -1798,11 +1798,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Írjon be 4 számjegyet, vagy hagyja üresen a zár kikapcsolásához.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"security": "Biztonság",
"@security": {
"type": "String",
-4
View File
@@ -1814,10 +1814,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@changePassword": {
"type": "String",
"placeholders": {}
-5
View File
@@ -1843,11 +1843,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Mohon masukkan 4 digit atau tinggalkan kosong untuk menonaktifkan kunci aplikasi.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPin": "Masukkan pin",
"@pleaseEnterYourPin": {
"type": "String",
-4
View File
@@ -2067,10 +2067,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@unknownEvent": {
"type": "String",
"placeholders": {
-5
View File
@@ -1074,11 +1074,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Inserisci 4 cifre o lascia vuoto per disabilitare il blocco dell'app.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Inserisci la tua password",
"@pleaseEnterYourPassword": {
"type": "String",
+1 -6
View File
@@ -1081,11 +1081,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "アプリのロック用に4桁の数字を入力してください。空欄の場合は無効になります。",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "パスワードを入力してください",
"@pleaseEnterYourPassword": {
"type": "String",
@@ -2323,4 +2318,4 @@
"yesterday": "昨日",
"replyInThread": "スレッドで返信",
"select": "選択"
}
}
-4
View File
@@ -1965,10 +1965,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@unknownEvent": {
"type": "String",
"placeholders": {
-5
View File
@@ -2044,11 +2044,6 @@
}
}
},
"pleaseEnter4Digits": "Ttxil-k·m sekcem 4 n wuṭṭunen neɣ eǧǧ-it d ilem akken ad tsenseḍ asekkeṛ n usnas.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPin": "Ttxil-k·m sekcem tangalt-ik·im pin",
"@pleaseEnterYourPin": {
"type": "String",
-5
View File
@@ -1354,11 +1354,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "4자리 숫자를 입력하거나 앱 잠금을 사용하지 않도록 하려면 비워두세요.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseChooseAPasscode": "비밀번호를 골라주세요",
"@pleaseChooseAPasscode": {
"type": "String",
-5
View File
@@ -286,11 +286,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Įveskite 4 skaitmenis arba palikite tuščią, jei norite išjungti programėlės užraktą.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPin": "Įveskite savo PIN kodą",
"@pleaseEnterYourPin": {
"type": "String",
-5
View File
@@ -2009,11 +2009,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Lūgums ievadīt 4 ciparus vai atstāt tukšu, lai atspējotu lietotnes slēgu.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"newSpace": "Jauna vieta",
"changePassword": "Nomainīt paroli",
"@changePassword": {
-5
View File
@@ -1813,11 +1813,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Skriv inn fire sifre eller la feltet stå tomt for å deaktivere applåsen.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPin": "Oppgi din PIN-kode",
"@pleaseEnterYourPin": {
"type": "String",
-5
View File
@@ -1245,11 +1245,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Voer 4 cijfers in of laat leeg om app-vergrendeling uit te schakelen.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Voer jouw wachtwoord in",
"@pleaseEnterYourPassword": {
"type": "String",
-5
View File
@@ -1459,11 +1459,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Proszę podaj 4 cyfry. By wyłączyć blokadę pozostaw puste.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"removeYourAvatar": "Usuń swoje zdjęcie",
"@removeYourAvatar": {
"type": "String",
-4
View File
@@ -1788,10 +1788,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@changePassword": {
"type": "String",
"placeholders": {}
-5
View File
@@ -1244,11 +1244,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Digite 4 dígitos ou deixe em branco para desativar o bloqueio do app.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Digite sua senha",
"@pleaseEnterYourPassword": {
"type": "String",
-5
View File
@@ -1263,11 +1263,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Por favor, insere 4 dígitos ou deixa vazio para desativar o bloqueio da aplicação.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Por favor, insere a tua palavra-passe",
"@pleaseEnterYourPassword": {
"type": "String",
-5
View File
@@ -1816,11 +1816,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Vă rugăm să introduceți 4 cifre sau puteți să lăsați gol să dezactivați lacătul aplicației.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Vă rugăm să introduceți parola voastră",
"@pleaseEnterYourPassword": {
"type": "String",
+1 -6
View File
@@ -1236,11 +1236,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Введите 4 цифры или оставьте поле пустым, чтобы отключить блокировку приложения.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Пожалуйста, введите ваш пароль",
"@pleaseEnterYourPassword": {
"type": "String",
@@ -2899,4 +2894,4 @@
"placeholders": {}
},
"messageNotDecryptable": "Не удалось расшифровать сообщение"
}
}
-4
View File
@@ -2056,10 +2056,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@appWantsToUseForLogin": {
"type": "String",
"placeholders": {
-4
View File
@@ -1913,10 +1913,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@devices": {
"type": "String",
"placeholders": {}
-4
View File
@@ -1804,10 +1804,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@changePassword": {
"type": "String",
"placeholders": {}
-5
View File
@@ -1160,11 +1160,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Унесите 4 цифре или оставите празно да не закључавате апликацију.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Унесите своју лозинку",
"@pleaseEnterYourPassword": {
"type": "String",
-5
View File
@@ -1027,11 +1027,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Ange 4 siffror eller lämna tom för att inaktivera app-lås.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Ange ditt lösenord",
"@pleaseEnterYourPassword": {
"type": "String",
-5
View File
@@ -1727,11 +1727,6 @@
}
}
},
"pleaseEnter4Digits": "பயன்பாட்டு பூட்டை முடக்க 4 இலக்கங்களை உள்ளிடவும் அல்லது காலியாக விடவும்.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "உங்கள் கடவுச்சொல்லை உள்ளிடவும்",
"@pleaseEnterYourPassword": {
"type": "String",
-4
View File
@@ -1803,10 +1803,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@changePassword": {
"type": "String",
"placeholders": {}
-4
View File
@@ -1853,10 +1853,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@changePassword": {
"type": "String",
"placeholders": {}
-5
View File
@@ -1246,11 +1246,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Lütfen 4 basamak girin veya uygulama kilidini devre dışı bırakmak için boş bırakın.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Lütfen parolanızı girin",
"@pleaseEnterYourPassword": {
"type": "String",
-5
View File
@@ -1392,11 +1392,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Введіть 4 цифри або залиште порожнім, щоб вимкнути блокування застосунку.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"redactMessage": "Редагувати повідомлення",
"@redactMessage": {
"type": "String",
-5
View File
@@ -1371,11 +1371,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "Ilova qulfini oʻchirish uchun 4 ta raqamni kiriting yoki boʻsh qoldiring.",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "Iltimos parolingizni kiriting",
"@pleaseEnterYourPassword": {
"type": "String",
-4
View File
@@ -1829,10 +1829,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@unknownEvent": {
"type": "String",
"placeholders": {
-4
View File
@@ -1872,10 +1872,6 @@
"type": "String",
"placeholders": {}
},
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"@changePassword": {
"type": "String",
"placeholders": {}
+1 -6
View File
@@ -1180,11 +1180,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "请输入 4 位数字或留空以停用应用锁。",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "请输入你的密码",
"@pleaseEnterYourPassword": {
"type": "String",
@@ -2900,4 +2895,4 @@
"couldNotBeSent": "无法发送",
"yesterday": "昨天",
"resetRecoveryKey": "重置恢复密钥"
}
}
-5
View File
@@ -1069,11 +1069,6 @@
"type": "String",
"placeholders": {}
},
"pleaseEnter4Digits": "請輸入4位數字,或留空以停用密碼鎖定。",
"@pleaseEnter4Digits": {
"type": "String",
"placeholders": {}
},
"pleaseEnterYourPassword": "請輸入您的密碼",
"@pleaseEnterYourPassword": {
"type": "String",
+13 -1
View File
@@ -98,11 +98,17 @@ void main(List<String> args) async {
Future<void> startGui(List<Client> clients, SharedPreferences store) async {
// Fetch the pin for the applock if existing for mobile applications.
String? pin;
var useBiometrics = false;
if (PlatformInfos.isMobile) {
try {
pin = await const FlutterSecureStorage().read(
key: 'chat.fluffy.app_lock',
);
useBiometrics =
(await const FlutterSecureStorage().read(
key: 'chat.fluffy.use_biometrics',
)) ==
'true';
} catch (e, s) {
Logs().d('Unable to read PIN from Secure storage', e, s);
}
@@ -113,7 +119,13 @@ Future<void> startGui(List<Client> clients, SharedPreferences store) async {
await firstClient?.roomsLoading;
await firstClient?.accountDataLoading;
runApp(FluffyChatApp(clients: clients, pincode: pin, store: store));
runApp(
FluffyChatApp(
clients: clients,
appLockSettings: (pincode: pin, useBiometrics: useBiometrics),
store: store,
),
);
}
/// Watches the lifecycle changes to start the application when it
@@ -11,6 +11,7 @@ import 'package:fluffychat/widgets/app_lock.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:local_auth/local_auth.dart';
import 'package:matrix/matrix.dart';
import 'settings_security_view.dart';
@@ -23,7 +24,54 @@ class SettingsSecurity extends StatefulWidget {
}
class SettingsSecurityController extends State<SettingsSecurity> {
Future<void> toggleBiometrics() async {
AppLock.of(context).showLockScreen();
final oldState = AppLock.of(context).useBiometrics;
final actionStr = oldState
? L10n.of(context).disableBiometrics
: L10n.of(context).enableBiometrics;
final consent = await showOkCancelAlertDialog(
context: context,
title: actionStr,
message: L10n.of(context).biometricsDescription,
okLabel: actionStr,
cancelLabel: L10n.of(context).cancel,
isDestructive: true,
);
if (consent != OkCancelResult.ok) return;
if (!mounted) return;
if (!oldState) {
final localAuth = LocalAuthentication();
final unlocked = await localAuth.authenticate(localizedReason: actionStr);
if (!unlocked) return;
if (!mounted) return;
}
await AppLock.of(context).changeUseBiometrics(!oldState);
setState(() {});
}
Future<void> disableAppLockAction() async {
AppLock.of(context).showLockScreen();
final consent = await showOkCancelAlertDialog(
context: context,
title: L10n.of(context).disableAppLock,
message: L10n.of(context).disableAppLockAreYouSure,
okLabel: L10n.of(context).disableAppLock,
cancelLabel: L10n.of(context).cancel,
isDestructive: true,
);
if (consent != OkCancelResult.ok) return;
if (!mounted) return;
await AppLock.of(context).changePincode(null);
if (!mounted) return;
setState(() {});
}
Future<void> setAppLockAction() async {
await AppLock.of(context).changeUseBiometrics(false);
if (!mounted) return;
final l10n = L10n.of(context);
if (AppLock.of(context).isActive) {
AppLock.of(context).showLockScreen();
@@ -32,23 +80,24 @@ class SettingsSecurityController extends State<SettingsSecurity> {
useRootNavigator: false,
context: context,
title: l10n.pleaseChooseAPasscode,
message: l10n.pleaseEnter4Digits,
message: l10n.pleaseEnter6Digits,
cancelLabel: l10n.cancel,
validator: (text) {
if (text.isEmpty || (text.length == 4 && int.tryParse(text)! >= 0)) {
if (text.length == 6 && int.tryParse(text)! >= 0) {
return null;
}
return l10n.pleaseEnter4Digits;
return l10n.pleaseEnter6Digits;
},
keyboardType: TextInputType.number,
obscureText: true,
maxLines: 1,
minLines: 1,
maxLength: 4,
maxLength: 6,
);
if (newLock != null) {
if (!mounted) return;
await AppLock.of(context).changePincode(newLock);
setState(() {});
}
}
@@ -8,12 +8,13 @@ import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/beautify_string_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/app_lock.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/widgets/settings_switch_list_tile.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:local_auth/local_auth.dart';
import 'package:matrix/matrix.dart';
import 'settings_security.dart';
@@ -88,15 +89,44 @@ class SettingsSecurityView extends StatelessWidget {
onTap: () =>
context.go('/rooms/settings/security/ignorelist'),
),
if (Matrix.of(context).client.encryption != null) ...{
if (PlatformInfos.isMobile)
ListTile(
trailing: const Icon(Icons.chevron_right_outlined),
title: Text(L10n.of(context).appLock),
subtitle: Text(L10n.of(context).appLockDescription),
onTap: controller.setAppLockAction,
Divider(color: theme.dividerColor),
ListTile(
title: Text(
L10n.of(context).appLock,
style: TextStyle(
color: theme.colorScheme.secondary,
fontWeight: FontWeight.bold,
),
},
),
subtitle: Text(L10n.of(context).appLockDescription),
),
SwitchListTile.adaptive(
value: AppLock.of(context).isActive,
title: Text(L10n.of(context).useAppLock),
onChanged: (active) => active
? controller.setAppLockAction()
: controller.disableAppLockAction(),
),
if (AppLock.of(context).isActive) ...[
FutureBuilder(
future: LocalAuthentication().canCheckBiometrics,
builder: (context, snapshot) {
if (snapshot.data != true) return SizedBox.shrink();
return SwitchListTile.adaptive(
value: AppLock.of(context).useBiometrics,
onChanged: (_) => controller.toggleBiometrics(),
title: Text(L10n.of(context).unlockWithBiometrics),
);
},
),
ListTile(
trailing: const Icon(Icons.lock_reset_outlined),
title: Text(L10n.of(context).resetPin),
onTap: controller.setAppLockAction,
),
],
Divider(color: theme.dividerColor),
ListTile(
title: Text(
+40 -5
View File
@@ -6,17 +6,19 @@
import 'package:fluffychat/widgets/lock_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:local_auth/local_auth.dart';
import 'package:provider/provider.dart';
class AppLockWidget extends StatefulWidget {
const AppLockWidget({
required this.child,
required this.pincode,
required this.useBiometrics,
required this.isLoggedIn,
super.key,
});
final bool isLoggedIn;
final bool isLoggedIn, useBiometrics;
final String? pincode;
final Widget child;
@@ -27,20 +29,25 @@ class AppLockWidget extends StatefulWidget {
class AppLock extends State<AppLockWidget> with WidgetsBindingObserver {
String? _pincode;
bool _isLocked = false;
bool _useBiometrics = false;
bool _triedAutoBiometrics = false;
bool _paused = false;
bool get isActive =>
_pincode != null &&
int.tryParse(_pincode!) != null &&
_pincode!.length == 4 &&
!_paused;
bool get useBiometrics => _useBiometrics;
@override
void initState() {
_useBiometrics = widget.useBiometrics;
_pincode = widget.pincode;
_isLocked = isActive;
super.initState();
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback(_checkLoggedIn);
if (isActive && useBiometrics) unlockWithBiometrics();
}
Future<void> _checkLoggedIn(_) async {
@@ -54,16 +61,28 @@ class AppLock extends State<AppLockWidget> with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (isActive &&
state == AppLifecycleState.hidden &&
!_isLocked &&
isActive) {
if (isActive && state == AppLifecycleState.hidden && !_isLocked) {
showLockScreen();
}
if (_isLocked &&
state == AppLifecycleState.resumed &&
useBiometrics &&
!_triedAutoBiometrics) {
unlockWithBiometrics();
}
}
bool get isLocked => _isLocked;
Future<void> changeUseBiometrics(bool useBiometrics) async {
await const FlutterSecureStorage().write(
key: 'chat.fluffy.use_biometrics',
value: useBiometrics.toString(),
);
_useBiometrics = useBiometrics;
return;
}
Future<void> changePincode(String? pincode) async {
await const FlutterSecureStorage().write(
key: 'chat.fluffy.app_lock',
@@ -73,6 +92,22 @@ class AppLock extends State<AppLockWidget> with WidgetsBindingObserver {
return;
}
Future<bool> unlockWithBiometrics() async {
_triedAutoBiometrics = true;
final localAuth = LocalAuthentication();
final unlocked = await localAuth.authenticate(
localizedReason: 'Please authenticate to unlock the app.',
persistAcrossBackgrounding: true,
);
if (unlocked) {
setState(() {
_isLocked = false;
_triedAutoBiometrics = false;
});
}
return unlocked;
}
bool unlock(String pincode) {
final isCorrect = pincode == _pincode;
if (isCorrect) {
+4 -3
View File
@@ -21,7 +21,7 @@ import 'matrix.dart';
class FluffyChatApp extends StatelessWidget {
final Widget? testWidget;
final List<Client> clients;
final String? pincode;
final ({String? pincode, bool useBiometrics}) appLockSettings;
final SharedPreferences store;
const FluffyChatApp({
@@ -29,7 +29,7 @@ class FluffyChatApp extends StatelessWidget {
this.testWidget,
required this.clients,
required this.store,
this.pincode,
required this.appLockSettings,
});
/// getInitialLink may rereturn the value multiple times if this view is
@@ -77,7 +77,8 @@ class FluffyChatApp extends StatelessWidget {
supportedLocales: L10n.supportedLocales,
routerConfig: router,
builder: (context, child) => AppLockWidget(
pincode: pincode,
pincode: appLockSettings.pincode,
useBiometrics: appLockSettings.useBiometrics,
isLoggedIn: clients.any((client) => client.isLogged()),
// Need a navigator above the Matrix widget for
// displaying dialogs
+104 -38
View File
@@ -10,6 +10,7 @@ import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/widgets/app_lock.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:local_auth/local_auth.dart';
class LockScreen extends StatefulWidget {
const LockScreen({super.key});
@@ -24,6 +25,35 @@ class _LockScreenState extends State<LockScreen> {
bool _inputBlocked = false;
final TextEditingController _textEditingController = TextEditingController();
Future<void> tryUnlockWithBiometrics() async {
setState(() {
_errorText = null;
});
final success = await AppLock.of(context).unlockWithBiometrics();
if (success) {
setState(() {
_inputBlocked = false;
_errorText = null;
});
return;
}
setState(() {
_errorText = L10n.of(context).wrongPinEntered(_coolDownSeconds);
_inputBlocked = true;
});
Future.delayed(Duration(seconds: _coolDownSeconds)).then((_) {
setState(() {
_inputBlocked = false;
_coolDownSeconds *= 2;
_errorText = null;
});
});
_textEditingController.clear();
}
Future<void> tryUnlock(String text) async {
text = text.trim();
setState(() {
@@ -71,49 +101,85 @@ class _LockScreenState extends State<LockScreen> {
title: Text(L10n.of(context).pleaseEnterYourPin),
centerTitle: true,
),
extendBodyBehindAppBar: true,
body: Center(
child: Padding(
body: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: FluffyThemes.columnWidth),
child: ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(16.0),
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth,
children: [
Center(
child: Image.asset(
'assets/logo/mini/logo_mono_mini.png',
width: 128,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
),
),
child: ListView(
shrinkWrap: true,
children: [
Center(
child: Image.asset('assets/info-logo.png', width: 256),
const SizedBox(height: 16),
TextField(
controller: _textEditingController,
textInputAction: TextInputAction.go,
keyboardType: TextInputType.number,
obscureText: true,
autofocus: true,
textAlign: TextAlign.center,
readOnly: _inputBlocked,
onChanged: tryUnlock,
onSubmitted: tryUnlock,
style: const TextStyle(fontSize: 40),
inputFormatters: [LengthLimitingTextInputFormatter(4)],
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(40),
),
TextField(
controller: _textEditingController,
textInputAction: TextInputAction.done,
keyboardType: TextInputType.number,
obscureText: true,
autofocus: true,
textAlign: TextAlign.center,
readOnly: _inputBlocked,
onChanged: tryUnlock,
onSubmitted: tryUnlock,
style: const TextStyle(fontSize: 40),
inputFormatters: [LengthLimitingTextInputFormatter(4)],
decoration: InputDecoration(
errorText: _errorText,
hintText: '****',
suffix: IconButton(
icon: const Icon(Icons.lock_open_outlined),
onPressed: () => tryUnlock(_textEditingController.text),
),
),
errorText: _errorText,
hintText: '✱✱✱✱',
hintStyle: TextStyle(
color: Theme.of(
context,
).colorScheme.surfaceContainerHighest,
),
if (_inputBlocked)
const Padding(
padding: EdgeInsets.all(8.0),
child: LinearProgressIndicator(),
),
],
prefix: AppLock.of(context).useBiometrics
? IconButton(
tooltip: L10n.of(context).unlockWithBiometrics,
icon: FutureBuilder(
future: LocalAuthentication()
.getAvailableBiometrics(),
builder: (context, snapshot) {
final availableBiometrics = snapshot.data ?? [];
if (availableBiometrics.contains(
BiometricType.face,
)) {
return Icon(Icons.face_unlock_outlined);
}
return Icon(Icons.fingerprint_outlined);
},
),
onPressed: _inputBlocked
? null
: tryUnlockWithBiometrics,
)
: IconButton(
tooltip: L10n.of(context).reset,
icon: Icon(Icons.cancel_outlined),
onPressed: _inputBlocked
? null
: _textEditingController.clear,
),
suffix: IconButton(
tooltip: L10n.of(context).unlock,
icon: Icon(Icons.send_outlined),
onPressed: _inputBlocked
? null
: () => tryUnlock(_textEditingController.text),
),
),
),
),
if (_inputBlocked)
const Padding(
padding: EdgeInsets.all(16.0),
child: CircularProgressIndicator.adaptive(),
),
],
),
),
),
@@ -20,6 +20,7 @@ import flutter_web_auth_2
import flutter_webrtc
import geolocator_apple
import just_audio
import local_auth_darwin
import open_file_mac
import package_info_plus
import pasteboard
@@ -51,6 +52,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
+42 -2
View File
@@ -1060,6 +1060,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.0"
local_auth:
dependency: "direct main"
description:
name: local_auth
sha256: ae6f382f638108c6becd134318d7c3f0a93875383a54010f61d7c97ac05d5137
url: "https://pub.dev"
source: hosted
version: "3.0.1"
local_auth_android:
dependency: transitive
description:
name: local_auth_android
sha256: fdb936d59ab945c7af297defd67bd1ed87b11b6db1bc16d01e94677a8f1c38ec
url: "https://pub.dev"
source: hosted
version: "2.0.9"
local_auth_darwin:
dependency: transitive
description:
name: local_auth_darwin
sha256: a8c3d4e17454111f7fd31ff72a31222359f6059f7fe956c2dcfe0f88f49826d4
url: "https://pub.dev"
source: hosted
version: "2.0.3"
local_auth_platform_interface:
dependency: transitive
description:
name: local_auth_platform_interface
sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122
url: "https://pub.dev"
source: hosted
version: "1.1.0"
local_auth_windows:
dependency: transitive
description:
name: local_auth_windows
sha256: be12c5b8ba5e64896983123655c5f67d2484ecfcc95e367952ad6e3bff94cb16
url: "https://pub.dev"
source: hosted
version: "2.0.1"
logger:
dependency: transitive
description:
@@ -2339,5 +2379,5 @@ packages:
source: hosted
version: "2.2.4"
sdks:
dart: ">=3.11.1 <4.0.0"
flutter: ">=3.41.0"
dart: ">=3.12.0 <4.0.0"
flutter: ">=3.44.0"
+1
View File
@@ -53,6 +53,7 @@ dependencies:
intl: any
just_audio: ^0.10.5
latlong2: ^0.9.1
local_auth: ^3.0.1
matrix: #^7.4.0
git:
url: https://github.com/famedly/matrix-dart-sdk.git
@@ -14,6 +14,7 @@
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <geolocator_windows/geolocator_windows.h>
#include <local_auth_windows/local_auth_plugin.h>
#include <pasteboard/pasteboard_plugin.h>
#include <record_windows/record_windows_plugin_c_api.h>
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
@@ -40,6 +41,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
GeolocatorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("GeolocatorWindows"));
LocalAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
PasteboardPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PasteboardPlugin"));
RecordWindowsPluginCApiRegisterWithRegistrar(
+1
View File
@@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
flutter_webrtc
geolocator_windows
local_auth_windows
pasteboard
record_windows
screen_retriever_windows