Browse Source

Merge conflict: Resolved

Muhammed Ayimen 1 year ago
parent
commit
4cfbe91ad4

+ 6 - 0
assets/custom-icons/_data/custom-icons.json

@@ -95,6 +95,12 @@
       "title": "La Poste",
 	    "slug": "laposte"
     },
+    {
+      "title": "Mastodon",
+      "altNames": ["mstdn", "fediscience", "mathstodon", "fosstodon"],
+      "slug": "mastodon",
+      "hex": "6364FF"
+    },
     {
       "title": "Microsoft"
     },

+ 1 - 0
assets/custom-icons/icons/mastodon.svg

@@ -0,0 +1 @@
+<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Mastodon</title><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/></svg>

+ 1 - 1
assets/simple-icons

@@ -1 +1 @@
-Subproject commit 7e1ad4517598f36ba625741a4dfbc33610d105d8
+Subproject commit 8e7701d6a40462733043f54b3849faf35af70a83

+ 56 - 1
lib/l10n/arb/app_en.arb

@@ -1,5 +1,6 @@
 {
   "account": "Account",
+  "unlock": "Unlock",
   "recoveryKey": "Recovery key",
   "counterAppBarTitle": "Counter",
   "@counterAppBarTitle": {
@@ -85,6 +86,7 @@
   "importSelectJsonFile": "Select JSON file",
   "importEnteEncGuide": "Select the encrypted JSON file exported from ente",
   "importRaivoGuide": "Use the \"Export OTPs to Zip archive\" option in Raivo's Settings.\n\nExtract the zip file and import the JSON file.",
+  "importBitwardenGuide": "Use the \"Export vault\" option within Bitwarden Tools and import the unencrypted JSON file.",
   "importAegisGuide": "Use the \"Export the vault\" option in Aegis's Settings.\n\nIf your vault is encrypted, you will need to enter vault password to decrypt the vault.",
   "exportCodes": "Export codes",
   "importLabel": "Import",
@@ -97,6 +99,7 @@
   "authToViewYourRecoveryKey": "Please authenticate to view your recovery key",
   "authToChangeYourEmail": "Please authenticate to change your email",
   "authToChangeYourPassword": "Please authenticate to change your password",
+  "authToViewSecrets": "Please authenticate to view your secrets",
   "ok": "Ok",
   "cancel": "Cancel",
   "yes": "Yes",
@@ -336,5 +339,57 @@
   "deleteCodeAuthMessage": "Authenticate to delete code",
   "showQRAuthMessage": "Authenticate to show QR code",
   "confirmAccountDeleteTitle": "Confirm account deletion",
-  "confirmAccountDeleteMessage": "This account is linked to other ente apps, if you use any.\n\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted."
+  "confirmAccountDeleteMessage": "This account is linked to other ente apps, if you use any.\n\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
+  "androidBiometricHint": "Verify identity",
+  "@androidBiometricHint": {
+    "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
+  },
+  "androidBiometricNotRecognized": "Not recognized. Try again.",
+  "@androidBiometricNotRecognized": {
+    "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
+  },
+  "androidBiometricSuccess": "Success",
+  "@androidBiometricSuccess": {
+    "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
+  },
+  "androidCancelButton": "Cancel",
+  "@androidCancelButton": {
+    "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
+  },
+  "androidSignInTitle": "Authentication required",
+  "@androidSignInTitle": {
+    "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
+  },
+  "androidBiometricRequiredTitle": "Biometric required",
+  "@androidBiometricRequiredTitle": {
+    "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
+  },
+  "androidDeviceCredentialsRequiredTitle": "Device credentials required",
+  "@androidDeviceCredentialsRequiredTitle": {
+    "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
+  },
+  "androidDeviceCredentialsSetupDescription": "Device credentials required",
+  "@androidDeviceCredentialsSetupDescription": {
+    "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
+  },
+  "goToSettings": "Go to settings",
+  "@goToSettings": {
+    "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
+  },
+  "androidGoToSettingsDescription": "Biometric authentication is not set up on your device. Go to 'Settings > Security' to add biometric authentication.",
+  "@androidGoToSettingsDescription": {
+    "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
+  },
+  "iOSLockOut": "Biometric authentication is disabled. Please lock and unlock your screen to enable it.",
+  "@iOSLockOut": {
+    "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
+  },
+  "iOSGoToSettingsDescription": "Biometric authentication is not set up on your device. Please either enable Touch ID or Face ID on your phone.",
+  "@iOSGoToSettingsDescription": {
+    "description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
+  },
+  "iOSOkButton": "OK",
+  "@iOSOkButton": {
+    "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
+  }
 }

+ 55 - 1
lib/l10n/arb/app_zh.arb

@@ -1,5 +1,6 @@
 {
   "account": "账户",
+  "unlock": "解锁",
   "recoveryKey": "恢复密钥",
   "counterAppBarTitle": "计数器",
   "@counterAppBarTitle": {
@@ -97,6 +98,7 @@
   "authToViewYourRecoveryKey": "请验证以查看您的恢复密钥",
   "authToChangeYourEmail": "请验证以更改您的电子邮件",
   "authToChangeYourPassword": "请验证以更改密码",
+  "authToViewSecrets": "请进行身份验证以查看您的秘密",
   "ok": "好的",
   "cancel": "取消",
   "yes": "是",
@@ -336,5 +338,57 @@
   "deleteCodeAuthMessage": "删除代码需要身份验证",
   "showQRAuthMessage": "显示QR码需要身份验证",
   "confirmAccountDeleteTitle": "确认删除账户",
-  "confirmAccountDeleteMessage": "该账户已链接到其他ente旗下的应用程序(如果您使用任何相关的应用程序)。\n\n您在所有ente旗下应用程序中上传的数据都将被安排删除,并且您的账户将被永久删除。"
+  "confirmAccountDeleteMessage": "该账户已链接到其他ente旗下的应用程序(如果您使用任何相关的应用程序)。\n\n您在所有ente旗下应用程序中上传的数据都将被安排删除,并且您的账户将被永久删除。",
+  "androidBiometricHint": "验证身份",
+  "@androidBiometricHint": {
+    "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
+  },
+  "androidBiometricNotRecognized": "未能识别。再试一次。",
+  "@androidBiometricNotRecognized": {
+    "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
+  },
+  "androidBiometricSuccess": "成功",
+  "@androidBiometricSuccess": {
+    "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
+  },
+  "androidCancelButton": "取消",
+  "@androidCancelButton": {
+    "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
+  },
+  "androidSignInTitle": "需要进行身份验证",
+  "@androidSignInTitle": {
+    "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
+  },
+  "androidBiometricRequiredTitle": "需要进行生物识别认证",
+  "@androidBiometricRequiredTitle": {
+    "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
+  },
+  "androidDeviceCredentialsRequiredTitle": "需要设备凭据",
+  "@androidDeviceCredentialsRequiredTitle": {
+    "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
+  },
+  "androidDeviceCredentialsSetupDescription": "需要设备凭据",
+  "@androidDeviceCredentialsSetupDescription": {
+    "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
+  },
+  "goToSettings": "前往设置",
+  "@goToSettings": {
+    "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
+  },
+  "androidGoToSettingsDescription": "您的设备上未设置生物识别身份验证。转到“设置 > 安全”以添加生物识别身份验证。",
+  "@androidGoToSettingsDescription": {
+    "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
+  },
+  "iOSLockOut": "生物识别身份验证已禁用。请锁定再解锁您的屏幕以启用它。",
+  "@iOSLockOut": {
+    "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
+  },
+  "iOSGoToSettingsDescription": "您的设备上未设置生物识别身份验证。请在您的手机上启用 触控 ID 或 面容 ID。",
+  "@iOSGoToSettingsDescription": {
+    "description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
+  },
+  "iOSOkButton": "好的",
+  "@iOSOkButton": {
+    "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
+  }
 }

+ 2 - 3
lib/services/local_authentication_service.dart

@@ -1,5 +1,3 @@
-
-
 import 'package:ente_auth/core/configuration.dart';
 import 'package:ente_auth/ui/tools/app_lock.dart';
 import 'package:ente_auth/utils/auth_util.dart';
@@ -19,7 +17,7 @@ class LocalAuthenticationService {
   ) async {
     if (await _isLocalAuthSupportedOnDevice()) {
       AppLock.of(context)!.setEnabled(false);
-      final result = await requestAuthentication(infoMessage);
+      final result = await requestAuthentication(context, infoMessage);
       AppLock.of(context)!.setEnabled(
         Configuration.instance.shouldShowLockScreen(),
       );
@@ -43,6 +41,7 @@ class LocalAuthenticationService {
     if (await LocalAuthentication().isDeviceSupported()) {
       AppLock.of(context)!.disable();
       final result = await requestAuthentication(
+        context,
         infoMessage,
       );
       if (result) {

+ 103 - 0
lib/ui/settings/data/import/bitwarden_import.dart

@@ -0,0 +1,103 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:ente_auth/l10n/l10n.dart';
+import 'package:ente_auth/models/code.dart';
+import 'package:ente_auth/services/authenticator_service.dart';
+import 'package:ente_auth/store/code_store.dart';
+import 'package:ente_auth/ui/components/buttons/button_widget.dart';
+import 'package:ente_auth/ui/components/dialog_widget.dart';
+import 'package:ente_auth/ui/components/models/button_type.dart';
+import 'package:ente_auth/ui/settings/data/import/import_success.dart';
+import 'package:ente_auth/utils/dialog_util.dart';
+import 'package:file_picker/file_picker.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+
+Future<void> showBitwardenImportInstruction(BuildContext context) async {
+  final l10n = context.l10n;
+  final result = await showDialogWidget(
+    context: context,
+    title: l10n.importFromApp("Bitwarden"),
+    body: l10n.importBitwardenGuide,
+    buttons: [
+      ButtonWidget(
+        buttonType: ButtonType.primary,
+        labelText: l10n.importSelectJsonFile,
+        isInAlert: true,
+        buttonSize: ButtonSize.large,
+        buttonAction: ButtonAction.first,
+      ),
+      ButtonWidget(
+        buttonType: ButtonType.secondary,
+        labelText: context.l10n.cancel,
+        buttonSize: ButtonSize.large,
+        isInAlert: true,
+        buttonAction: ButtonAction.second,
+      ),
+    ],
+  );
+  if (result?.action != null && result!.action != ButtonAction.cancel) {
+    if (result.action == ButtonAction.first) {
+      await _pickBitwardenJsonFile(context);
+    }
+  }
+}
+
+Future<void> _pickBitwardenJsonFile(BuildContext context) async {
+  final l10n = context.l10n;
+  FilePickerResult? result = await FilePicker.platform.pickFiles();
+  if (result == null) {
+    return;
+  }
+  final progressDialog = createProgressDialog(context, l10n.pleaseWait);
+  await progressDialog.show();
+  try {
+    String path = result.files.single.path!;
+    int? count = await _processBitwardenExportFile(context, path);
+    await progressDialog.hide();
+    if (count != null) {
+      await importSuccessDialog(context, count);
+    }
+  } catch (e) {
+    await progressDialog.hide();
+    await showErrorDialog(
+      context,
+      context.l10n.sorry,
+      context.l10n.importFailureDesc,
+    );
+  }
+}
+
+Future<int?> _processBitwardenExportFile(
+  BuildContext context,
+  String path,
+) async {
+  File file = File(path);
+  final jsonString = await file.readAsString();
+  final data = jsonDecode(jsonString);
+  List<dynamic> jsonArray = data['items'];
+  final parsedCodes = [];
+  for (var item in jsonArray) {
+    if (item['login']['totp'] != null) {
+      var issuer = item['name'];
+      var account = item['login']['username'];
+      var secret = item['login']['totp'];
+
+      parsedCodes.add(
+        Code.fromAccountAndSecret(
+          account,
+          issuer,
+          secret,
+        ),
+      );
+    }
+  }
+
+  for (final code in parsedCodes) {
+    await CodeStore.instance.addCode(code, shouldSync: false);
+  }
+  unawaited(AuthenticatorService.instance.onlineSync());
+  return parsedCodes.length;
+}

+ 3 - 0
lib/ui/settings/data/import/import_service.dart

@@ -1,5 +1,6 @@
 import 'package:ente_auth/ui/settings/data/import/aegis_import.dart';
 import 'package:ente_auth/ui/settings/data/import/analyze_qr_code.dart';
+import 'package:ente_auth/ui/settings/data/import/bitwarden_import.dart';
 import 'package:ente_auth/ui/settings/data/import/encrypted_ente_import.dart';
 import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart';
 import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart';
@@ -40,6 +41,8 @@ class ImportService {
             },
           ),
         );
+      case ImportType.bitwarden:
+        showBitwardenImportInstruction(context);
         break;
     }
   }

+ 5 - 1
lib/ui/settings/data/import_page.dart

@@ -15,7 +15,8 @@ enum ImportType {
   ravio,
   googleAuthenticator,
   aegis,
-  googleAuthenticatorImage
+  googleAuthenticatorImage,
+  bitwarden,
 }
 
 class ImportCodePage extends StatelessWidget {
@@ -26,6 +27,7 @@ class ImportCodePage extends StatelessWidget {
     ImportType.aegis,
     ImportType.googleAuthenticator,
     ImportType.googleAuthenticatorImage,
+    ImportType.bitwarden,
   ];
 
   ImportCodePage({super.key});
@@ -45,6 +47,8 @@ class ImportCodePage extends StatelessWidget {
         return 'Aegis Authenticator';
       case ImportType.googleAuthenticatorImage:
         return 'Google Authenticator (saved image)';
+      case ImportType.bitwarden:
+        return 'Bitwarden';
     }
   }
 

+ 67 - 11
lib/ui/tools/lock_screen.dart

@@ -1,5 +1,4 @@
-
-
+import 'package:ente_auth/l10n/l10n.dart';
 import 'package:ente_auth/ui/common/gradient_button.dart';
 import 'package:ente_auth/ui/tools/app_lock.dart';
 import 'package:ente_auth/utils/auth_util.dart';
@@ -13,13 +12,19 @@ class LockScreen extends StatefulWidget {
   State<LockScreen> createState() => _LockScreenState();
 }
 
-class _LockScreenState extends State<LockScreen> {
+class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
   final _logger = Logger("LockScreen");
+  bool _isShowingLockScreen = false;
+  bool _hasPlacedAppInBackground = false;
+  bool _hasAuthenticationFailed = false;
+  int? lastAuthenticatingTime;
 
   @override
   void initState() {
-    _showLockScreen();
+    _logger.info("initState");
     super.initState();
+    _showLockScreen(source: "initState");
+    WidgetsBinding.instance.addObserver(this);
   }
 
   @override
@@ -34,16 +39,16 @@ class _LockScreenState extends State<LockScreen> {
               alignment: Alignment.center,
               children: [
                 Opacity(
-                  opacity: 0.3,
+                  opacity: 0.2,
                   child: Image.asset('assets/loading_photos_background.png'),
                 ),
                 SizedBox(
-                  width: 142,
+                  width: 180,
                   child: GradientButton(
-                    text: "Unlock",
+                    text: context.l10n.unlock,
                     iconData: Icons.lock_open_outlined,
                     onTap: () async {
-                      _showLockScreen();
+                      _showLockScreen(source: "tapUnlock");
                     },
                   ),
                 ),
@@ -55,16 +60,67 @@ class _LockScreenState extends State<LockScreen> {
     );
   }
 
-  Future<void> _showLockScreen() async {
-    _logger.info("Showing lockscreen");
+  @override
+  void didChangeAppLifecycleState(AppLifecycleState state) {
+    _logger.info(state.toString());
+    if (state == AppLifecycleState.resumed && !_isShowingLockScreen) {
+      // This is triggered either when the lock screen is dismissed or when
+      // the app is brought to foreground
+      _hasPlacedAppInBackground = false;
+      final bool didAuthInLast5Seconds = lastAuthenticatingTime != null &&
+          DateTime.now().millisecondsSinceEpoch - lastAuthenticatingTime! <
+              5000;
+      if (!_hasAuthenticationFailed && !didAuthInLast5Seconds) {
+        // Show the lock screen again only if the app is resuming from the
+        // background, and not when the lock screen was explicitly dismissed
+        Future.delayed(
+          Duration.zero,
+          () => _showLockScreen(source: "lifeCycle"),
+        );
+      } else {
+        _hasAuthenticationFailed = false; // Reset failure state
+      }
+    } else if (state == AppLifecycleState.paused ||
+        state == AppLifecycleState.inactive) {
+      // This is triggered either when the lock screen pops up or when
+      // the app is pushed to background
+      if (!_isShowingLockScreen) {
+        _hasPlacedAppInBackground = true;
+        _hasAuthenticationFailed = false; // reset failure state
+      }
+    }
+  }
+
+  @override
+  void dispose() {
+    WidgetsBinding.instance.removeObserver(this);
+    super.dispose();
+  }
+
+  Future<void> _showLockScreen({String source = ''}) async {
+    final int id = DateTime.now().millisecondsSinceEpoch;
+    _logger.info("Showing lock screen $source $id");
     try {
+      _isShowingLockScreen = true;
       final result = await requestAuthentication(
-        "Please authenticate to view your secrets",
+        context,
+        context.l10n.authToViewSecrets,
       );
+      _logger.finest("LockScreen Result $result $id");
+      _isShowingLockScreen = false;
       if (result) {
+        lastAuthenticatingTime = DateTime.now().millisecondsSinceEpoch;
         AppLock.of(context)!.didUnlock();
+      } else {
+        if (!_hasPlacedAppInBackground) {
+          // Treat this as a failure only if user did not explicitly
+          // put the app in background
+          _hasAuthenticationFailed = true;
+          _logger.info("Authentication failed");
+        }
       }
     } catch (e, s) {
+      _isShowingLockScreen = false;
       _logger.severe(e, s);
     }
   }

+ 8 - 0
lib/ui/utils/icon_utils.dart

@@ -93,6 +93,14 @@ class IconUtils {
           icon["slug"],
           icon["hex"],
         );
+        if (icon["altNames"] != null) {
+          for (final name in icon["altNames"]) {
+            _customIcons[name] = CustomIconData(
+              icon["slug"],
+              icon["hex"],
+            );
+          }
+        }
       }
     } catch (e) {
       Logger("IconUtils").severe("Error loading icons", e);

+ 25 - 13
lib/utils/auth_util.dart

@@ -1,25 +1,37 @@
+import 'package:ente_auth/l10n/l10n.dart';
+import 'package:flutter/cupertino.dart';
 import 'package:local_auth/local_auth.dart';
 import 'package:local_auth_android/local_auth_android.dart';
+import 'package:local_auth_ios/types/auth_messages_ios.dart';
 import 'package:logging/logging.dart';
 
-Future<bool> requestAuthentication(String reason) async {
+Future<bool> requestAuthentication(BuildContext context, String reason) async {
   Logger("AuthUtil").info("Requesting authentication");
   await LocalAuthentication().stopAuthentication();
+  final l10n = context.l10n;
   return await LocalAuthentication().authenticate(
     localizedReason: reason,
     authMessages: [
-      const AndroidAuthMessages(
-        biometricHint: "Verify identity",
-        biometricNotRecognized: "Not recognized, try again",
-        biometricRequiredTitle: "Biometric required",
-        biometricSuccess: "Successfully verified",
-        cancelButton: "Cancel",
-        deviceCredentialsRequiredTitle: "Device credentials required",
-        deviceCredentialsSetupDescription: "Device credentials required",
-        goToSettingsButton: "Go to settings",
-        goToSettingsDescription:
-            "Authentication is not setup on your device, go to Settings > Security to set it up",
-        signInTitle: "Authentication required",
+      AndroidAuthMessages(
+        biometricHint: l10n.androidBiometricHint,
+        biometricNotRecognized: l10n.androidBiometricNotRecognized,
+        biometricRequiredTitle: l10n.androidBiometricRequiredTitle,
+        biometricSuccess: l10n.androidBiometricSuccess,
+        cancelButton: l10n.androidCancelButton,
+        deviceCredentialsRequiredTitle:
+            l10n.androidDeviceCredentialsRequiredTitle,
+        deviceCredentialsSetupDescription:
+            l10n.androidDeviceCredentialsSetupDescription,
+        goToSettingsButton: l10n.goToSettings,
+        goToSettingsDescription: l10n.androidGoToSettingsDescription,
+        signInTitle: l10n.androidSignInTitle,
+      ),
+      IOSAuthMessages(
+        goToSettingsButton: l10n.goToSettings,
+        goToSettingsDescription: l10n.goToSettings,
+        lockOut: l10n.iOSLockOut,
+        // cancelButton default value is "Ok"
+        cancelButton: l10n.iOSOkButton,
       ),
     ],
   );

+ 2 - 2
pubspec.lock

@@ -792,7 +792,7 @@ packages:
     source: hosted
     version: "2.1.7"
   local_auth_android:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: local_auth_android
       sha256: "523dd636ce061ddb296cbc3db410cb8f21efb7d8798f7b9532c8038ce2f8bad5"
@@ -800,7 +800,7 @@ packages:
     source: hosted
     version: "1.0.31"
   local_auth_ios:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: local_auth_ios
       sha256: edc2977c5145492f3451db9507a2f2f284ee4f408950b3e16670838726761940

+ 4 - 1
pubspec.yaml

@@ -1,6 +1,6 @@
 name: ente_auth
 description: ente two-factor authenticator
-version: 2.0.15+215
+version: 2.0.17+217
 publish_to: none
 
 environment:
@@ -54,6 +54,9 @@ dependencies:
   intl: ^0.18.0
   json_annotation: ^4.5.0
   local_auth: ^2.1.7
+
+  local_auth_android: ^1.0.31
+  local_auth_ios: ^1.1.3
   logging: ^1.0.1
   mobile_scanner: ^3.5.2
   modal_bottom_sheet: ^3.0.0-pre