From e916ea399b3f98a2f6826008673f0ae1c0a74805 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 25 Jun 2024 15:17:58 +0200 Subject: [PATCH 1/2] improved logging (#396) --- lib/utils/logger.dart | 2 +- .../qr_scanner_view_widgets/qr_scanner_widget.dart | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index 53a40e504..0dd1e2d1c 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -266,7 +266,7 @@ Device Parameters $deviceInfo"""; error( 'Uncaught Error: ${isolateError.first.toString()}', error: isolateError.first.toString(), - stackTrace: isolateError.last.toString(), + stackTrace: isolateError.length >= 2 && isolateError[1] != null && isolateError[1].toString() != '' ? isolateError[1] : StackTrace.current, ); }).sendPort, ); diff --git a/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart b/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart index 587a79a62..d26467352 100644 --- a/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart +++ b/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart @@ -25,11 +25,11 @@ Result? _decodeQRCode(BinaryBitmap bitmap) { /// Args: [SendPort] sendPort, [CameraImage] cameraImage, [int] rotation, [double] borderPaddingPercent void _scanQrCodeIsolate(List args) { final SendPort sendPort = args[0] as SendPort; - final CameraImage cameraImage = args[1] as CameraImage; - final int rotation = args[2] as int; - final double borderPaddingPercent = args[3] as double; - try { + final CameraImage cameraImage = args[1] as CameraImage; + final int rotation = args[2] as int; + final double borderPaddingPercent = args[3] as double; + final imgSize = min(cameraImage.width, cameraImage.height); final cropPadding = (imgSize * borderPaddingPercent / 100).round(); final cropHorizontal = (cameraImage.width - imgSize + cropPadding) ~/ 2; @@ -50,14 +50,9 @@ void _scanQrCodeIsolate(List args) { ); var bitmap = BinaryBitmap(GlobalHistogramBinarizer(source)); Result? result = _decodeQRCode(bitmap); - if (result == null) { - sendPort.send(null); - return; - } sendPort.send(result); return; } catch (e) { - Logger.error('Error while scanning QR code: $e, name: _QRScannerWidgetState#_scanQrCode'); sendPort.send(e); return; } From cb848d2ae16f38cae31c28e32bcba36c21e87208 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 25 Jun 2024 15:29:36 +0200 Subject: [PATCH 2/2] 377-recognize-privacyidea-token (#397) * backup for non privacyidea token * recognize privacyidea token * updated some tests * update some tests * Removed unused URI_ORIGIN_NAME and URI_CREATOR constants * recognize privacyidea token * recognize privacyidea token * recognize privacyidea token * recognize privacyidea token --- integration_test/add_tokens_test.dart | 47 ++-- integration_test/copy_to_clipboard_test.dart | 6 +- integration_test/rename_and_delete_test.dart | 30 ++- integration_test/two_step_rollout_test.dart | 6 +- integration_test/views_test.dart | 40 +++- lib/l10n/app_cs.arb | 10 + lib/l10n/app_de.arb | 10 + lib/l10n/app_en.arb | 10 + lib/l10n/app_es.arb | 10 + lib/l10n/app_fr.arb | 10 + lib/l10n/app_localizations.dart | 61 ++--- lib/l10n/app_localizations_cs.dart | 49 +++-- lib/l10n/app_localizations_de.dart | 82 ++++--- lib/l10n/app_localizations_en.dart | 58 +++-- lib/l10n/app_localizations_es.dart | 73 ++++-- lib/l10n/app_localizations_fr.dart | 82 ++++--- lib/l10n/app_localizations_nl.dart | 73 ++++-- lib/l10n/app_localizations_pl.dart | 58 +++-- lib/l10n/app_nl.arb | 10 + lib/l10n/app_pl.arb | 10 + .../enums/token_origin_source_type.dart | 55 ++++- lib/model/token_import/token_origin_data.dart | 51 +++-- .../token_import/token_origin_data.g.dart | 8 +- lib/model/tokens/token.dart | 4 +- .../free_otp_plus_qr_processor.dart | 9 +- .../google_authenticator_qr_processor.dart | 3 +- .../otp_auth_processor.dart | 86 +++++--- .../aegis_import_file_processor.dart | 4 +- ...thenticator_pro_import_file_processor.dart | 2 +- .../free_otp_plus_import_file_processor.dart | 2 +- .../two_fas_import_file_processor.dart | 2 +- lib/state_notifiers/token_notifier.dart | 30 ++- lib/utils/image_converter.dart | 2 +- lib/utils/lock_auth.dart | 9 +- lib/utils/riverpod_providers.dart | 3 - lib/utils/utils.dart | 6 +- .../add_token_manually_view.dart | 2 +- .../main_view_tokens_list.dart | 30 +-- .../edit_day_password_token_action.dart | 116 ++-------- .../default_edit_action.dart | 82 ++----- .../default_edit_action_dialog.dart | 148 +++++++++++++ .../actions/edit_hotp_token_action.dart | 101 +-------- .../actions/edit_push_token_action.dart | 208 +++++++----------- .../actions/edit_totp_token_action.dart | 111 ++-------- .../qr_scanner_widget.dart | 1 + .../dialogs/select_export_type_dialog.dart | 11 +- .../dialogs/select_tokens_dialog.dart | 4 +- ...nable_text_form_field_after_many_taps.dart | 41 ++-- ...ken_origin_source_type_extension_test.dart | 3 +- test/unit_test/model/push_request_test.dart | 1 + .../token_import/token_origin_data_test.dart | 6 +- .../otp_auth_processor_test.dart | 31 +-- 52 files changed, 1078 insertions(+), 829 deletions(-) create mode 100644 lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart diff --git a/integration_test/add_tokens_test.dart b/integration_test/add_tokens_test.dart index 30e224114..dfd84faea 100644 --- a/integration_test/add_tokens_test.dart +++ b/integration_test/add_tokens_test.dart @@ -10,6 +10,8 @@ import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/enums/token_types.dart'; import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/tokens/token.dart'; +import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/state_notifiers/completed_introduction_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_folder_notifier.dart'; @@ -37,18 +39,29 @@ void main() { late final MockIntroductionRepository mockIntroductionRepository; setUp(() { mockSettingsRepository = MockSettingsRepository(); - when(mockSettingsRepository.loadSettings()).thenAnswer((_) async => SettingsState(useSystemLocale: false, localePreference: const Locale('en'))); + when(mockSettingsRepository.loadSettings()) + .thenAnswer((_) async => SettingsState(useSystemLocale: false, localePreference: const Locale('en'), latestVersion: Version.parse('999.999.999'))); when(mockSettingsRepository.saveSettings(any)).thenAnswer((_) async => true); mockTokenRepository = MockTokenRepository(); - when(mockTokenRepository.loadTokens()).thenAnswer((_) async => []); - when(mockTokenRepository.saveOrReplaceTokens(any)).thenAnswer((_) async => []); - when(mockTokenRepository.deleteTokens(any)).thenAnswer((_) async => []); + var tokens = []; + when(mockTokenRepository.loadTokens()).thenAnswer((_) async => tokens); + when(mockTokenRepository.saveOrReplaceToken(any)).thenAnswer((invocation) async { + final arguments = invocation.positionalArguments; + tokens.removeWhere((element) => element.id == (arguments[0] as Token).id); + tokens.add(arguments[0] as Token); + return true; + }); + when(mockTokenRepository.deleteToken(any)).thenAnswer((invocation) async { + final arguments = invocation.positionalArguments; + tokens.removeWhere((element) => element.id == (arguments[0] as Token).id); + return true; + }); mockTokenFolderRepository = MockTokenFolderRepository(); when(mockTokenFolderRepository.loadFolders()).thenAnswer((_) async => []); when(mockTokenFolderRepository.saveReplaceList(any)).thenAnswer((_) async => true); mockIntroductionRepository = MockIntroductionRepository(); - final introductions = {...Introduction.values}..remove(Introduction.introductionScreen); - when(mockIntroductionRepository.loadCompletedIntroductions()).thenAnswer((_) async => IntroductionState(completedIntroductions: introductions)); + when(mockIntroductionRepository.loadCompletedIntroductions()) + .thenAnswer((_) async => const IntroductionState(completedIntroductions: {...Introduction.values})); }); testWidgets( 'Add Tokens Test', @@ -62,9 +75,7 @@ void main() { ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), )); - - await _introToMainView(tester); - expectMainViewIsEmptyAndCorrect(); + await expectMainViewIsEmptyAndCorrect(tester); await _addHotpToken(tester); expect(find.byType(HOTPTokenWidget), findsOneWidget); await _addTotpToken(tester); @@ -89,21 +100,6 @@ void main() { ); } -Future _introToMainView(WidgetTester tester) async { - var finder = find.byType(FloatingActionButton); - await pumpUntilFindNWidgets(tester, finder, 1, const Duration(seconds: 20)); - await tester.tap(finder); - await tester.pump(const Duration(milliseconds: 2000)); - await tester.tap(finder); - await tester.pump(const Duration(milliseconds: 2000)); - await tester.tap(finder); - await tester.pump(const Duration(milliseconds: 2000)); - finder = find.text(AppLocalizationsEn().ok); - await pumpUntilFindNWidgets(tester, finder, 1, const Duration(seconds: 10)); - await tester.tap(finder); - await tester.pump(const Duration(milliseconds: 1000)); -} - Future _addHotpToken(WidgetTester tester) async { await tester.pump(); await tester.tap(find.byIcon(Icons.add_moderator)); @@ -218,7 +214,8 @@ Future _openFolder(WidgetTester tester) async { await tester.pump(); } -void expectMainViewIsEmptyAndCorrect() { +Future expectMainViewIsEmptyAndCorrect(WidgetTester tester) async { + await pumpUntilFindNWidgets(tester, find.byType(FloatingActionButton), 1, const Duration(seconds: 10)); expect(find.byType(FloatingActionButton), findsOneWidget); expect(find.byType(AppBarItem), findsNWidgets(5)); // 4 at BottomNavigationBar and 1 at AppBar expect(find.byType(TokenWidgetBase), findsNothing); diff --git a/integration_test/copy_to_clipboard_test.dart b/integration_test/copy_to_clipboard_test.dart index a020a2c29..cc0fd51d4 100644 --- a/integration_test/copy_to_clipboard_test.dart +++ b/integration_test/copy_to_clipboard_test.dart @@ -9,6 +9,7 @@ import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; +import 'package:privacyidea_authenticator/state_notifiers/completed_introduction_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_notifier.dart'; @@ -40,8 +41,8 @@ void main() { when(mockTokenFolderRepository.loadFolders()).thenAnswer((_) async => []); when(mockTokenFolderRepository.saveReplaceList(any)).thenAnswer((_) async => true); mockIntroductionRepository = MockIntroductionRepository(); - final introductions = {...Introduction.values}..remove(Introduction.introductionScreen); - when(mockIntroductionRepository.loadCompletedIntroductions()).thenAnswer((_) async => IntroductionState(completedIntroductions: introductions)); + when(mockIntroductionRepository.loadCompletedIntroductions()) + .thenAnswer((_) async => const IntroductionState(completedIntroductions: {...Introduction.values})); }); testWidgets('Copy to Clipboard Test', (tester) async { await tester.pumpWidget(TestsAppWrapper( @@ -49,6 +50,7 @@ void main() { settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)), tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository)), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), + introductionProvider.overrideWith((ref) => IntroductionNotifier(repository: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), )); diff --git a/integration_test/rename_and_delete_test.dart b/integration_test/rename_and_delete_test.dart index c33f191bb..e5f2c3140 100644 --- a/integration_test/rename_and_delete_test.dart +++ b/integration_test/rename_and_delete_test.dart @@ -9,6 +9,7 @@ import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; +import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/state_notifiers/completed_introduction_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_folder_notifier.dart'; @@ -35,17 +36,28 @@ void main() { SettingsState(isFirstRun: false, useSystemLocale: false, localePreference: const Locale('en'), latestVersion: Version.parse('999.999.999'))); when(mockSettingsRepository.saveSettings(any)).thenAnswer((_) async => true); mockTokenRepository = MockTokenRepository(); - when(mockTokenRepository.loadTokens()).thenAnswer((_) async => [ - HOTPToken(label: 'test', issuer: 'test', id: 'id', algorithm: Algorithms.SHA256, digits: 6, secret: 'secret', counter: 0), - ]); - when(mockTokenRepository.saveOrReplaceTokens(any)).thenAnswer((_) async => []); - when(mockTokenRepository.deleteTokens(any)).thenAnswer((_) async => []); + var tokens = [ + HOTPToken(label: 'test', issuer: 'test', id: 'id', algorithm: Algorithms.SHA256, digits: 6, secret: 'secret', counter: 0), + ]; + when(mockTokenRepository.loadTokens()).thenAnswer((_) async { + return tokens; + }); + when(mockTokenRepository.saveOrReplaceToken(any)).thenAnswer((invocation) async { + final arguments = invocation.positionalArguments; + tokens.removeWhere((element) => element.id == (arguments[0] as Token).id); + tokens.add(arguments[0] as Token); + return true; + }); + when(mockTokenRepository.deleteToken(tokens.first)).thenAnswer((_) async { + tokens.remove(tokens.first); + return true; + }); mockTokenFolderRepository = MockTokenFolderRepository(); when(mockTokenFolderRepository.loadFolders()).thenAnswer((_) async => []); when(mockTokenFolderRepository.saveReplaceList(any)).thenAnswer((_) async => true); mockIntroductionRepository = MockIntroductionRepository(); - final introductions = {...Introduction.values}..remove(Introduction.introductionScreen); - when(mockIntroductionRepository.loadCompletedIntroductions()).thenAnswer((_) async => IntroductionState(completedIntroductions: introductions)); + when(mockIntroductionRepository.loadCompletedIntroductions()) + .thenAnswer((_) async => const IntroductionState(completedIntroductions: {...Introduction.values})); }); testWidgets('Rename and Delete Token', (tester) async { await tester.pumpWidget(TestsAppWrapper( @@ -74,7 +86,7 @@ Future _renameToken(WidgetTester tester, String newName) async { await tester.tap(find.byType(EditHOTPTokenAction)); await tester.pumpAndSettle(); expect(find.text(AppLocalizationsEn().editToken), findsOneWidget); - expect(find.byType(TextFormField), findsNWidgets(3)); + expect(find.byType(TextFormField), findsNWidgets(4)); await tester.pumpAndSettle(); await tester.enterText(find.byType(TextFormField).first, ''); await tester.pumpAndSettle(); @@ -97,6 +109,6 @@ Future _deleteToken(WidgetTester tester) async { expect(find.text(AppLocalizationsEn().confirmDeletion), findsOneWidget); expect(find.text(AppLocalizationsEn().delete), findsOneWidget); await tester.tap(find.text(AppLocalizationsEn().delete)); - await tester.pumpAndSettle(); + await pumpUntilFindNWidgets(tester, find.byType(HOTPTokenWidget), 0, const Duration(seconds: 2)); expect(find.byType(HOTPTokenWidget), findsNothing); } diff --git a/integration_test/two_step_rollout_test.dart b/integration_test/two_step_rollout_test.dart index bb1a400b4..c24614857 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -7,6 +7,7 @@ import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/states/settings_state.dart'; +import 'package:privacyidea_authenticator/state_notifiers/completed_introduction_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_notifier.dart'; @@ -41,8 +42,8 @@ void main() { when(mockTokenFolderRepository.loadFolders()).thenAnswer((_) async => []); when(mockTokenFolderRepository.saveReplaceList(any)).thenAnswer((_) async => true); mockIntroductionRepository = MockIntroductionRepository(); - final introductions = {...Introduction.values}..remove(Introduction.introductionScreen); - when(mockIntroductionRepository.loadCompletedIntroductions()).thenAnswer((_) async => IntroductionState(completedIntroductions: introductions)); + when(mockIntroductionRepository.loadCompletedIntroductions()) + .thenAnswer((_) async => const IntroductionState(completedIntroductions: {...Introduction.values})); }); testWidgets( '2step rollout test', @@ -52,6 +53,7 @@ void main() { settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)), tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository)), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), + introductionProvider.overrideWith((ref) => IntroductionNotifier(repository: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), )); diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index 35015e40b..5d81b4846 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -9,10 +9,14 @@ import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/tokens/token.dart'; +import 'package:privacyidea_authenticator/state_notifiers/completed_introduction_notifier.dart'; +import 'package:privacyidea_authenticator/state_notifiers/push_request_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; +import 'package:privacyidea_authenticator/utils/push_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; import 'package:privacyidea_authenticator/model/version.dart'; @@ -36,9 +40,19 @@ void main() { SettingsState(isFirstRun: false, useSystemLocale: false, localePreference: const Locale('en'), latestVersion: Version.parse('999.999.999'))); when(mockSettingsRepository.saveSettings(any)).thenAnswer((_) async => true); mockTokenRepository = MockTokenRepository(); - when(mockTokenRepository.loadTokens()).thenAnswer((_) async => []); - when(mockTokenRepository.saveOrReplaceTokens(any)).thenAnswer((_) async => []); - when(mockTokenRepository.deleteTokens(any)).thenAnswer((_) async => []); + var tokens = []; + when(mockTokenRepository.loadTokens()).thenAnswer((_) async => tokens); + when(mockTokenRepository.saveOrReplaceToken(any)).thenAnswer((invocation) async { + final arguments = invocation.positionalArguments; + tokens.removeWhere((element) => element.id == (arguments[0] as Token).id); + tokens.add(arguments[0] as Token); + return true; + }); + when(mockTokenRepository.deleteToken(any)).thenAnswer((invocation) async { + final arguments = invocation.positionalArguments; + tokens.removeWhere((element) => element.id == (arguments[0] as Token).id); + return true; + }); mockTokenFolderRepository = MockTokenFolderRepository(); when(mockTokenFolderRepository.loadFolders()).thenAnswer((_) async => []); when(mockTokenFolderRepository.saveReplaceList(any)).thenAnswer((_) async => true); @@ -56,8 +70,8 @@ void main() { sslVerify: anyNamed('sslVerify'), )).thenAnswer((_) => Future.value(Response('{"detail": {"public_key": "publicKey"}}', 200))); mockIntroductionRepository = MockIntroductionRepository(); - final introductions = {...Introduction.values}..remove(Introduction.introductionScreen); - when(mockIntroductionRepository.loadCompletedIntroductions()).thenAnswer((_) async => IntroductionState(completedIntroductions: introductions)); + when(mockIntroductionRepository.loadCompletedIntroductions()) + .thenAnswer((_) async => const IntroductionState(completedIntroductions: {...Introduction.values})); }); testWidgets('Views Test', (tester) async { @@ -70,7 +84,19 @@ void main() { firebaseUtils: mockFirebaseUtils, ioClient: mockIOClient, )), + pushRequestProvider.overrideWith( + (ref) => PushRequestNotifier( + rsaUtils: mockRsaUtils, + pushProvider: PushProvider( + rsaUtils: mockRsaUtils, + ioClient: mockIOClient, + firebaseUtils: mockFirebaseUtils, + ), + ioClient: mockIOClient, + ), + ), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), + introductionProvider.overrideWith((ref) => IntroductionNotifier(repository: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), )); @@ -108,10 +134,10 @@ Future _settingsViewTest(WidgetTester tester) async { expect(find.text(AppLocalizationsEn().theme), findsOneWidget); expect(find.text(AppLocalizationsEn().language), findsOneWidget); expect(find.text(AppLocalizationsEn().errorLogTitle), findsOneWidget); - expect(find.byType(SettingsGroup), findsNWidgets(5)); + expect(find.byType(SettingsGroup), findsNWidgets(6)); globalRef!.read(tokenProvider.notifier).handleQrCode( 'otpauth://pipush/label?url=http%3A%2F%2Fwww.example.com&ttl=10&issuer=issuer&enrollment_credential=enrollmentCredentials&v=1&serial=serial&serial=serial&sslverify=0'); await pumpUntilFindNWidgets(tester, find.text(AppLocalizationsEn().pushToken), 1, const Duration(minutes: 5)); expect(find.text(AppLocalizationsEn().pushToken), findsOneWidget); - expect(find.byType(SettingsGroup), findsNWidgets(5)); + expect(find.byType(SettingsGroup), findsNWidgets(6)); } diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index 620cfb3f4..b50d07b0d 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -636,6 +636,16 @@ "oneMore": "Ještě jeden", "done": "Hotovo", "confirmPassword": "Potvrďte heslo", + "exampleUrl": "Zadejte prosím platnou adresu URL, například: \"https://example.com/\"", + "pushEndpointUrl": "URL koncového bodu push", + "mustNotBeEmpty": "{field} nesmí být prázdné", + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, "sendPushRequestResponseFailed": "Odpověď se nepodařilo odeslat.", "@sendPushRequestResponseFailed": { "description": "Error message when the response to a push request could not be sent." diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 6d9010d09..b61ccf68e 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -615,6 +615,16 @@ "oneMore": "Noch eins", "done": "Fertig", "confirmPassword": "Passwort bestätigen", + "exampleUrl": "Bitte geben Sie eine gültige URL ein wie: \"https://example.com\"", + "pushEndpointUrl": "Push-Endpunkt URL", + "mustNotBeEmpty": "{field} darf nicht leer sein", + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, "sendPushRequestResponseFailed": "Senden der Antwort fehlgeschlagen.", "@sendPushRequestResponseFailed": { "description": "Error message when the response to a push request could not be sent." diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 649e7d69b..42b307dc0 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -658,6 +658,16 @@ } } }, + "pushEndpointUrl": "Push endpoint URL", + "exampleUrl": "Please enter a valid URL like: \"https://example.com/\"", + "mustNotBeEmpty": "{field} must not be empty", + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, "sendPushRequestResponseFailed": "Failed to send the response.", "@sendPushRequestResponseFailed": { "description": "Error message when the response to a push request could not be sent." diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 639a39492..4fce9bbed 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -632,6 +632,16 @@ "oneMore": "Uno más", "done": "Hecho", "confirmPassword": "Confirmar contraseña", + "exampleUrl": "Por favor, introduzca una URL válida como: \"https://example.com/\"", + "pushEndpointUrl": "URL del punto final push", + "mustNotBeEmpty": "{field} no debe estar vacío", + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, "sendPushRequestResponseFailed": "No se ha podido enviar la respuesta.", "@sendPushRequestResponseFailed": { "description": "Error message when the response to a push request could not be sent." diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 8768c3253..72592f1af 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -637,6 +637,16 @@ "oneMore": "Encore un", "done": "Terminé", "confirmPassword": "Confirmer le mot de passe", + "exampleUrl": "Veuillez saisir une URL valide comme : \"https://example.com/\"", + "pushEndpointUrl": "URL de l'endpoint Push", + "mustNotBeEmpty": "{field} ne doit pas être vide", + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, "sendPushRequestResponseFailed": "Échec de l'envoi de la réponse.", "@sendPushRequestResponseFailed": { "description": "Error message when the response to a push request could not be sent." diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 3f4f45888..5299ec91d 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -93,15 +93,7 @@ abstract class AppLocalizations { ]; /// A list of this localizations delegate's supported locales. - static const List supportedLocales = [ - Locale('cs'), - Locale('de'), - Locale('en'), - Locale('es'), - Locale('fr'), - Locale('nl'), - Locale('pl') - ]; + static const List supportedLocales = [Locale('cs'), Locale('de'), Locale('en'), Locale('es'), Locale('fr'), Locale('nl'), Locale('pl')]; /// No description provided for @patchNotesNewFeatures. /// @@ -1669,11 +1661,23 @@ abstract class AppLocalizations { /// **'The {name} [{value}] is not supported by this version of the app.'** String unsupported(Object name, Object value); - /// Error message when the response to a push request could not be sent. + /// No description provided for @pushEndpointUrl. /// /// In en, this message translates to: - /// **'Failed to send the response.'** - String get sendPushRequestResponseFailed; + /// **'Push endpoint URL'** + String get pushEndpointUrl; + + /// No description provided for @exampleUrl. + /// + /// In en, this message translates to: + /// **'Please enter a valid URL like: \"https://example.com/\"'** + String get exampleUrl; + + /// No description provided for @mustNotBeEmpty. + /// + /// In en, this message translates to: + /// **'{field} must not be empty'** + String mustNotBeEmpty(Object field); } class _AppLocalizationsDelegate extends LocalizationsDelegate { @@ -1692,23 +1696,26 @@ class _AppLocalizationsDelegate extends LocalizationsDelegate } AppLocalizations lookupAppLocalizations(Locale locale) { - - // Lookup logic when only language code is specified. switch (locale.languageCode) { - case 'cs': return AppLocalizationsCs(); - case 'de': return AppLocalizationsDe(); - case 'en': return AppLocalizationsEn(); - case 'es': return AppLocalizationsEs(); - case 'fr': return AppLocalizationsFr(); - case 'nl': return AppLocalizationsNl(); - case 'pl': return AppLocalizationsPl(); + case 'cs': + return AppLocalizationsCs(); + case 'de': + return AppLocalizationsDe(); + case 'en': + return AppLocalizationsEn(); + case 'es': + return AppLocalizationsEs(); + case 'fr': + return AppLocalizationsFr(); + case 'nl': + return AppLocalizationsNl(); + case 'pl': + return AppLocalizationsPl(); } - throw FlutterError( - 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' - 'an issue with the localizations generation tool. Please file an issue ' - 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.' - ); + throw FlutterError('AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.'); } diff --git a/lib/l10n/app_localizations_cs.dart b/lib/l10n/app_localizations_cs.dart index 301116cf4..14d1ab4a1 100644 --- a/lib/l10n/app_localizations_cs.dart +++ b/lib/l10n/app_localizations_cs.dart @@ -22,7 +22,8 @@ class AppLocalizationsCs extends AppLocalizations { String get patchNotesV4_3_1Improvement1 => 'Skener QR kódů byl vylepšen.'; @override - String get patchNotesV4_3_0NewFeatures1 => 'Přidána podpora pro import tokenů z Google, Aegis a 2FAS Authenticator. Další zdroje importu budou přidány v budoucnu.'; + String get patchNotesV4_3_0NewFeatures1 => + 'Přidána podpora pro import tokenů z Google, Aegis a 2FAS Authenticator. Další zdroje importu budou přidány v budoucnu.'; @override String get patchNotesV4_3_0NewFeatures2 => 'Do nastavení byla přidána možnost zpětné vazby.'; @@ -108,7 +109,8 @@ class AppLocalizationsCs extends AppLocalizations { } @override - String get confirmTokenDeletionHint => 'Pokud tento token odstraníte, nebude již možné se přihlásit.\nProsím, ujistěte se, že se můžete přihlásit k přidruženému účtu bez tohoto tokenu.'; + String get confirmTokenDeletionHint => + 'Pokud tento token odstraníte, nebude již možné se přihlásit.\nProsím, ujistěte se, že se můžete přihlásit k přidruženému účtu bez tohoto tokenu.'; @override String get confirmFolderDeletionHint => 'Odstranění složky nemá žádný vliv na tokeny v ní.\nTokeny jsou přesunuty do hlavního seznamu.'; @@ -329,7 +331,8 @@ class AppLocalizationsCs extends AppLocalizations { String get send => 'Odeslat'; @override - String get sendErrorLogDescription => 'Vytvoří se připravený e-mail.\nObsahuje informace o aplikaci, chybě a zařízení.\nPřed odesláním můžete e-mail upravit.\nZde se můžete podívat, jak informace používáme:'; + String get sendErrorLogDescription => + 'Vytvoří se připravený e-mail.\nObsahuje informace o aplikaci, chybě a zařízení.\nPřed odesláním můžete e-mail upravit.\nZde se můžete podívat, jak informace používáme:'; @override String get showPrivacyPolicy => 'Zobrazit zásady ochrany osobních údajů'; @@ -356,7 +359,8 @@ class AppLocalizationsCs extends AppLocalizations { String get open => 'Otevřít'; @override - String get sendErrorDialogBody => 'V aplikaci se vyskytla neznámá chyba. Informace uvedené níže mohou být odeslány vývojářům e-mailem pro vyřešení chyby v budoucnu.'; + String get sendErrorDialogBody => + 'V aplikaci se vyskytla neznámá chyba. Informace uvedené níže mohou být odeslány vývojářům e-mailem pro vyřešení chyby v budoucnu.'; @override String get noFbToken => 'Není k dispozici žádný token Firebase.'; @@ -489,7 +493,8 @@ class AppLocalizationsCs extends AppLocalizations { String get decryptErrorTitle => 'Chyba dešifrování'; @override - String get decryptErrorContent => 'Bohužel se aplikaci nepodařilo dešifrovat vaše tokeny. To znamená, že šifrovací klíč je poškozen. Můžete to zkusit znovu nebo odstranit data aplikace, čímž by došlo k odstranění tokenů v aplikaci.'; + String get decryptErrorContent => + 'Bohužel se aplikaci nepodařilo dešifrovat vaše tokeny. To znamená, že šifrovací klíč je poškozen. Můžete to zkusit znovu nebo odstranit data aplikace, čímž by došlo k odstranění tokenů v aplikaci.'; @override String get decryptErrorButtonDelete => 'Odstranit'; @@ -551,7 +556,8 @@ class AppLocalizationsCs extends AppLocalizations { } @override - String get legacySigningErrorMessage => 'Token byl vytvořen v zastaralé verzi aplikace, což může vést k problémům při jeho používání.\nPokud problém přetrvává, doporučujeme vytvořit nový push token!'; + String get legacySigningErrorMessage => + 'Token byl vytvořen v zastaralé verzi aplikace, což může vést k problémům při jeho používání.\nPokud problém přetrvává, doporučujeme vytvořit nový push token!'; @override String get selectImportSource => 'Vyberte zdroj importu'; @@ -661,7 +667,8 @@ class AppLocalizationsCs extends AppLocalizations { String get importHint2FAS => 'Vyberte zálohu 2FAS.\nPokud nemáte zálohu, vytvořte ji v aplikaci 2FAS. Doporučujeme použít heslo.'; @override - String get importHintAegisBackupFile => 'Vyberte svůj export Aegis (.JSON).\nPokud nemáte export, vytvořte si jej prostřednictvím nabídky nastavení v aplikaci Aegis. Doporučujeme použít heslo.'; + String get importHintAegisBackupFile => + 'Vyberte svůj export Aegis (.JSON).\nPokud nemáte export, vytvořte si jej prostřednictvím nabídky nastavení v aplikaci Aegis. Doporučujeme použít heslo.'; @override String get importHintAegisQrScan => 'Naskenujte QR kód, který obdržíte při přenosu záznamů z aplikace Aegis.'; @@ -673,16 +680,19 @@ class AppLocalizationsCs extends AppLocalizations { String get importHintGoogleQrScan => 'Naskenujte QR kód, který obdržíte při exportu účtů z Google Authenticator.'; @override - String get importHintGoogleQrFile => 'Vyberte obrazový soubor s QR kódem, který obdržíte při exportu účtů z Google Authenticator.\n!! Upozorňujeme, že není bezpečné ukládat QR kód do zařízení, protože tokeny nejsou šifrovány !!'; + String get importHintGoogleQrFile => + 'Vyberte obrazový soubor s QR kódem, který obdržíte při exportu účtů z Google Authenticator.\n!! Upozorňujeme, že není bezpečné ukládat QR kód do zařízení, protože tokeny nejsou šifrovány !!'; @override - String get importHintAuthenticatorProFile => 'Chcete-li vytvořit zálohu aplikace Authenticator Pro, přejděte do nastavení a klepněte na položku \"Automatické zálohování\". Vyberte umístění úložiště a nastavte heslo. Poté stiskněte \"Zálohovat nyní\" a exportujte tokeny.'; + String get importHintAuthenticatorProFile => + 'Chcete-li vytvořit zálohu aplikace Authenticator Pro, přejděte do nastavení a klepněte na položku \"Automatické zálohování\". Vyberte umístění úložiště a nastavte heslo. Poté stiskněte \"Zálohovat nyní\" a exportujte tokeny.'; @override String get importHintFreeOtpPlusQrScan => 'Naskenujte QR kód, který obdržíte po stisknutí tří teček na dlaždici tokenu, a vyberte možnost \"Sdílet QR kód\".'; @override - String get importHintFreeOtpPlusFile => 'Chcete-li vytvořit zálohu aplikace FreeOTP+, klepněte na tři tečky v pravém horním rohu a vyberte možnost \"Exportovat\". Můžete si vybrat mezi formátem JSON a URI. Zálohu doporučujeme po importu odstranit, protože není šifrovaná.'; + String get importHintFreeOtpPlusFile => + 'Chcete-li vytvořit zálohu aplikace FreeOTP+, klepněte na tři tečky v pravém horním rohu a vyberte možnost \"Exportovat\". Můžete si vybrat mezi formátem JSON a URI. Zálohu doporučujeme po importu odstranit, protože není šifrovaná.'; @override String get qrFileDecodeError => 'Z vybraného obrázku nebylo možné dekódovat QR kód, použijte prosím místo toho skener QR kódů.'; @@ -700,7 +710,8 @@ class AppLocalizationsCs extends AppLocalizations { String get feedbackDescription => 'Pokud máte nějaké dotazy, návrhy nebo problémy, dejte nám prosím vědět.'; @override - String get feedbackHint => 'Otevře se připravený e-mail, který nám můžete zaslat. V případě potřeby budou doplněny informace o vašem zařízení a verzi aplikace. Před odesláním můžete e-mail zkontrolovat a upravit.'; + String get feedbackHint => + 'Otevře se připravený e-mail, který nám můžete zaslat. V případě potřeby budou doplněny informace o vašem zařízení a verzi aplikace. Před odesláním můžete e-mail zkontrolovat a upravit.'; @override String get feedbackPrivacyPolicy1 => 'Odesláním zpětné vazby souhlasíte s našimi '; @@ -730,7 +741,8 @@ class AppLocalizationsCs extends AppLocalizations { String get noMailAppTitle => 'Není nainstalována žádná e-mailová aplikace'; @override - String get noMailAppDescription => 'There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.'; + String get noMailAppDescription => + 'There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.'; @override String get authenticationRequest => 'Žádost o ověření'; @@ -746,7 +758,8 @@ class AppLocalizationsCs extends AppLocalizations { } @override - String get pleaseSyncManuallyWhenNetworkIsAvailable => 'Synchronizujte prosím push tokeny ručně prostřednictvím nastavení, když je k dispozici síťové připojení.'; + String get pleaseSyncManuallyWhenNetworkIsAvailable => + 'Synchronizujte prosím push tokeny ručně prostřednictvím nastavení, když je k dispozici síťové připojení.'; @override String get pushTokens => 'Žetony Push'; @@ -877,5 +890,13 @@ class AppLocalizationsCs extends AppLocalizations { } @override - String get sendPushRequestResponseFailed => 'Odpověď se nepodařilo odeslat.'; + String get pushEndpointUrl => 'URL koncového bodu push'; + + @override + String get exampleUrl => 'Zadejte prosím platnou adresu URL, například: \"https://example.com/\"'; + + @override + String mustNotBeEmpty(Object field) { + return '$field nesmí být prázdné'; + } } diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index d41c7b09e..28bf84a9a 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -22,7 +22,8 @@ class AppLocalizationsDe extends AppLocalizations { String get patchNotesV4_3_1Improvement1 => 'Der QR-Code-Scanner wurde verbessert.'; @override - String get patchNotesV4_3_0NewFeatures1 => 'Unterstützung für den Import von Token von Google, Aegis und 2FAS Authenticator hinzugefügt. Weitere Importquellen werden in Zukunft hinzugefügt.'; + String get patchNotesV4_3_0NewFeatures1 => + 'Unterstützung für den Import von Token von Google, Aegis und 2FAS Authenticator hinzugefügt. Weitere Importquellen werden in Zukunft hinzugefügt.'; @override String get patchNotesV4_3_0NewFeatures2 => 'Feedback-Option zu den Einstellungen hinzugefügt.'; @@ -108,10 +109,12 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get confirmTokenDeletionHint => 'Unter Umständen können Sie sich nicht mehr einloggen, wenn Sie diesen Token löschen.\nBitte stellen Sie sicher, dass Sie sich ohne diesen Token in den dazugehörigen Account einloggen können.'; + String get confirmTokenDeletionHint => + 'Unter Umständen können Sie sich nicht mehr einloggen, wenn Sie diesen Token löschen.\nBitte stellen Sie sicher, dass Sie sich ohne diesen Token in den dazugehörigen Account einloggen können.'; @override - String get confirmFolderDeletionHint => 'Das Löschen eines Ordners hat keine Auswirkungen auf die Token, die sich darin befinden.\nDie Token werden in die Hauptliste verschoben.'; + String get confirmFolderDeletionHint => + 'Das Löschen eines Ordners hat keine Auswirkungen auf die Token, die sich darin befinden.\nDie Token werden in die Hauptliste verschoben.'; @override String get generatingPhonePart => 'Generiere Telefonanteil'; @@ -149,7 +152,8 @@ class AppLocalizationsDe extends AppLocalizations { String get enablePolling => 'Aktives Stellen von Push-Anfragen'; @override - String get requestPushChallengesPeriodically => 'Fordert regelmäßig Push-Anfragen vom Server an. Aktivieren Sie diese Funktion, wenn Nachrichten ansonsten nicht erhalten werden.'; + String get requestPushChallengesPeriodically => + 'Fordert regelmäßig Push-Anfragen vom Server an. Aktivieren Sie diese Funktion, wenn Nachrichten ansonsten nicht erhalten werden.'; @override String get synchronizePushTokens => 'Synchronisiere Push Token'; @@ -267,7 +271,8 @@ class AppLocalizationsDe extends AppLocalizations { String get goToSettingsButton => 'Gehe zu Einstellungen'; @override - String get goToSettingsDescription => 'Authentifizierung durch Gerätepasswort oder Biometrie ist nicht eingerichtet. Bitte aktivieren Sie dies in den Geräteeinstellungen.'; + String get goToSettingsDescription => + 'Authentifizierung durch Gerätepasswort oder Biometrie ist nicht eingerichtet. Bitte aktivieren Sie dies in den Geräteeinstellungen.'; @override String get lockOut => 'Biometrie ist deaktiviert. Bitte sperren und entsperren Sie Ihren Bildschirm um diese zu aktivieren.'; @@ -329,7 +334,8 @@ class AppLocalizationsDe extends AppLocalizations { String get send => 'Senden'; @override - String get sendErrorLogDescription => 'Es wird eine vorgefertigte E-Mail erstellt.\nSie enthält Informationen über die App, den Fehler und das Gerät.\nSie können die E-Mail vor dem Senden bearbeiten.\nWie wir die Informationen verwenden, sehen Sie hier:'; + String get sendErrorLogDescription => + 'Es wird eine vorgefertigte E-Mail erstellt.\nSie enthält Informationen über die App, den Fehler und das Gerät.\nSie können die E-Mail vor dem Senden bearbeiten.\nWie wir die Informationen verwenden, sehen Sie hier:'; @override String get showPrivacyPolicy => 'Datenschutzerklärung anzeigen'; @@ -356,7 +362,8 @@ class AppLocalizationsDe extends AppLocalizations { String get open => 'Öffnen'; @override - String get sendErrorDialogBody => 'Ein unbekannter Fehler ist aufgetreten. Die unten gezeigten Informationen können den Entwicklern per E-Mail zugesendet werden, um zu helfen, diesen Fehler in Zukunft zu vermeiden.'; + String get sendErrorDialogBody => + 'Ein unbekannter Fehler ist aufgetreten. Die unten gezeigten Informationen können den Entwicklern per E-Mail zugesendet werden, um zu helfen, diesen Fehler in Zukunft zu vermeiden.'; @override String get noFbToken => 'Kein Firebase Token vorhanden'; @@ -480,7 +487,8 @@ class AppLocalizationsDe extends AppLocalizations { String get grantCameraPermissionDialogContent => 'Um QR-Codes zu scannen, benötigt die App Zugriff auf die Kamera.'; @override - String get grantCameraPermissionDialogPermanentlyDenied => 'Sie haben die Berechtigung für den Kamerazugriff permanent verweigert. Bitte aktivieren Sie die Berechtigung in den Einstellungen ihres Smartphones.'; + String get grantCameraPermissionDialogPermanentlyDenied => + 'Sie haben die Berechtigung für den Kamerazugriff permanent verweigert. Bitte aktivieren Sie die Berechtigung in den Einstellungen ihres Smartphones.'; @override String get grantCameraPermissionDialogButton => 'Berechtigung erteilen'; @@ -489,7 +497,8 @@ class AppLocalizationsDe extends AppLocalizations { String get decryptErrorTitle => 'Entschlüsselung fehlgeschlagen'; @override - String get decryptErrorContent => 'Leider konnten Ihre Token nicht entschlüsselt werden. Das deutet darauf hin, dass der Verschlüsselungsschlüssel nicht mehr verfügbar ist. Sie können es erneut versuchen oder die App Daten löschen. Dabei werden alle Token aus der App geschlöscht.'; + String get decryptErrorContent => + 'Leider konnten Ihre Token nicht entschlüsselt werden. Das deutet darauf hin, dass der Verschlüsselungsschlüssel nicht mehr verfügbar ist. Sie können es erneut versuchen oder die App Daten löschen. Dabei werden alle Token aus der App geschlöscht.'; @override String get decryptErrorButtonDelete => 'Löschen.'; @@ -507,7 +516,8 @@ class AppLocalizationsDe extends AppLocalizations { String get hidePushTokens => 'Push-Token ausblenden'; @override - String get hidePushTokensDescription => 'Push-Token aus der Token-Liste ausblenden. Dadurch werden die Token nicht gelöscht und sind weiterhin auf einem separaten Bildschirm sichtbar.'; + String get hidePushTokensDescription => + 'Push-Token aus der Token-Liste ausblenden. Dadurch werden die Token nicht gelöscht und sind weiterhin auf einem separaten Bildschirm sichtbar.'; @override String get settingsGroupGeneral => 'Allgemeines'; @@ -519,7 +529,8 @@ class AppLocalizationsDe extends AppLocalizations { String get privacyPolicy => 'Datenschutzerklärung'; @override - String get introScanQrCode => 'Sie können QR-Codes scannen, um Token hinzuzufügen.\nWir unterstützen alle gängigen Two-Factor-Authentication Token und auch die privacyIDEA Token.'; + String get introScanQrCode => + 'Sie können QR-Codes scannen, um Token hinzuzufügen.\nWir unterstützen alle gängigen Two-Factor-Authentication Token und auch die privacyIDEA Token.'; @override String get introAddTokenManually => 'Wenn Sie keinen QR-Code scannen möchten, können Sie Token auch manuell hinzufügen.'; @@ -531,7 +542,8 @@ class AppLocalizationsDe extends AppLocalizations { String get introEditToken => 'Hier können Sie den Namen des Tokens bearbeiten und einige Details einsehen.'; @override - String get introLockToken => 'Um die Sicherheit noch weiter zu erhöhen, können Sie Token sperren.\nDer Token kann dann erst nach der Authentifizierung verwendet werden.'; + String get introLockToken => + 'Um die Sicherheit noch weiter zu erhöhen, können Sie Token sperren.\nDer Token kann dann erst nach der Authentifizierung verwendet werden.'; @override String get introDragToken => 'Reorganisieren Sie Ihre Token, indem Sie sie einige Sekunden lang drücken und dann an die gewünschte Position ziehen.'; @@ -551,7 +563,8 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get legacySigningErrorMessage => 'Der Token wurde in einer veralteten Version der App erstellt, was zu Problemen bei der Verwendung führen kann. Es wird empfohlen, einen neuen Push-Token zu erstellen, wenn das Problem weiterhin besteht!'; + String get legacySigningErrorMessage => + 'Der Token wurde in einer veralteten Version der App erstellt, was zu Problemen bei der Verwendung führen kann. Es wird empfohlen, einen neuen Push-Token zu erstellen, wenn das Problem weiterhin besteht!'; @override String get selectImportSource => 'Importquelle auswählen'; @@ -658,10 +671,12 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get importHint2FAS => 'Wählen Sie das 2FAS-Backup aus.\nFalls Sie kein Backup haben, erstellen Sie eins in der 2FAS-App. Wir empfehlen die Verwendung eines Passworts.'; + String get importHint2FAS => + 'Wählen Sie das 2FAS-Backup aus.\nFalls Sie kein Backup haben, erstellen Sie eins in der 2FAS-App. Wir empfehlen die Verwendung eines Passworts.'; @override - String get importHintAegisBackupFile => 'Wähle dein Aegis-Export (.json) aus.\nWenn Sie keinen Export haben, erstellen Sie bitte eins über das Einstellungen Menu in der Aegis-App. Wir empfehlen die Verwendung eines Passworts.'; + String get importHintAegisBackupFile => + 'Wähle dein Aegis-Export (.json) aus.\nWenn Sie keinen Export haben, erstellen Sie bitte eins über das Einstellungen Menu in der Aegis-App. Wir empfehlen die Verwendung eines Passworts.'; @override String get importHintAegisQrScan => 'Scannen Sie den QR-Code, den Sie erhalten, wenn Sie Einträge aus Aegis übertragen.'; @@ -673,19 +688,24 @@ class AppLocalizationsDe extends AppLocalizations { String get importHintGoogleQrScan => 'Scannen Sie den QR-Code, den Sie erhalten, wenn Sie Ihre Konten aus Google Authenticator exportieren.'; @override - String get importHintGoogleQrFile => 'Wählen Sie eine Bilddatei mit dem QR-Code, den Sie erhalten, wenn Sie Ihre Konten aus dem Google Authenticator exportieren.\n!! Der QR-Code enthält die Token in unverschlüsselter Form. Es ist deshalb nicht sicher, diesen länger als nötig aufzubewahren !!'; + String get importHintGoogleQrFile => + 'Wählen Sie eine Bilddatei mit dem QR-Code, den Sie erhalten, wenn Sie Ihre Konten aus dem Google Authenticator exportieren.\n!! Der QR-Code enthält die Token in unverschlüsselter Form. Es ist deshalb nicht sicher, diesen länger als nötig aufzubewahren !!'; @override - String get importHintAuthenticatorProFile => 'Um ein Backup der Authenticator Pro-App zu erstellen navigieren Sie zu den Einstellungen und tippen Sie auf \"Automatische Sicherung\". Wählen Sie einen Speicherort und setzen Sie ein Passwort. Anschließend drücken Sie auf \"Jetzt sichern\" um die Token zu exportieren.'; + String get importHintAuthenticatorProFile => + 'Um ein Backup der Authenticator Pro-App zu erstellen navigieren Sie zu den Einstellungen und tippen Sie auf \"Automatische Sicherung\". Wählen Sie einen Speicherort und setzen Sie ein Passwort. Anschließend drücken Sie auf \"Jetzt sichern\" um die Token zu exportieren.'; @override - String get importHintFreeOtpPlusQrScan => 'Scannen Sie den QR-Code, den Sie erhalten, wenn Sie auf die drei Punkte in der Kachel des Tokens drücken, und wählen Sie \"QR-Code teilen\".'; + String get importHintFreeOtpPlusQrScan => + 'Scannen Sie den QR-Code, den Sie erhalten, wenn Sie auf die drei Punkte in der Kachel des Tokens drücken, und wählen Sie \"QR-Code teilen\".'; @override - String get importHintFreeOtpPlusFile => 'Um ein Backup der FreeOTP+ App zu erstellen, tippen Sie auf die drei Punkte in der oberen rechten Ecke und wählen Sie \"Exportieren\". Sie können zwischen dem JSON- und dem URI-Format wählen. Wir empfehlen, das Backup nach dem Importieren zu löschen, da es nicht verschlüsselt ist.'; + String get importHintFreeOtpPlusFile => + 'Um ein Backup der FreeOTP+ App zu erstellen, tippen Sie auf die drei Punkte in der oberen rechten Ecke und wählen Sie \"Exportieren\". Sie können zwischen dem JSON- und dem URI-Format wählen. Wir empfehlen, das Backup nach dem Importieren zu löschen, da es nicht verschlüsselt ist.'; @override - String get qrFileDecodeError => 'Es war nicht möglich, den QR-Code aus dem ausgewählten Bild zu dekodieren. Bitte verwenden Sie stattdessen den QR-Code-Scanner.'; + String get qrFileDecodeError => + 'Es war nicht möglich, den QR-Code aus dem ausgewählten Bild zu dekodieren. Bitte verwenden Sie stattdessen den QR-Code-Scanner.'; @override String get tokenLink => 'Token Link'; @@ -700,7 +720,8 @@ class AppLocalizationsDe extends AppLocalizations { String get feedbackDescription => 'Wenn Sie Fragen, Anregungen oder Probleme haben, lassen Sie es uns wissen.'; @override - String get feedbackHint => 'Es öffnet sich eine vorgefertigte E-Mail, die Sie an uns senden können. Falls gewünscht, werden Informationen über Ihr Gerät und die Version der Anwendung hinzugefügt. Vor dem Versenden können Sie die E-Mail überprüfen und bearbeiten.'; + String get feedbackHint => + 'Es öffnet sich eine vorgefertigte E-Mail, die Sie an uns senden können. Falls gewünscht, werden Informationen über Ihr Gerät und die Version der Anwendung hinzugefügt. Vor dem Versenden können Sie die E-Mail überprüfen und bearbeiten.'; @override String get feedbackPrivacyPolicy1 => 'Mit dem Senden des Feedbacks stimmen Sie unserer '; @@ -730,7 +751,8 @@ class AppLocalizationsDe extends AppLocalizations { String get noMailAppTitle => 'Keine Mail-App gefunden'; @override - String get noMailAppDescription => 'Auf diesem Gerät ist keine E-Mail-App installiert oder initialisiert, bitte versuchen Sie es erneut, wenn Sie eine E-Mail-Nachricht senden können.'; + String get noMailAppDescription => + 'Auf diesem Gerät ist keine E-Mail-App installiert oder initialisiert, bitte versuchen Sie es erneut, wenn Sie eine E-Mail-Nachricht senden können.'; @override String get authenticationRequest => 'Authentifizierung'; @@ -746,7 +768,8 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get pleaseSyncManuallyWhenNetworkIsAvailable => 'Bitte synchronisieren Sie die Push Token über die Einstellungen manuell, wenn eine Netzwerkverbindung verfügbar ist.'; + String get pleaseSyncManuallyWhenNetworkIsAvailable => + 'Bitte synchronisieren Sie die Push Token über die Einstellungen manuell, wenn eine Netzwerkverbindung verfügbar ist.'; @override String get pushTokens => 'Push-Token'; @@ -826,7 +849,8 @@ class AppLocalizationsDe extends AppLocalizations { String get exportTokens => 'Tokens exportieren'; @override - String get enterPasswordToEncrypt => 'Geben Sie ein Passwort ein, um die Tokens zu verschlüsseln. Dieses Passwort wird benötigt, um die Tokens zu importieren.'; + String get enterPasswordToEncrypt => + 'Geben Sie ein Passwort ein, um die Tokens zu verschlüsseln. Dieses Passwort wird benötigt, um die Tokens zu importieren.'; @override String get exportLockedTokenReason => 'Bitte authentifizieren Sie sich, um gesperrte Tokens zu exportieren.'; @@ -877,5 +901,13 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get sendPushRequestResponseFailed => 'Senden der Antwort fehlgeschlagen.'; + String get pushEndpointUrl => 'Push-Endpunkt URL'; + + @override + String get exampleUrl => 'Bitte geben Sie eine gültige URL ein wie: \"https://example.com\"'; + + @override + String mustNotBeEmpty(Object field) { + return '$field darf nicht leer sein'; + } } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index be05f09b7..707bce230 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -22,7 +22,8 @@ class AppLocalizationsEn extends AppLocalizations { String get patchNotesV4_3_1Improvement1 => 'Improved the qr code scanner.'; @override - String get patchNotesV4_3_0NewFeatures1 => 'Support for importing tokens from Google, Aegis and 2FAS Authenticator has been added. More import sources will be added in the future.'; + String get patchNotesV4_3_0NewFeatures1 => + 'Support for importing tokens from Google, Aegis and 2FAS Authenticator has been added. More import sources will be added in the future.'; @override String get patchNotesV4_3_0NewFeatures2 => 'Added feedback option to the settings.'; @@ -108,7 +109,8 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get confirmTokenDeletionHint => 'You may no longer be able to log in if you delete this token.\nPlease make sure that you can log in to the associated account without this token.'; + String get confirmTokenDeletionHint => + 'You may no longer be able to log in if you delete this token.\nPlease make sure that you can log in to the associated account without this token.'; @override String get confirmFolderDeletionHint => 'Deleting a folder has no effect on the tokens in it.\nThe tokens are moved to the main list.'; @@ -149,7 +151,8 @@ class AppLocalizationsEn extends AppLocalizations { String get enablePolling => 'Enable polling'; @override - String get requestPushChallengesPeriodically => 'Request push challenges from the server periodically. Enable this if push challenges are not received normally.'; + String get requestPushChallengesPeriodically => + 'Request push challenges from the server periodically. Enable this if push challenges are not received normally.'; @override String get synchronizePushTokens => 'Synchronize push tokens'; @@ -329,7 +332,8 @@ class AppLocalizationsEn extends AppLocalizations { String get send => 'Send'; @override - String get sendErrorLogDescription => 'A predefined email is created.\nIt contains information about the app, the error and the device.\nYou can edit the email before sending it.\nYou can see here how we use the information:'; + String get sendErrorLogDescription => + 'A predefined email is created.\nIt contains information about the app, the error and the device.\nYou can edit the email before sending it.\nYou can see here how we use the information:'; @override String get showPrivacyPolicy => 'Show privacy policy'; @@ -356,7 +360,8 @@ class AppLocalizationsEn extends AppLocalizations { String get open => 'Open'; @override - String get sendErrorDialogBody => 'An unexpected error occurred in the application. The information below can be send to the developers by email to help prevent this error in the future.'; + String get sendErrorDialogBody => + 'An unexpected error occurred in the application. The information below can be send to the developers by email to help prevent this error in the future.'; @override String get noFbToken => 'No Firebase token available'; @@ -480,7 +485,8 @@ class AppLocalizationsEn extends AppLocalizations { String get grantCameraPermissionDialogContent => 'Please grant camera permission to scan QR codes.'; @override - String get grantCameraPermissionDialogPermanentlyDenied => 'Camera permission is permanently denied. Please grant camera permission in your Phone\'s settings.'; + String get grantCameraPermissionDialogPermanentlyDenied => + 'Camera permission is permanently denied. Please grant camera permission in your Phone\'s settings.'; @override String get grantCameraPermissionDialogButton => 'Grant permission'; @@ -489,7 +495,8 @@ class AppLocalizationsEn extends AppLocalizations { String get decryptErrorTitle => 'Decryption error'; @override - String get decryptErrorContent => 'Unfortunately, the app was unable to decrypt your tokens. This indicates that the encryption key is broken. You can try again or delete the app data, which would delete the tokens in the app.'; + String get decryptErrorContent => + 'Unfortunately, the app was unable to decrypt your tokens. This indicates that the encryption key is broken. You can try again or delete the app data, which would delete the tokens in the app.'; @override String get decryptErrorButtonDelete => 'Delete'; @@ -507,7 +514,8 @@ class AppLocalizationsEn extends AppLocalizations { String get hidePushTokens => 'Hide push tokens'; @override - String get hidePushTokensDescription => 'Hide push tokens from the token list. This will not delete the tokens and they will still be visible on a separate screen.'; + String get hidePushTokensDescription => + 'Hide push tokens from the token list. This will not delete the tokens and they will still be visible on a separate screen.'; @override String get settingsGroupGeneral => 'General'; @@ -519,7 +527,8 @@ class AppLocalizationsEn extends AppLocalizations { String get privacyPolicy => 'Privacy policy'; @override - String get introScanQrCode => 'You can scan QR codes to add tokens.\nWe support every common Two-Factor-Authentication token and also the privacyIDEA tokens.'; + String get introScanQrCode => + 'You can scan QR codes to add tokens.\nWe support every common Two-Factor-Authentication token and also the privacyIDEA tokens.'; @override String get introAddTokenManually => 'If you don\'t want to scan a QR code, you can also add tokens manually.'; @@ -551,7 +560,8 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get legacySigningErrorMessage => 'The token was enrolled in a old version of this app, which may cause trouble using it.\nIt is suggested to enroll a new push token if the problem persist!'; + String get legacySigningErrorMessage => + 'The token was enrolled in a old version of this app, which may cause trouble using it.\nIt is suggested to enroll a new push token if the problem persist!'; @override String get selectImportSource => 'Select import source'; @@ -661,7 +671,8 @@ class AppLocalizationsEn extends AppLocalizations { String get importHint2FAS => 'Select your 2FAS backup.\nIf you do not have a backup, create one in the 2FAS app. We recommend using a password.'; @override - String get importHintAegisBackupFile => 'Select your Aegis export (.JSON).\nIf you do not have an export, please create one via the settings menu in the Aegis app. The use of a password is recommended.'; + String get importHintAegisBackupFile => + 'Select your Aegis export (.JSON).\nIf you do not have an export, please create one via the settings menu in the Aegis app. The use of a password is recommended.'; @override String get importHintAegisQrScan => 'Scan the QR code you receive when you transfer entries from Aegis.'; @@ -673,16 +684,19 @@ class AppLocalizationsEn extends AppLocalizations { String get importHintGoogleQrScan => 'Scan the QR code you receive when you export your accounts from Google Authenticator.'; @override - String get importHintGoogleQrFile => 'Select an image file with the QR code you receive when you export your accounts from Google Authenticator.\n!! Note that it is not safe to save the QR code on your device as the tokens are not encrypted !!'; + String get importHintGoogleQrFile => + 'Select an image file with the QR code you receive when you export your accounts from Google Authenticator.\n!! Note that it is not safe to save the QR code on your device as the tokens are not encrypted !!'; @override - String get importHintAuthenticatorProFile => 'To create a backup of the Authenticator Pro app, navigate to the settings and tap on \"Auto backup\". Select a storage location and set a password. Then press \"Back up now\" to export the tokens.'; + String get importHintAuthenticatorProFile => + 'To create a backup of the Authenticator Pro app, navigate to the settings and tap on \"Auto backup\". Select a storage location and set a password. Then press \"Back up now\" to export the tokens.'; @override String get importHintFreeOtpPlusQrScan => 'Scan the QR code you receive when you press the three dots in the tile of the token and select \"Share QR code\".'; @override - String get importHintFreeOtpPlusFile => 'To create a backup of the FreeOTP+ app, tap on the three dots in the upper right corner and select \"Export\". You can choose between JSON and URI format. We recommend to delete the backup after importing it, because it is not encrypted.'; + String get importHintFreeOtpPlusFile => + 'To create a backup of the FreeOTP+ app, tap on the three dots in the upper right corner and select \"Export\". You can choose between JSON and URI format. We recommend to delete the backup after importing it, because it is not encrypted.'; @override String get qrFileDecodeError => 'It was not possible to decode the QR code from the selected image, please use the QR code scanner instead.'; @@ -700,7 +714,8 @@ class AppLocalizationsEn extends AppLocalizations { String get feedbackDescription => 'If you have any questions, suggestions or problems, please let us know.'; @override - String get feedbackHint => 'A ready-made e-mail will open, which you can send to us. If desired, information about your device and the version of the application will be added. You can check and edit the email before sending it.'; + String get feedbackHint => + 'A ready-made e-mail will open, which you can send to us. If desired, information about your device and the version of the application will be added. You can check and edit the email before sending it.'; @override String get feedbackPrivacyPolicy1 => 'By sending the feedback you agree to our '; @@ -730,7 +745,8 @@ class AppLocalizationsEn extends AppLocalizations { String get noMailAppTitle => 'No mail app found'; @override - String get noMailAppDescription => 'There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.'; + String get noMailAppDescription => + 'There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.'; @override String get authenticationRequest => 'Authentication request'; @@ -877,5 +893,13 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get sendPushRequestResponseFailed => 'Failed to send the response.'; + String get pushEndpointUrl => 'Push endpoint URL'; + + @override + String get exampleUrl => 'Please enter a valid URL like: \"https://example.com/\"'; + + @override + String mustNotBeEmpty(Object field) { + return '$field must not be empty'; + } } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 9df760cb9..254a35c70 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -22,7 +22,8 @@ class AppLocalizationsEs extends AppLocalizations { String get patchNotesV4_3_1Improvement1 => 'Se ha mejorado el escáner de códigos QR.'; @override - String get patchNotesV4_3_0NewFeatures1 => 'Añadido soporte para importar tokens de Google, Aegis y 2FAS Authenticator. En el futuro se añadirán más fuentes de importación'; + String get patchNotesV4_3_0NewFeatures1 => + 'Añadido soporte para importar tokens de Google, Aegis y 2FAS Authenticator. En el futuro se añadirán más fuentes de importación'; @override String get patchNotesV4_3_0NewFeatures2 => 'Añadida opción de feedback a los ajustes'; @@ -108,7 +109,8 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get confirmTokenDeletionHint => 'Es posible que ya no pueda iniciar sesión si elimina este token.\nAsegúrese de que puede iniciar sesión en la cuenta asociada sin este token.'; + String get confirmTokenDeletionHint => + 'Es posible que ya no pueda iniciar sesión si elimina este token.\nAsegúrese de que puede iniciar sesión en la cuenta asociada sin este token.'; @override String get confirmFolderDeletionHint => 'Eliminar una carpeta no afecta a los tokens que contiene.\nLos tokens se mueven a la lista principal.'; @@ -149,7 +151,8 @@ class AppLocalizationsEs extends AppLocalizations { String get enablePolling => 'Activar polling'; @override - String get requestPushChallengesPeriodically => 'Solicita retos push al servidor periódicamente. Habilite esta opción si los retos push no se reciben normalmente.'; + String get requestPushChallengesPeriodically => + 'Solicita retos push al servidor periódicamente. Habilite esta opción si los retos push no se reciben normalmente.'; @override String get synchronizePushTokens => 'Sinchronizar push tokens'; @@ -267,7 +270,8 @@ class AppLocalizationsEs extends AppLocalizations { String get goToSettingsButton => 'Ir a la configuración'; @override - String get goToSettingsDescription => 'La autenticación por credenciales o biométrica no está configurada en tu dispositivo. Por favor, configúrala en los ajustes del dispositivo.'; + String get goToSettingsDescription => + 'La autenticación por credenciales o biométrica no está configurada en tu dispositivo. Por favor, configúrala en los ajustes del dispositivo.'; @override String get lockOut => 'La autenticación biométrica está desactivada. Bloquea y desbloquea la pantalla para activarla.'; @@ -329,7 +333,8 @@ class AppLocalizationsEs extends AppLocalizations { String get send => 'Enviar'; @override - String get sendErrorLogDescription => 'Se crea un correo electrónico listo.\nContiene información sobre la app, el error y el dispositivo.\nPuedes editar el correo antes de enviarlo.\nAquí puede ver cómo utilizamos la información:'; + String get sendErrorLogDescription => + 'Se crea un correo electrónico listo.\nContiene información sobre la app, el error y el dispositivo.\nPuedes editar el correo antes de enviarlo.\nAquí puede ver cómo utilizamos la información:'; @override String get showPrivacyPolicy => 'Mostrar política de privacidad'; @@ -356,7 +361,8 @@ class AppLocalizationsEs extends AppLocalizations { String get open => 'Abrir'; @override - String get sendErrorDialogBody => 'Se ha producido un error inesperado en la aplicación. La siguiente información puede ser enviada a los desarrolladores por correo electrónico para ayudar a prevenir este error en el futuro.'; + String get sendErrorDialogBody => + 'Se ha producido un error inesperado en la aplicación. La siguiente información puede ser enviada a los desarrolladores por correo electrónico para ayudar a prevenir este error en el futuro.'; @override String get noFbToken => 'No hay token de Firebase.'; @@ -480,7 +486,8 @@ class AppLocalizationsEs extends AppLocalizations { String get grantCameraPermissionDialogContent => 'Por favor, concede permiso a la cámara para escanear códigos QR'; @override - String get grantCameraPermissionDialogPermanentlyDenied => 'El permiso de cámara está denegado permanentemente. Concede el permiso de cámara en la configuración del teléfono'; + String get grantCameraPermissionDialogPermanentlyDenied => + 'El permiso de cámara está denegado permanentemente. Concede el permiso de cámara en la configuración del teléfono'; @override String get grantCameraPermissionDialogButton => 'Conceder permiso'; @@ -489,7 +496,8 @@ class AppLocalizationsEs extends AppLocalizations { String get decryptErrorTitle => 'Error de descifrado'; @override - String get decryptErrorContent => 'Lamentablemente, la aplicación no ha podido descifrar tus tokens. Esto indica que la clave de cifrado está rota. Puedes volver a intentarlo o borrar los datos de la app, lo que eliminaría los tokens de la app.'; + String get decryptErrorContent => + 'Lamentablemente, la aplicación no ha podido descifrar tus tokens. Esto indica que la clave de cifrado está rota. Puedes volver a intentarlo o borrar los datos de la app, lo que eliminaría los tokens de la app.'; @override String get decryptErrorButtonDelete => 'Borrar'; @@ -507,7 +515,8 @@ class AppLocalizationsEs extends AppLocalizations { String get hidePushTokens => 'Ocultar tokens push'; @override - String get hidePushTokensDescription => 'Ocultar tokens push de la lista de tokens. Esto no borrará los tokens y seguirán siendo visibles en una pantalla aparte'; + String get hidePushTokensDescription => + 'Ocultar tokens push de la lista de tokens. Esto no borrará los tokens y seguirán siendo visibles en una pantalla aparte'; @override String get settingsGroupGeneral => 'Información general'; @@ -519,7 +528,8 @@ class AppLocalizationsEs extends AppLocalizations { String get privacyPolicy => 'Política de privacidad'; @override - String get introScanQrCode => 'Puedes escanear códigos QR para añadir tokens.\nSoportamos todos los tokens comunes de Two-Factor-Authentication y también los tokens privacyIDEA'; + String get introScanQrCode => + 'Puedes escanear códigos QR para añadir tokens.\nSoportamos todos los tokens comunes de Two-Factor-Authentication y también los tokens privacyIDEA'; @override String get introAddTokenManually => 'Si no quieres escanear un código QR, también puedes añadir tokens manualmente'; @@ -531,7 +541,8 @@ class AppLocalizationsEs extends AppLocalizations { String get introEditToken => 'Aquí puedes editar el nombre del token y ver algunos detalles'; @override - String get introLockToken => 'Para mejorar la seguridad aún más, puedes bloquear los tokens.\nEntonces el token sólo se puede utilizar después de la autenticación.'; + String get introLockToken => + 'Para mejorar la seguridad aún más, puedes bloquear los tokens.\nEntonces el token sólo se puede utilizar después de la autenticación.'; @override String get introDragToken => 'Reorganiza tus tokens pulsándolo durante unos segundos y arrastrándolo a la posición deseada'; @@ -551,7 +562,8 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get legacySigningErrorMessage => 'El token se creó en una versión obsoleta de la aplicación, lo que puede provocar problemas al utilizarlo.\nSe recomienda crear un nuevo token push si el problema persiste.'; + String get legacySigningErrorMessage => + 'El token se creó en una versión obsoleta de la aplicación, lo que puede provocar problemas al utilizarlo.\nSe recomienda crear un nuevo token push si el problema persiste.'; @override String get selectImportSource => 'Seleccionar fuente de importación'; @@ -658,10 +670,12 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get importHint2FAS => 'Seleccione su copia de seguridad de 2FAS. Si no tiene una copia de seguridad, cree una en la aplicación 2FAS. Le recomendamos que utilice una contraseña'; + String get importHint2FAS => + 'Seleccione su copia de seguridad de 2FAS. Si no tiene una copia de seguridad, cree una en la aplicación 2FAS. Le recomendamos que utilice una contraseña'; @override - String get importHintAegisBackupFile => 'Seleccione su exportación de Aegis (.JSON).\nSi no tiene una exportación, cree una a través del menú de configuración en la app de Aegis. Se recomienda utilizar una contraseña'; + String get importHintAegisBackupFile => + 'Seleccione su exportación de Aegis (.JSON).\nSi no tiene una exportación, cree una a través del menú de configuración en la app de Aegis. Se recomienda utilizar una contraseña'; @override String get importHintAegisQrScan => 'Escanea el código QR que recibes al transferir entradas desde Aegis'; @@ -673,16 +687,20 @@ class AppLocalizationsEs extends AppLocalizations { String get importHintGoogleQrScan => 'Escanea el código QR que recibes al exportar tus cuentas desde Google Authenticator'; @override - String get importHintGoogleQrFile => 'Selecciona un archivo de imagen con el código QR que recibes al exportar tus cuentas desde Google Authenticator.\n!! Tenga en cuenta que no es seguro guardar el código QR en su dispositivo, ya que los tokens no están cifrados !!'; + String get importHintGoogleQrFile => + 'Selecciona un archivo de imagen con el código QR que recibes al exportar tus cuentas desde Google Authenticator.\n!! Tenga en cuenta que no es seguro guardar el código QR en su dispositivo, ya que los tokens no están cifrados !!'; @override - String get importHintAuthenticatorProFile => 'Para crear una copia de seguridad de la aplicación Authenticator Pro, vaya a la configuración y pulse en \"Copia de seguridad automática\". Seleccione una ubicación de almacenamiento y establezca una contraseña. A continuación, pulse \"Hacer copia de seguridad ahora\" para exportar los tokens.'; + String get importHintAuthenticatorProFile => + 'Para crear una copia de seguridad de la aplicación Authenticator Pro, vaya a la configuración y pulse en \"Copia de seguridad automática\". Seleccione una ubicación de almacenamiento y establezca una contraseña. A continuación, pulse \"Hacer copia de seguridad ahora\" para exportar los tokens.'; @override - String get importHintFreeOtpPlusQrScan => 'Escanea el código QR que recibes al pulsar los tres puntos en el azulejo de la ficha y selecciona \"Compartir código QR\".'; + String get importHintFreeOtpPlusQrScan => + 'Escanea el código QR que recibes al pulsar los tres puntos en el azulejo de la ficha y selecciona \"Compartir código QR\".'; @override - String get importHintFreeOtpPlusFile => 'Para crear una copia de seguridad de la app FreeOTP+, pulse los tres puntos de la esquina superior derecha y seleccione \"Exportar\". Puede elegir entre los formatos JSON y URI. Recomendamos eliminar la copia de seguridad después de importarla, ya que no está cifrada.'; + String get importHintFreeOtpPlusFile => + 'Para crear una copia de seguridad de la app FreeOTP+, pulse los tres puntos de la esquina superior derecha y seleccione \"Exportar\". Puede elegir entre los formatos JSON y URI. Recomendamos eliminar la copia de seguridad después de importarla, ya que no está cifrada.'; @override String get qrFileDecodeError => 'No fue posible decodificar el código QR de la imagen seleccionada, por favor utilice el escáner de código QR en su lugar.'; @@ -700,7 +718,8 @@ class AppLocalizationsEs extends AppLocalizations { String get feedbackDescription => 'Si tienes alguna pregunta, sugerencia o problema, háznoslo saber'; @override - String get feedbackHint => 'Se abrirá un correo electrónico preparado que podrá enviarnos. Si lo desea, se añadirá información sobre su dispositivo y la versión de la aplicación. Puede comprobar y editar el correo electrónico antes de enviarlo.'; + String get feedbackHint => + 'Se abrirá un correo electrónico preparado que podrá enviarnos. Si lo desea, se añadirá información sobre su dispositivo y la versión de la aplicación. Puede comprobar y editar el correo electrónico antes de enviarlo.'; @override String get feedbackPrivacyPolicy1 => 'Al enviar sus comentarios, acepta nuestra '; @@ -730,7 +749,8 @@ class AppLocalizationsEs extends AppLocalizations { String get noMailAppTitle => 'No hay aplicación de correo electrónico'; @override - String get noMailAppDescription => 'No hay ninguna app de correo electrónico instalada o inicializada en este dispositivo, inténtalo de nuevo cuando puedas enviar un mensaje de correo electrónico.'; + String get noMailAppDescription => + 'No hay ninguna app de correo electrónico instalada o inicializada en este dispositivo, inténtalo de nuevo cuando puedas enviar un mensaje de correo electrónico.'; @override String get authenticationRequest => 'Autenticación'; @@ -746,7 +766,8 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get pleaseSyncManuallyWhenNetworkIsAvailable => 'Por favor, sincronice los tokens push manualmente a través de los ajustes cuando haya una conexión de red disponible.'; + String get pleaseSyncManuallyWhenNetworkIsAvailable => + 'Por favor, sincronice los tokens push manualmente a través de los ajustes cuando haya una conexión de red disponible.'; @override String get pushTokens => 'Push Tokens'; @@ -877,5 +898,13 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get sendPushRequestResponseFailed => 'No se ha podido enviar la respuesta.'; + String get pushEndpointUrl => 'URL del punto final push'; + + @override + String get exampleUrl => 'Por favor, introduzca una URL válida como: \"https://example.com/\"'; + + @override + String mustNotBeEmpty(Object field) { + return '$field no debe estar vacío'; + } } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index b40fa1a6a..e35bf2af7 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -22,7 +22,8 @@ class AppLocalizationsFr extends AppLocalizations { String get patchNotesV4_3_1Improvement1 => 'Le scanner de codes QR a été amélioré.'; @override - String get patchNotesV4_3_0NewFeatures1 => 'Ajout de la prise en charge de l\'importation de jetons depuis Google, Aegis et 2FAS Authenticator. D\'autres sources d\'importation seront ajoutées à l\'avenir.'; + String get patchNotesV4_3_0NewFeatures1 => + 'Ajout de la prise en charge de l\'importation de jetons depuis Google, Aegis et 2FAS Authenticator. D\'autres sources d\'importation seront ajoutées à l\'avenir.'; @override String get patchNotesV4_3_0NewFeatures2 => 'Ajout d\'une option de retour d\'information dans les paramètres'; @@ -108,10 +109,12 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get confirmTokenDeletionHint => 'Il se peut que vous ne puissiez plus vous connecter si vous supprimez ce token.\nVeuillez vous assurer que vous pouvez vous connecter au compte associé sans ce token.'; + String get confirmTokenDeletionHint => + 'Il se peut que vous ne puissiez plus vous connecter si vous supprimez ce token.\nVeuillez vous assurer que vous pouvez vous connecter au compte associé sans ce token.'; @override - String get confirmFolderDeletionHint => 'La suppression d\'un dossier n\'a aucun effet sur les tokens qui s\'y trouvent.\nLes tokens sont déplacés dans la liste principale.'; + String get confirmFolderDeletionHint => + 'La suppression d\'un dossier n\'a aucun effet sur les tokens qui s\'y trouvent.\nLes tokens sont déplacés dans la liste principale.'; @override String get generatingPhonePart => 'Générer la part du téléphone'; @@ -149,7 +152,8 @@ class AppLocalizationsFr extends AppLocalizations { String get enablePolling => 'Activer l\'interrogation du serveur.'; @override - String get requestPushChallengesPeriodically => 'Demander des challenges push depuis le serveur périodiquement. Activer cette fonction si les challenges push ne sont pas reçus normalement.'; + String get requestPushChallengesPeriodically => + 'Demander des challenges push depuis le serveur périodiquement. Activer cette fonction si les challenges push ne sont pas reçus normalement.'; @override String get synchronizePushTokens => 'Synchoniser les jetons Push'; @@ -267,7 +271,8 @@ class AppLocalizationsFr extends AppLocalizations { String get goToSettingsButton => 'Aller aux paramètres'; @override - String get goToSettingsDescription => 'L\'authentification par identifiants ou biométrie n\'est pas configurée sur votre appareil. Veuillez le configurer dans les paramètres de l\'appareil.'; + String get goToSettingsDescription => + 'L\'authentification par identifiants ou biométrie n\'est pas configurée sur votre appareil. Veuillez le configurer dans les paramètres de l\'appareil.'; @override String get lockOut => 'L\'authentification biométrique est désactivée. Veuillez verrouiller et déverrouiller votre écran pour l\'activer.'; @@ -329,7 +334,8 @@ class AppLocalizationsFr extends AppLocalizations { String get send => 'Envoyer'; @override - String get sendErrorLogDescription => 'Un e-mail pré-rempli est créé.\nIl contient des informations sur l\'application, l\'erreur et le périphérique.\nVous pouvez modifier l\'e-mail avant de l\'envoyer.\nVous pouvez voir ici comment nous utilisons les informations:'; + String get sendErrorLogDescription => + 'Un e-mail pré-rempli est créé.\nIl contient des informations sur l\'application, l\'erreur et le périphérique.\nVous pouvez modifier l\'e-mail avant de l\'envoyer.\nVous pouvez voir ici comment nous utilisons les informations:'; @override String get showPrivacyPolicy => 'Afficher la déclaration de confidentialité'; @@ -356,7 +362,8 @@ class AppLocalizationsFr extends AppLocalizations { String get open => 'Ouvrir'; @override - String get sendErrorDialogBody => 'Une erreur inattendue est survenue dans l\'application. L\'information suivante peut être transmise aux développeurs par email afin d\'aider à corriger cette erreur dans le futur.'; + String get sendErrorDialogBody => + 'Une erreur inattendue est survenue dans l\'application. L\'information suivante peut être transmise aux développeurs par email afin d\'aider à corriger cette erreur dans le futur.'; @override String get noFbToken => 'Pas de jeton Firebase'; @@ -480,7 +487,8 @@ class AppLocalizationsFr extends AppLocalizations { String get grantCameraPermissionDialogContent => 'Veuillez accorder à la caméra l\'autorisation de scanner les codes QR'; @override - String get grantCameraPermissionDialogPermanentlyDenied => 'L\'autorisation de l\'appareil photo est refusée de manière permanente. Veuillez accorder l\'autorisation à l\'appareil photo dans les paramètres de votre téléphone.'; + String get grantCameraPermissionDialogPermanentlyDenied => + 'L\'autorisation de l\'appareil photo est refusée de manière permanente. Veuillez accorder l\'autorisation à l\'appareil photo dans les paramètres de votre téléphone.'; @override String get grantCameraPermissionDialogButton => 'Accorder l\'autorisation'; @@ -489,7 +497,8 @@ class AppLocalizationsFr extends AppLocalizations { String get decryptErrorTitle => 'Erreur de décryptage'; @override - String get decryptErrorContent => 'Malheureusement, l\'application n\'a pas pu décrypter vos jetons. Cela indique que la clé de cryptage est cassée. Vous pouvez réessayer ou supprimer les données de l\'application, ce qui supprimera les jetons dans l\'application.'; + String get decryptErrorContent => + 'Malheureusement, l\'application n\'a pas pu décrypter vos jetons. Cela indique que la clé de cryptage est cassée. Vous pouvez réessayer ou supprimer les données de l\'application, ce qui supprimera les jetons dans l\'application.'; @override String get decryptErrorButtonDelete => 'Supprimer'; @@ -507,7 +516,8 @@ class AppLocalizationsFr extends AppLocalizations { String get hidePushTokens => 'Hide push tokens'; @override - String get hidePushTokensDescription => 'Masquer les jetons de poussée de la liste des jetons. Cela ne supprimera pas les jetons et ils seront toujours visibles sur un écran séparé'; + String get hidePushTokensDescription => + 'Masquer les jetons de poussée de la liste des jetons. Cela ne supprimera pas les jetons et ils seront toujours visibles sur un écran séparé'; @override String get settingsGroupGeneral => 'Généralités'; @@ -519,7 +529,8 @@ class AppLocalizationsFr extends AppLocalizations { String get privacyPolicy => 'Politique de confidentialité'; @override - String get introScanQrCode => 'You can scan QR codes to add tokens.\nWe support every common Two-Factor-Authentication token and also the privacyIDEA tokens.'; + String get introScanQrCode => + 'You can scan QR codes to add tokens.\nWe support every common Two-Factor-Authentication token and also the privacyIDEA tokens.'; @override String get introAddTokenManually => 'Si vous ne souhaitez pas scanner un code QR, vous pouvez également ajouter des jetons manuellement.'; @@ -531,10 +542,12 @@ class AppLocalizationsFr extends AppLocalizations { String get introEditToken => 'Ici, vous pouvez modifier le nom du token et voir quelques détails'; @override - String get introLockToken => 'Pour améliorer encore la sécurité, vous pouvez verrouiller les tokens. Le token ne peut alors être utilisé qu\'après l\'authentification.'; + String get introLockToken => + 'Pour améliorer encore la sécurité, vous pouvez verrouiller les tokens. Le token ne peut alors être utilisé qu\'après l\'authentification.'; @override - String get introDragToken => 'Réorganisez vos jetons en appuyant dessus pendant quelques secondes, puis en les faisant glisser jusqu\'à la position souhaitée'; + String get introDragToken => + 'Réorganisez vos jetons en appuyant dessus pendant quelques secondes, puis en les faisant glisser jusqu\'à la position souhaitée'; @override String get introAddFolder => 'Vous pouvez créer des dossiers pour organiser vos jetons'; @@ -551,7 +564,8 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get legacySigningErrorMessage => 'Le token a été créé dans une version obsolète de l\'application, ce qui peut entraîner des problèmes d\'utilisation.\nIl est recommandé de créer un nouveau token push si le problème persiste !'; + String get legacySigningErrorMessage => + 'Le token a été créé dans une version obsolète de l\'application, ce qui peut entraîner des problèmes d\'utilisation.\nIl est recommandé de créer un nouveau token push si le problème persiste !'; @override String get selectImportSource => 'Sélectionner la source d\'importation'; @@ -658,10 +672,12 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get importHint2FAS => 'Choisissez votre sauvegarde 2FAS.\nSi vous n\'avez pas de sauvegarde, créez-en une dans l\'application 2FAS. Nous vous recommandons d\'utiliser un mot de passe'; + String get importHint2FAS => + 'Choisissez votre sauvegarde 2FAS.\nSi vous n\'avez pas de sauvegarde, créez-en une dans l\'application 2FAS. Nous vous recommandons d\'utiliser un mot de passe'; @override - String get importHintAegisBackupFile => 'Choisissez votre exportation Aegis (.JSON).\nSi vous n\'avez pas d\'exportation, veuillez en créer une via le menu Paramètres dans l\'application Aegis. Il est recommandé d\'utiliser un mot de passe'; + String get importHintAegisBackupFile => + 'Choisissez votre exportation Aegis (.JSON).\nSi vous n\'avez pas d\'exportation, veuillez en créer une via le menu Paramètres dans l\'application Aegis. Il est recommandé d\'utiliser un mot de passe'; @override String get importHintAegisQrScan => 'Scannez le code QR que vous recevez lorsque vous transférez des entrées depuis Aegis'; @@ -673,19 +689,24 @@ class AppLocalizationsFr extends AppLocalizations { String get importHintGoogleQrScan => 'Scannez le code QR que vous recevez lorsque vous exportez vos comptes depuis Google Authenticator'; @override - String get importHintGoogleQrFile => 'Sélectionnez un fichier image avec le code QR que vous obtenez lorsque vous exportez vos comptes depuis Google Authenticator.\n!! Notez qu\'il n\'est pas sûr d\'enregistrer le code QR sur votre appareil, car les jetons ne sont pas cryptés !!'; + String get importHintGoogleQrFile => + 'Sélectionnez un fichier image avec le code QR que vous obtenez lorsque vous exportez vos comptes depuis Google Authenticator.\n!! Notez qu\'il n\'est pas sûr d\'enregistrer le code QR sur votre appareil, car les jetons ne sont pas cryptés !!'; @override - String get importHintAuthenticatorProFile => 'Pour créer une sauvegarde de l\'application Authenticator Pro, accédez aux paramètres et appuyez sur \"Sauvegarde automatique\". Sélectionnez un emplacement de stockage et définissez un mot de passe. Puis appuyez sur \"Sauvegarder maintenant\" pour exporter les tokens.'; + String get importHintAuthenticatorProFile => + 'Pour créer une sauvegarde de l\'application Authenticator Pro, accédez aux paramètres et appuyez sur \"Sauvegarde automatique\". Sélectionnez un emplacement de stockage et définissez un mot de passe. Puis appuyez sur \"Sauvegarder maintenant\" pour exporter les tokens.'; @override - String get importHintFreeOtpPlusQrScan => 'Scannez le code QR que vous recevez lorsque vous appuyez sur les trois points dans la tuile du jeton et sélectionnez \"Partager le code QR\".'; + String get importHintFreeOtpPlusQrScan => + 'Scannez le code QR que vous recevez lorsque vous appuyez sur les trois points dans la tuile du jeton et sélectionnez \"Partager le code QR\".'; @override - String get importHintFreeOtpPlusFile => 'Pour créer une sauvegarde de l\'application FreeOTP+, appuyez sur les trois points dans le coin supérieur droit et sélectionnez \"Exporter\". Vous pouvez choisir entre les formats JSON et URI. Nous recommandons de supprimer la sauvegarde après l\'avoir importée, car elle n\'est pas cryptée.'; + String get importHintFreeOtpPlusFile => + 'Pour créer une sauvegarde de l\'application FreeOTP+, appuyez sur les trois points dans le coin supérieur droit et sélectionnez \"Exporter\". Vous pouvez choisir entre les formats JSON et URI. Nous recommandons de supprimer la sauvegarde après l\'avoir importée, car elle n\'est pas cryptée.'; @override - String get qrFileDecodeError => 'Il n\'a pas été possible de décoder le code QR à partir de l\'image sélectionnée, veuillez utiliser le scanner de code QR à la place'; + String get qrFileDecodeError => + 'Il n\'a pas été possible de décoder le code QR à partir de l\'image sélectionnée, veuillez utiliser le scanner de code QR à la place'; @override String get tokenLink => 'Lien vers le token'; @@ -700,7 +721,8 @@ class AppLocalizationsFr extends AppLocalizations { String get feedbackDescription => 'Si vous avez des questions, des suggestions ou des problèmes, n\'hésitez pas à nous en faire part'; @override - String get feedbackHint => 'Un e-mail prêt à l\'emploi s\'ouvre, que vous pouvez nous envoyer. Si vous le souhaitez, des informations sur votre appareil et la version de l\'application seront ajoutées. Vous pouvez vérifier et modifier l\'e-mail avant de l\'envoyer.'; + String get feedbackHint => + 'Un e-mail prêt à l\'emploi s\'ouvre, que vous pouvez nous envoyer. Si vous le souhaitez, des informations sur votre appareil et la version de l\'application seront ajoutées. Vous pouvez vérifier et modifier l\'e-mail avant de l\'envoyer.'; @override String get feedbackPrivacyPolicy1 => 'En envoyant le retour d\'information, vous acceptez notre '; @@ -730,7 +752,8 @@ class AppLocalizationsFr extends AppLocalizations { String get noMailAppTitle => 'Aucune application de messagerie trouvée'; @override - String get noMailAppDescription => 'Aucune application de messagerie n\'est installée ou initialisée sur cet appareil. Veuillez réessayer lorsque vous serez en mesure d\'envoyer un message électronique.'; + String get noMailAppDescription => + 'Aucune application de messagerie n\'est installée ou initialisée sur cet appareil. Veuillez réessayer lorsque vous serez en mesure d\'envoyer un message électronique.'; @override String get authenticationRequest => 'Authentification'; @@ -746,7 +769,8 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get pleaseSyncManuallyWhenNetworkIsAvailable => 'Veuillez synchroniser manuellement les jetons Push via les paramètres lorsqu\'une connexion réseau est disponible'; + String get pleaseSyncManuallyWhenNetworkIsAvailable => + 'Veuillez synchroniser manuellement les jetons Push via les paramètres lorsqu\'une connexion réseau est disponible'; @override String get pushTokens => 'Push Tokens'; @@ -877,5 +901,13 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get sendPushRequestResponseFailed => 'Échec de l\'envoi de la réponse.'; + String get pushEndpointUrl => 'URL de l\'endpoint Push'; + + @override + String get exampleUrl => 'Veuillez saisir une URL valide comme : \"https://example.com/\"'; + + @override + String mustNotBeEmpty(Object field) { + return '$field ne doit pas être vide'; + } } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index f238a1273..d12f5c5e6 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -22,7 +22,8 @@ class AppLocalizationsNl extends AppLocalizations { String get patchNotesV4_3_1Improvement1 => 'De QR-code scanner is verbeterd.'; @override - String get patchNotesV4_3_0NewFeatures1 => 'Ondersteuning toegevoegd voor het importeren van tokens van Google, Aegis en 2FAS Authenticator. Meer importbronnen zullen in de toekomst worden toegevoegd.'; + String get patchNotesV4_3_0NewFeatures1 => + 'Ondersteuning toegevoegd voor het importeren van tokens van Google, Aegis en 2FAS Authenticator. Meer importbronnen zullen in de toekomst worden toegevoegd.'; @override String get patchNotesV4_3_0NewFeatures2 => 'Feedbackoptie toegevoegd aan de instellingen.'; @@ -108,10 +109,12 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get confirmTokenDeletionHint => 'U kunt mogelijk niet meer inloggen als u dit token verwijdert. Controleer of u zonder dit token kunt inloggen op het gekoppelde account.'; + String get confirmTokenDeletionHint => + 'U kunt mogelijk niet meer inloggen als u dit token verwijdert. Controleer of u zonder dit token kunt inloggen op het gekoppelde account.'; @override - String get confirmFolderDeletionHint => 'Het verwijderen van een map heeft geen effect op de tokens in de map. De tokens worden verplaatst naar de hoofdlijst.'; + String get confirmFolderDeletionHint => + 'Het verwijderen van een map heeft geen effect op de tokens in de map. De tokens worden verplaatst naar de hoofdlijst.'; @override String get generatingPhonePart => 'Genereren telefoon gedeelte'; @@ -329,7 +332,8 @@ class AppLocalizationsNl extends AppLocalizations { String get send => 'verzenden'; @override - String get sendErrorLogDescription => 'Er wordt een kant-en-klare e-mail gemaakt die informatie bevat over de app, de fout en het apparaat.\nJe kunt de e-mail bewerken voordat je hem verstuurt.\nJe kunt hier zien hoe we de informatie gebruiken:'; + String get sendErrorLogDescription => + 'Er wordt een kant-en-klare e-mail gemaakt die informatie bevat over de app, de fout en het apparaat.\nJe kunt de e-mail bewerken voordat je hem verstuurt.\nJe kunt hier zien hoe we de informatie gebruiken:'; @override String get showPrivacyPolicy => 'Privacybeleid tonen'; @@ -356,7 +360,8 @@ class AppLocalizationsNl extends AppLocalizations { String get open => 'Openen'; @override - String get sendErrorDialogBody => 'Een onverwachte fout heeft plaatsgevonden in de applicatie. De onderstaande informatie kan worden verstuurd naar de ontwikkelaars via e-mail om het probleem in de toekomst te voorkomen.'; + String get sendErrorDialogBody => + 'Een onverwachte fout heeft plaatsgevonden in de applicatie. De onderstaande informatie kan worden verstuurd naar de ontwikkelaars via e-mail om het probleem in de toekomst te voorkomen.'; @override String get noFbToken => 'Geen Firebase Token beschikbaar'; @@ -480,7 +485,8 @@ class AppLocalizationsNl extends AppLocalizations { String get grantCameraPermissionDialogContent => 'Geef de camera toestemming om QR-codes te scannen.'; @override - String get grantCameraPermissionDialogPermanentlyDenied => 'Cameratoestemming is permanent geweigerd. Geef de camera toestemming in de instellingen van uw telefoon.'; + String get grantCameraPermissionDialogPermanentlyDenied => + 'Cameratoestemming is permanent geweigerd. Geef de camera toestemming in de instellingen van uw telefoon.'; @override String get grantCameraPermissionDialogButton => 'Toestemming verlenen'; @@ -489,7 +495,8 @@ class AppLocalizationsNl extends AppLocalizations { String get decryptErrorTitle => 'Fout bij decoderen'; @override - String get decryptErrorContent => 'Helaas heeft de app je tokens niet kunnen decoderen. Dit geeft aan dat de coderingssleutel is verbroken. U kunt het opnieuw proberen of de app-gegevens verwijderen, waardoor de tokens in de app worden verwijderd.'; + String get decryptErrorContent => + 'Helaas heeft de app je tokens niet kunnen decoderen. Dit geeft aan dat de coderingssleutel is verbroken. U kunt het opnieuw proberen of de app-gegevens verwijderen, waardoor de tokens in de app worden verwijderd.'; @override String get decryptErrorButtonDelete => 'Verwijderen'; @@ -507,7 +514,8 @@ class AppLocalizationsNl extends AppLocalizations { String get hidePushTokens => 'Verberg push tokens'; @override - String get hidePushTokensDescription => 'Verberg push tokens uit de token lijst. Hierdoor worden de tokens niet verwijderd en blijven ze zichtbaar op een apart scherm.'; + String get hidePushTokensDescription => + 'Verberg push tokens uit de token lijst. Hierdoor worden de tokens niet verwijderd en blijven ze zichtbaar op een apart scherm.'; @override String get settingsGroupGeneral => 'Algemene informatie'; @@ -519,7 +527,8 @@ class AppLocalizationsNl extends AppLocalizations { String get privacyPolicy => 'Privacybeleid'; @override - String get introScanQrCode => 'Je kunt QR-codes scannen om tokens toe te voegen.We ondersteunen alle gangbare Two-Factor-Authenticatie tokens en ook de privacyIDEA tokens.'; + String get introScanQrCode => + 'Je kunt QR-codes scannen om tokens toe te voegen.We ondersteunen alle gangbare Two-Factor-Authenticatie tokens en ook de privacyIDEA tokens.'; @override String get introAddTokenManually => 'Als je geen QR-code wilt scannen, kun je tokens ook handmatig toevoegen.'; @@ -531,7 +540,8 @@ class AppLocalizationsNl extends AppLocalizations { String get introEditToken => 'Hier kun je de naam van het token bewerken en enkele details bekijken.'; @override - String get introLockToken => 'Om de beveiliging nog meer te verbeteren, kun je tokens vergrendelen.¨Dan kan het token alleen gebruikt worden na authenticatie.'; + String get introLockToken => + 'Om de beveiliging nog meer te verbeteren, kun je tokens vergrendelen.¨Dan kan het token alleen gebruikt worden na authenticatie.'; @override String get introDragToken => 'Reorganiseer je tokens door er een paar seconden op te drukken en het dan naar de gewenste positie te slepen.'; @@ -551,7 +561,8 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get legacySigningErrorMessage => 'Het token is aangemaakt in een verouderde versie van de app, wat kan leiden tot problemen bij het gebruik ervan.\nHet wordt aanbevolen om een nieuw push token aan te maken als het probleem zich blijft voordoen!'; + String get legacySigningErrorMessage => + 'Het token is aangemaakt in een verouderde versie van de app, wat kan leiden tot problemen bij het gebruik ervan.\nHet wordt aanbevolen om een nieuw push token aan te maken als het probleem zich blijft voordoen!'; @override String get selectImportSource => 'Selecteer importbron'; @@ -658,10 +669,12 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get importHint2FAS => 'Selecteer uw 2FAS-back-up. Als u geen back-up hebt, maak er dan een aan in de 2FAS-app. Wij raden u aan een wachtwoord te gebruiken.'; + String get importHint2FAS => + 'Selecteer uw 2FAS-back-up. Als u geen back-up hebt, maak er dan een aan in de 2FAS-app. Wij raden u aan een wachtwoord te gebruiken.'; @override - String get importHintAegisBackupFile => 'Selecteer uw Aegis-export (.JSON).Als u geen export hebt, maak er dan een aan via het instellingenmenu in de Aegis-app. Het gebruik van een wachtwoord wordt aanbevolen.'; + String get importHintAegisBackupFile => + 'Selecteer uw Aegis-export (.JSON).Als u geen export hebt, maak er dan een aan via het instellingenmenu in de Aegis-app. Het gebruik van een wachtwoord wordt aanbevolen.'; @override String get importHintAegisQrScan => 'Scan de QR-code die u ontvangt bij het overbrengen van items uit Aegis.'; @@ -673,19 +686,24 @@ class AppLocalizationsNl extends AppLocalizations { String get importHintGoogleQrScan => 'Scan de QR-code die u ontvangt wanneer u uw accounts exporteert vanuit Google Authenticator.'; @override - String get importHintGoogleQrFile => 'Selecteer een afbeeldingsbestand met de QR-code die u ontvangt wanneer u uw accounts exporteert vanuit Google Authenticator.\n!! Let op: het is niet veilig om de QR-code op je apparaat op te slaan, omdat de tokens niet versleuteld zijn !!'; + String get importHintGoogleQrFile => + 'Selecteer een afbeeldingsbestand met de QR-code die u ontvangt wanneer u uw accounts exporteert vanuit Google Authenticator.\n!! Let op: het is niet veilig om de QR-code op je apparaat op te slaan, omdat de tokens niet versleuteld zijn !!'; @override - String get importHintAuthenticatorProFile => 'Om een back-up te maken van de Authenticator Pro app, navigeer je naar de instellingen en tik je op \"Auto back-up\". Selecteer een opslaglocatie en stel een wachtwoord in. Druk vervolgens op \"Nu back-uppen\" om de tokens te exporteren.'; + String get importHintAuthenticatorProFile => + 'Om een back-up te maken van de Authenticator Pro app, navigeer je naar de instellingen en tik je op \"Auto back-up\". Selecteer een opslaglocatie en stel een wachtwoord in. Druk vervolgens op \"Nu back-uppen\" om de tokens te exporteren.'; @override - String get importHintFreeOtpPlusQrScan => 'Scan de QR-code die u ontvangt wanneer u op de drie stippen in de tegel van de token drukt en selecteer \"QR-code delen\".'; + String get importHintFreeOtpPlusQrScan => + 'Scan de QR-code die u ontvangt wanneer u op de drie stippen in de tegel van de token drukt en selecteer \"QR-code delen\".'; @override - String get importHintFreeOtpPlusFile => 'Om een back-up van de FreeOTP+ app te maken, tikt u op de drie puntjes in de rechterbovenhoek en selecteert u \"Exporteren\". U kunt kiezen tussen JSON en URI formaat. We raden u aan de back-up te verwijderen na het importeren, omdat deze niet versleuteld is.'; + String get importHintFreeOtpPlusFile => + 'Om een back-up van de FreeOTP+ app te maken, tikt u op de drie puntjes in de rechterbovenhoek en selecteert u \"Exporteren\". U kunt kiezen tussen JSON en URI formaat. We raden u aan de back-up te verwijderen na het importeren, omdat deze niet versleuteld is.'; @override - String get qrFileDecodeError => 'Het was niet mogelijk om de QR code te decoderen van de geselecteerde afbeelding, gebruik in plaats daarvan de QR code scanner.'; + String get qrFileDecodeError => + 'Het was niet mogelijk om de QR code te decoderen van de geselecteerde afbeelding, gebruik in plaats daarvan de QR code scanner.'; @override String get tokenLink => 'tokenlink'; @@ -700,7 +718,8 @@ class AppLocalizationsNl extends AppLocalizations { String get feedbackDescription => 'Als je vragen, suggesties of problemen hebt, laat het ons dan weten.'; @override - String get feedbackHint => 'Er wordt een kant-en-klare e-mail geopend die je naar ons kunt sturen. Indien gewenst wordt informatie over je apparaat en de versie van de applicatie toegevoegd. U kunt de e-mail controleren en bewerken voordat u deze verzendt.'; + String get feedbackHint => + 'Er wordt een kant-en-klare e-mail geopend die je naar ons kunt sturen. Indien gewenst wordt informatie over je apparaat en de versie van de applicatie toegevoegd. U kunt de e-mail controleren en bewerken voordat u deze verzendt.'; @override String get feedbackPrivacyPolicy1 => 'Door feedback te sturen ga je akkoord met ons '; @@ -730,7 +749,8 @@ class AppLocalizationsNl extends AppLocalizations { String get noMailAppTitle => 'Geen mail app gevonden'; @override - String get noMailAppDescription => 'Er is geen e-mail app geïnstalleerd of geïnitialiseerd op dit apparaat, probeer het opnieuw wanneer u in staat bent om een e-mailbericht te verzenden.'; + String get noMailAppDescription => + 'Er is geen e-mail app geïnstalleerd of geïnitialiseerd op dit apparaat, probeer het opnieuw wanneer u in staat bent om een e-mailbericht te verzenden.'; @override String get authenticationRequest => 'Verificatieverzoek'; @@ -746,7 +766,8 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get pleaseSyncManuallyWhenNetworkIsAvailable => 'Synchroniseer de push tokens handmatig via de instellingen als er een netwerkverbinding beschikbaar is.'; + String get pleaseSyncManuallyWhenNetworkIsAvailable => + 'Synchroniseer de push tokens handmatig via de instellingen als er een netwerkverbinding beschikbaar is.'; @override String get pushTokens => 'Push Tokens'; @@ -877,5 +898,13 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get sendPushRequestResponseFailed => 'Het verzenden van het antwoord is mislukt. '; + String get pushEndpointUrl => 'Push Endpoint URL'; + + @override + String get exampleUrl => 'Voer een geldige URL in zoals: \"https://example.com/\"'; + + @override + String mustNotBeEmpty(Object field) { + return '$field mag niet leeg zijn'; + } } diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 6ef9aabba..e962f0c4b 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -22,7 +22,8 @@ class AppLocalizationsPl extends AppLocalizations { String get patchNotesV4_3_1Improvement1 => 'Został poprawiony skaner kodów QR.'; @override - String get patchNotesV4_3_0NewFeatures1 => 'Dodano obsługę importowania tokenów z Google, Aegis i 2FAS Authenticator. Więcej źródeł importu zostanie dodanych w przyszłości'; + String get patchNotesV4_3_0NewFeatures1 => + 'Dodano obsługę importowania tokenów z Google, Aegis i 2FAS Authenticator. Więcej źródeł importu zostanie dodanych w przyszłości'; @override String get patchNotesV4_3_0NewFeatures2 => 'Dodano opcję opinii do ustawień.'; @@ -108,7 +109,8 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get confirmTokenDeletionHint => 'Usunięcie tego tokenu może uniemożliwić zalogowanie się. Upewnij się, że możesz zalogować się na powiązane konto bez tego tokenu.'; + String get confirmTokenDeletionHint => + 'Usunięcie tego tokenu może uniemożliwić zalogowanie się. Upewnij się, że możesz zalogować się na powiązane konto bez tego tokenu.'; @override String get confirmFolderDeletionHint => 'Usunięcie folderu nie ma wpływu na znajdujące się w nim tokeny. Tokeny są przenoszone do głównej listy.'; @@ -267,7 +269,8 @@ class AppLocalizationsPl extends AppLocalizations { String get goToSettingsButton => 'Idź do ustawień'; @override - String get goToSettingsDescription => 'Ustawienia zabezpieczeń, bądź uwierzytelnianie biometryczne nie są skonfigurowane w twoim urządzeniu. Skonfiguruj je w ustawieniach urządzenia.'; + String get goToSettingsDescription => + 'Ustawienia zabezpieczeń, bądź uwierzytelnianie biometryczne nie są skonfigurowane w twoim urządzeniu. Skonfiguruj je w ustawieniach urządzenia.'; @override String get lockOut => 'Uwierzytelnianie biometryczne jest wyłączone. Zablokuj i odblokuj ponownie ekran, żeby je włączyć.'; @@ -329,7 +332,8 @@ class AppLocalizationsPl extends AppLocalizations { String get send => 'Wyślij'; @override - String get sendErrorLogDescription => 'Tworzona jest gotowa wiadomość e-mail zawierająca informacje o aplikacji, błędzie i urządzeniu.\nMożesz edytować wiadomość e-mail przed jej wysłaniem.\nTutaj można zobaczyć, w jaki sposób wykorzystujemy te informacje:'; + String get sendErrorLogDescription => + 'Tworzona jest gotowa wiadomość e-mail zawierająca informacje o aplikacji, błędzie i urządzeniu.\nMożesz edytować wiadomość e-mail przed jej wysłaniem.\nTutaj można zobaczyć, w jaki sposób wykorzystujemy te informacje:'; @override String get showPrivacyPolicy => 'Pokaż politykę prywatności'; @@ -356,7 +360,8 @@ class AppLocalizationsPl extends AppLocalizations { String get open => 'Otwórz'; @override - String get sendErrorDialogBody => 'Napotkano nieoczekiwany błąd w aplikacji. Poniższa wiadomość może zostać wysłana do deweloperów poprzez email, żeby pomóc uniknąć tego problemu w przyszłości.'; + String get sendErrorDialogBody => + 'Napotkano nieoczekiwany błąd w aplikacji. Poniższa wiadomość może zostać wysłana do deweloperów poprzez email, żeby pomóc uniknąć tego problemu w przyszłości.'; @override String get noFbToken => 'Brak dostępnego tokena Firebase'; @@ -480,7 +485,8 @@ class AppLocalizationsPl extends AppLocalizations { String get grantCameraPermissionDialogContent => 'Przyznaj uprawnienia kamery do skanowania kodów QR.'; @override - String get grantCameraPermissionDialogPermanentlyDenied => 'Uprawnienia do aparatu zostały trwale zablokowane. Przyznaj uprawnienia aparatu w ustawieniach telefonu.'; + String get grantCameraPermissionDialogPermanentlyDenied => + 'Uprawnienia do aparatu zostały trwale zablokowane. Przyznaj uprawnienia aparatu w ustawieniach telefonu.'; @override String get grantCameraPermissionDialogButton => 'Grant permission'; @@ -489,7 +495,8 @@ class AppLocalizationsPl extends AppLocalizations { String get decryptErrorTitle => 'Decryption error'; @override - String get decryptErrorContent => 'Niestety, aplikacja nie była w stanie odszyfrować tokenów. Oznacza to, że klucz szyfrowania jest uszkodzony. Możesz spróbować ponownie lub usunąć dane aplikacji, co spowoduje usunięcie tokenów w aplikacji.'; + String get decryptErrorContent => + 'Niestety, aplikacja nie była w stanie odszyfrować tokenów. Oznacza to, że klucz szyfrowania jest uszkodzony. Możesz spróbować ponownie lub usunąć dane aplikacji, co spowoduje usunięcie tokenów w aplikacji.'; @override String get decryptErrorButtonDelete => 'Usuń'; @@ -519,7 +526,8 @@ class AppLocalizationsPl extends AppLocalizations { String get privacyPolicy => 'Polityka prywatności'; @override - String get introScanQrCode => 'Możesz skanować kody QR, aby dodawać tokeny. Obsługujemy każdy popularny token uwierzytelniania dwuskładnikowego, a także tokeny privacyIDEA.'; + String get introScanQrCode => + 'Możesz skanować kody QR, aby dodawać tokeny. Obsługujemy każdy popularny token uwierzytelniania dwuskładnikowego, a także tokeny privacyIDEA.'; @override String get introAddTokenManually => 'Jeśli nie chcesz skanować kodu QR, możesz również dodać tokeny ręcznie.'; @@ -531,7 +539,8 @@ class AppLocalizationsPl extends AppLocalizations { String get introEditToken => 'Tutaj możesz edytować nazwę tokena i zobaczyć kilka szczegółów.'; @override - String get introLockToken => 'Aby jeszcze bardziej zwiększyć bezpieczeństwo, możesz zablokować tokeny. Wtedy token może być używany tylko po uwierzytelnieniu.'; + String get introLockToken => + 'Aby jeszcze bardziej zwiększyć bezpieczeństwo, możesz zablokować tokeny. Wtedy token może być używany tylko po uwierzytelnieniu.'; @override String get introDragToken => 'Reorganizuj swoje tokeny, naciskając je przez kilka sekund, a następnie przeciągając je do żądanej pozycji.'; @@ -551,7 +560,8 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get legacySigningErrorMessage => 'Token został utworzony w nieaktualnej wersji aplikacji, co może prowadzić do problemów podczas korzystania z niego.\nZaleca się utworzenie nowego tokena push, jeśli problem nadal występuje!'; + String get legacySigningErrorMessage => + 'Token został utworzony w nieaktualnej wersji aplikacji, co może prowadzić do problemów podczas korzystania z niego.\nZaleca się utworzenie nowego tokena push, jeśli problem nadal występuje!'; @override String get selectImportSource => 'Wybierz źródło importu'; @@ -661,7 +671,8 @@ class AppLocalizationsPl extends AppLocalizations { String get importHint2FAS => 'Wybierz kopię zapasową 2FAS. Jeśli nie masz kopii zapasowej, utwórz ją w aplikacji 2FAS. Zalecamy użycie hasła.'; @override - String get importHintAegisBackupFile => 'Wybierz swój eksport Aegis (.JSON).\nJeśli nie masz eksportu, utwórz go za pomocą menu ustawień w aplikacji Aegis. Zalecane jest użycie hasła.'; + String get importHintAegisBackupFile => + 'Wybierz swój eksport Aegis (.JSON).\nJeśli nie masz eksportu, utwórz go za pomocą menu ustawień w aplikacji Aegis. Zalecane jest użycie hasła.'; @override String get importHintAegisQrScan => 'Zeskanuj kod QR otrzymany podczas przesyłania wpisów z Aegis'; @@ -673,16 +684,19 @@ class AppLocalizationsPl extends AppLocalizations { String get importHintGoogleQrScan => 'Zeskanuj kod QR otrzymany podczas eksportowania kont z Google Authenticator'; @override - String get importHintGoogleQrFile => 'Wybierz plik obrazu z kodem QR otrzymanym podczas eksportowania kont z Google Authenticator.\n!! Należy pamiętać, że zapisywanie kodu QR na urządzeniu nie jest bezpieczne, ponieważ tokeny nie są szyfrowane !!'; + String get importHintGoogleQrFile => + 'Wybierz plik obrazu z kodem QR otrzymanym podczas eksportowania kont z Google Authenticator.\n!! Należy pamiętać, że zapisywanie kodu QR na urządzeniu nie jest bezpieczne, ponieważ tokeny nie są szyfrowane !!'; @override - String get importHintAuthenticatorProFile => 'Aby utworzyć kopię zapasową aplikacji Authenticator Pro, przejdź do ustawień i dotknij \"Automatyczna kopia zapasowa\". Wybierz lokalizację przechowywania i ustaw hasło. Następnie naciśnij \"Utwórz teraz kopię zapasową\", aby wyeksportować tokeny.'; + String get importHintAuthenticatorProFile => + 'Aby utworzyć kopię zapasową aplikacji Authenticator Pro, przejdź do ustawień i dotknij \"Automatyczna kopia zapasowa\". Wybierz lokalizację przechowywania i ustaw hasło. Następnie naciśnij \"Utwórz teraz kopię zapasową\", aby wyeksportować tokeny.'; @override String get importHintFreeOtpPlusQrScan => 'Zeskanuj kod QR otrzymany po naciśnięciu trzech kropek na kafelku tokena i wybierz \"Udostępnij kod QR\".'; @override - String get importHintFreeOtpPlusFile => 'Aby utworzyć kopię zapasową aplikacji FreeOTP+, dotknij trzech kropek w prawym górnym rogu i wybierz \"Eksportuj\". Można wybrać format JSON lub URI. Zalecamy usunięcie kopii zapasowej po jej zaimportowaniu, ponieważ nie jest ona szyfrowana.'; + String get importHintFreeOtpPlusFile => + 'Aby utworzyć kopię zapasową aplikacji FreeOTP+, dotknij trzech kropek w prawym górnym rogu i wybierz \"Eksportuj\". Można wybrać format JSON lub URI. Zalecamy usunięcie kopii zapasowej po jej zaimportowaniu, ponieważ nie jest ona szyfrowana.'; @override String get qrFileDecodeError => 'Nie można było zdekodować kodu QR z wybranego obrazu, zamiast tego użyj skanera kodów QR.'; @@ -700,7 +714,8 @@ class AppLocalizationsPl extends AppLocalizations { String get feedbackDescription => 'Jeśli masz jakieś pytania, sugestie lub problemy, daj nam znać.'; @override - String get feedbackHint => 'Otworzy się gotowa wiadomość e-mail, którą możesz do nas wysłać. W razie potrzeby dodane zostaną informacje o urządzeniu i wersji aplikacji. Możesz sprawdzić i edytować wiadomość e-mail przed jej wysłaniem.'; + String get feedbackHint => + 'Otworzy się gotowa wiadomość e-mail, którą możesz do nas wysłać. W razie potrzeby dodane zostaną informacje o urządzeniu i wersji aplikacji. Możesz sprawdzić i edytować wiadomość e-mail przed jej wysłaniem.'; @override String get feedbackPrivacyPolicy1 => 'Wysyłając opinię, zgadzasz się z naszą '; @@ -730,7 +745,8 @@ class AppLocalizationsPl extends AppLocalizations { String get noMailAppTitle => 'Nie znaleziono aplikacji pocztowej'; @override - String get noMailAppDescription => 'Na tym urządzeniu nie zainstalowano ani nie zainicjowano aplikacji poczty e-mail, spróbuj ponownie, gdy będziesz w stanie wysłać wiadomość e-mail'; + String get noMailAppDescription => + 'Na tym urządzeniu nie zainstalowano ani nie zainicjowano aplikacji poczty e-mail, spróbuj ponownie, gdy będziesz w stanie wysłać wiadomość e-mail'; @override String get authenticationRequest => 'Żądanie uwierzytelnienia'; @@ -877,5 +893,13 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get sendPushRequestResponseFailed => 'Nie udało się wysłać odpowiedzi.'; + String get pushEndpointUrl => 'Adres URL punktu końcowego push'; + + @override + String get exampleUrl => 'Wprowadź prawidłowy adres URL, np: \"https://example.com/\"'; + + @override + String mustNotBeEmpty(Object field) { + return '$field nie może być puste'; + } } diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 8333b1480..799b4e33e 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -633,6 +633,16 @@ "oneMore": "Nog een", "done": "Klaar", "confirmPassword": "Wachtwoord bevestigen", + "exampleUrl": "Voer een geldige URL in zoals: \"https://example.com/\"", + "pushEndpointUrl": "Push Endpoint URL", + "mustNotBeEmpty": "{field} mag niet leeg zijn", + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, "sendPushRequestResponseFailed": "Het verzenden van het antwoord is mislukt. ", "@sendPushRequestResponseFailed": { "description": "Error message when the response to a push request could not be sent." diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 5c9ead6bc..cc4a23449 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -630,6 +630,16 @@ "oneMore": "Jeszcze jeden", "done": "Gotowe", "confirmPassword": "Potwierdź hasło", + "exampleUrl": "Wprowadź prawidłowy adres URL, np: \"https://example.com/\"", + "pushEndpointUrl": "Adres URL punktu końcowego push", + "mustNotBeEmpty": "{field} nie może być puste", + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, "sendPushRequestResponseFailed": "Nie udało się wysłać odpowiedzi.", "@sendPushRequestResponseFailed": { "description": "Error message when the response to a push request could not be sent." diff --git a/lib/model/extensions/enums/token_origin_source_type.dart b/lib/model/extensions/enums/token_origin_source_type.dart index 03220acfa..9454d5a48 100644 --- a/lib/model/extensions/enums/token_origin_source_type.dart +++ b/lib/model/extensions/enums/token_origin_source_type.dart @@ -1,17 +1,62 @@ -import '../../../mains/main_netknights.dart'; +import 'package:privacyidea_authenticator/utils/utils.dart'; + import '../../enums/token_origin_source_type.dart'; import '../../token_import/token_origin_data.dart'; import '../../tokens/token.dart'; +import '../../version.dart'; extension TokenSourceTypeX on TokenOriginSourceType { - TokenOriginData toTokenOrigin({String data = '', String? appName, bool? isPrivacyIdeaToken, DateTime? createdAt}) => TokenOriginData( + TokenOriginData _toTokenOrigin({ + String data = '', + String? originName, + bool? isPrivacyIdeaToken, + DateTime? createdAt, + String? creator, + Version? piServerVersion, + }) => + TokenOriginData( source: this, data: data, - appName: appName ?? PrivacyIDEAAuthenticator.currentCustomization?.appName, + appName: originName ?? getCurrentAppName(), isPrivacyIdeaToken: isPrivacyIdeaToken, createdAt: createdAt ?? DateTime.now(), + creator: creator, + piServerVersion: piServerVersion, ); - T addOriginToToken({required T token, required String data, required bool? isPrivacyIdeaToken, String? appName, DateTime? createdAt}) => - token.copyWith(origin: toTokenOrigin(data: data, appName: appName, isPrivacyIdeaToken: isPrivacyIdeaToken, createdAt: createdAt)) as T; + TokenOriginData toTokenOrigin({ + String data = '', + String? originName, + bool? isPrivacyIdeaToken, + DateTime? createdAt, + String? creator, + Version? piServerVersion, + }) => + _toTokenOrigin( + data: data, + originName: originName, + isPrivacyIdeaToken: isPrivacyIdeaToken, + createdAt: createdAt, + creator: creator, + piServerVersion: piServerVersion, + ); + + T addOriginToToken({ + required T token, + required String data, + required bool? isPrivacyIdeaToken, + String? appName, + DateTime? createdAt, + String? creator, + Version? piServerVersion, + }) => + token.copyWith( + origin: _toTokenOrigin( + data: data, + originName: appName, + isPrivacyIdeaToken: isPrivacyIdeaToken, + createdAt: createdAt, + creator: creator, + piServerVersion: piServerVersion, + )) as T; } diff --git a/lib/model/token_import/token_origin_data.dart b/lib/model/token_import/token_origin_data.dart index 8f02a54fa..37c8ca72e 100644 --- a/lib/model/token_import/token_origin_data.dart +++ b/lib/model/token_import/token_origin_data.dart @@ -8,66 +8,81 @@ part 'token_origin_data.g.dart'; @JsonSerializable() class TokenOriginData { final TokenOriginSourceType source; - final String? appName; - final String data; - final bool? isPrivacyIdeaToken; - final DateTime? createdAt; - final Version? piServerVersion; + final String appName; // Name of the app where the token comes from. + final String data; // The data that was used to create the token. Contains the secret!! + final DateTime createdAt; // The time when the token was created. If imported from another app, this is the time of the import + final bool? isPrivacyIdeaToken; // True if the token was created by a privacyIDEA server. Null if unknown. False if not created by a privacyIDEA server + final String? creator; // like issuer, but only for privacyIDEA servers. This is only set if the token was created by a privacyIDEA server + final Version? + piServerVersion; // The version of the privacyIDEA server that created the token. This is only set if the token was created by a privacyIDEA server const TokenOriginData._({ required this.source, + required this.appName, required this.data, - this.appName, + required this.createdAt, this.isPrivacyIdeaToken, - this.createdAt, + this.creator, this.piServerVersion, }); factory TokenOriginData({ required TokenOriginSourceType source, + required String appName, required String data, - String? appName, - bool? isPrivacyIdeaToken, DateTime? createdAt, + bool? isPrivacyIdeaToken, + String? creator, Version? piServerVersion, }) => TokenOriginData._( source: source, appName: appName, data: data, - isPrivacyIdeaToken: isPrivacyIdeaToken, createdAt: createdAt ?? DateTime.now(), + isPrivacyIdeaToken: isPrivacyIdeaToken, + creator: creator, piServerVersion: piServerVersion, ); @override bool operator ==(Object other) { if (identical(this, other)) return true; - return other is TokenOriginData && other.source == source && other.appName == appName && other.data == data; + return other is TokenOriginData && + other.source == source && + other.appName == appName && + other.createdAt == createdAt && + other.data == data && + other.isPrivacyIdeaToken == isPrivacyIdeaToken && + other.creator == creator && + other.piServerVersion == piServerVersion; } TokenOriginData copyWith({ TokenOriginSourceType? source, String? data, String? appName, - bool? isPrivacyIdeaToken, DateTime? createdAt, - Version? piServerVersion, + bool? Function()? isPrivacyIdeaToken, + String? Function()? creator, + Version? Function()? piServerVersion, }) => TokenOriginData( source: source ?? this.source, - appName: appName ?? this.appName, data: data ?? this.data, - isPrivacyIdeaToken: isPrivacyIdeaToken ?? this.isPrivacyIdeaToken, + appName: appName ?? this.appName, createdAt: createdAt ?? this.createdAt, - piServerVersion: piServerVersion ?? this.piServerVersion, + isPrivacyIdeaToken: isPrivacyIdeaToken != null ? isPrivacyIdeaToken() : this.isPrivacyIdeaToken, + creator: creator != null ? creator() : this.creator, + piServerVersion: piServerVersion != null ? piServerVersion() : this.piServerVersion, ); @override - int get hashCode => Object.hashAll([source, appName, data]); + int get hashCode => Object.hashAll([source, data, appName, isPrivacyIdeaToken, creator, createdAt, piServerVersion]); // toString prints not data because it contains the secret @override - String toString() => 'TokenOrigin{source: $source, app: $appName, isPrivacyIdeaToken: $isPrivacyIdeaToken, createdAt: $createdAt, data: $data'; + String toString() => + 'TokenOrigin{source: $source, appName: $appName, isPrivacyIdeaToken: $isPrivacyIdeaToken, creator: $creator, createdAt: $createdAt, piServerVersion: $piServerVersion}'; factory TokenOriginData.fromJson(Map json) => _$TokenOriginDataFromJson(json); diff --git a/lib/model/token_import/token_origin_data.g.dart b/lib/model/token_import/token_origin_data.g.dart index 07d913023..03b83af3f 100644 --- a/lib/model/token_import/token_origin_data.g.dart +++ b/lib/model/token_import/token_origin_data.g.dart @@ -9,12 +9,13 @@ part of 'token_origin_data.dart'; TokenOriginData _$TokenOriginDataFromJson(Map json) => TokenOriginData( source: $enumDecode(_$TokenOriginSourceTypeEnumMap, json['source']), + appName: json['appName'] as String, data: json['data'] as String, - appName: json['appName'] as String?, - isPrivacyIdeaToken: json['isPrivacyIdeaToken'] as bool?, createdAt: json['createdAt'] == null ? null : DateTime.parse(json['createdAt'] as String), + isPrivacyIdeaToken: json['isPrivacyIdeaToken'] as bool?, + creator: json['creator'] as String?, piServerVersion: json['piServerVersion'] == null ? null : Version.fromJson(json['piServerVersion'] as Map), @@ -25,8 +26,9 @@ Map _$TokenOriginDataToJson(TokenOriginData instance) => 'source': _$TokenOriginSourceTypeEnumMap[instance.source]!, 'appName': instance.appName, 'data': instance.data, + 'createdAt': instance.createdAt.toIso8601String(), 'isPrivacyIdeaToken': instance.isPrivacyIdeaToken, - 'createdAt': instance.createdAt?.toIso8601String(), + 'creator': instance.creator, 'piServerVersion': instance.piServerVersion, }; diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index 6d55e8cea..03b868627 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -39,9 +39,7 @@ abstract class Token with SortableMixin { if (TokenTypes.STEAM.isName(type, caseSensitive: false)) return SteamToken.fromJson(json); throw ArgumentError.value(json, 'Token#fromJson', 'Token type [$type] is not a supported'); } - factory Token.fromUriMap( - Map uriMap, - ) { + factory Token.fromUriMap(Map uriMap) { String type = uriMap[URI_TYPE]; if (TokenTypes.HOTP.isName(type, caseSensitive: false)) return HOTPToken.fromUriMap(uriMap); if (TokenTypes.TOTP.isName(type, caseSensitive: false)) return TOTPToken.fromUriMap(uriMap); diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor.dart b/lib/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor.dart index 6183f4ccb..089239a31 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor.dart @@ -12,12 +12,8 @@ class FreeOtpPlusQrProcessor extends OtpAuthProcessor { Future>> processUri(Uri uri, {bool fromInit = false}) => _processOtpAuth(uri); Future>> _processOtpAuth(Uri uri) async { - final results = >[]; - - final result = await super.processUri(uri); - results.addAll(result); - - return results.map((t) { + final results = (await super.processUri(uri)).toList(); + final resultsWithOrigin = results.map((t) { if (t is! ProcessorResultSuccess) return t; return ProcessorResultSuccess( TokenOriginSourceType.qrScanImport.addOriginToToken( @@ -28,6 +24,7 @@ class FreeOtpPlusQrProcessor extends OtpAuthProcessor { ), ); }).toList(); + return resultsWithOrigin; } @override diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart b/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart index 0ac271a78..b1b3e24df 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart @@ -109,7 +109,7 @@ class GoogleAuthenticatorQrProcessor extends TokenImportSchemeProcessor { } } - return results.map((t) { + final resultsWithOrigin = results.map((t) { if (t is! ProcessorResultSuccess) return t; return ProcessorResultSuccess( TokenOriginSourceType.qrScanImport.addOriginToToken( @@ -120,5 +120,6 @@ class GoogleAuthenticatorQrProcessor extends TokenImportSchemeProcessor { ), ); }).toList(); + return resultsWithOrigin; } } diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart b/lib/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart index 84895051f..a8203d57b 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart @@ -1,9 +1,10 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; -import '../../../model/enums/token_origin_source_type.dart'; -import '../../../model/token_import/token_origin_data.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; +import 'package:privacyidea_authenticator/model/token_import/token_origin_data.dart'; +import '../../../model/enums/token_origin_source_type.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/algorithms.dart'; import '../../../model/enums/encodings.dart'; @@ -16,8 +17,10 @@ import '../../../utils/errors.dart'; import '../../../utils/globals.dart'; import '../../../utils/identifiers.dart'; import '../../../utils/logger.dart'; +import '../../../utils/utils.dart' show getCurrentAppName; import '../../../utils/view_utils.dart'; import '../../../widgets/dialog_widgets/two_step_dialog.dart'; + import 'token_import_scheme_processor_interface.dart'; class OtpAuthProcessor extends TokenImportSchemeProcessor { @@ -67,8 +70,7 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { } Token newToken; try { - newToken = - Token.fromUriMap(uriMap).copyWith(origin: TokenOriginData(source: TokenOriginSourceType.link, data: uri.toString(), createdAt: DateTime.now())); + newToken = Token.fromUriMap(uriMap); } on FormatException catch (e) { Logger.warning('Error while parsing otpAuth.', name: 'token_notifier.dart#addTokenFromOtpAuth', error: e); return [ProcessorResultFailed(e.message)]; @@ -102,14 +104,13 @@ Map _parseOtpToken(Uri uri) { const String _steamTokenIssuer = "Steam"; Map _parseOtpAuth(Uri uri) { // otpauth://TYPE/LABEL?PARAMETERS - Map uriMap = {}; - + final Map uriMap = {}; // parse.host -> Type totp or hotp uriMap[URI_TYPE] = uri.host; - // parse.path.substring(1) -> Label - String infoLog = '\nKey: [..] | Value: [..]'; - uri.queryParameters.forEach((key, value) { + var infoLog = '\nKey: [..] | Value: [..]'; + final queryParameters = uri.queryParameters; + queryParameters.forEach((key, value) { if (key == URI_SECRET || key.toLowerCase().contains('secret')) { value = '********'; } @@ -128,21 +129,21 @@ Map _parseOtpAuth(Uri uri) { } // parse pin from response 'True' - if (uri.queryParameters['pin'] == 'True') { + if (queryParameters['pin'] == 'True') { uriMap[URI_PIN] = true; } - if (uri.queryParameters['image'] != null) { - uriMap[URI_IMAGE] = uri.queryParameters['image']; + if (queryParameters['image'] != null) { + uriMap[URI_IMAGE] = queryParameters['image']; } - String algorithm = uri.queryParameters['algorithm'] ?? Algorithms.SHA1.name; // Optional parameter + String algorithm = queryParameters['algorithm'] ?? Algorithms.SHA1.name; // Optional parameter algorithm = Algorithms.values.byName(algorithm).name; // Validate algorithm, throw error if not supported. uriMap[URI_ALGORITHM] = algorithm; // Parse digits. - String digitsAsString = uri.queryParameters['digits'] ?? '6'; // Optional parameter + String digitsAsString = queryParameters['digits'] ?? '6'; // Optional parameter if (digitsAsString != '6' && digitsAsString != '8') { throw LocalizedArgumentError( @@ -158,7 +159,7 @@ Map _parseOtpAuth(Uri uri) { uriMap[URI_DIGITS] = digits; // Parse secret. - String? secretAsString = uri.queryParameters['secret']; + String? secretAsString = queryParameters['secret']; ArgumentError.checkNotNull(secretAsString, 'secret'); // This is a fix for omitted padding in base32 encoded secrets. @@ -181,7 +182,7 @@ Map _parseOtpAuth(Uri uri) { uriMap[URI_SECRET] = secret; // Parse counter. - String? counterString = uri.queryParameters['counter']; + String? counterString = queryParameters['counter']; if (counterString != null) { uriMap[URI_COUNTER] = int.tryParse(counterString); if (uriMap[URI_COUNTER] == null) { @@ -195,7 +196,7 @@ Map _parseOtpAuth(Uri uri) { } // Parse period. - String? periodString = uri.queryParameters['period']; + String? periodString = queryParameters['period']; if (periodString != null) { uriMap[URI_PERIOD] = int.tryParse(periodString); if (uriMap[URI_PERIOD] == null) { @@ -212,15 +213,19 @@ Map _parseOtpAuth(Uri uri) { uriMap.addAll(_parse2StepURI(uri)); } + // Parse creator. + uriMap[URI_ORIGIN] = _parseCreatorToOrigin(uri); + return uriMap; } Map _parse2StepURI(Uri uri) { - Map uriMap2Step = {}; + final Map uriMap2Step = {}; + final queryParameters = uri.queryParameters; // Parse for 2 step roll out. - String saltLengthAsString = uri.queryParameters['2step_salt'] ?? '10'; - String outputLengthInByteAsString = uri.queryParameters['2step_output'] ?? '20'; - String iterationsAsString = uri.queryParameters['2step_difficulty'] ?? '10000'; + final String saltLengthAsString = queryParameters['2step_salt'] ?? '10'; + final String outputLengthInByteAsString = queryParameters['2step_output'] ?? '20'; + final String iterationsAsString = queryParameters['2step_difficulty'] ?? '10000'; // Parse parameters try { @@ -266,12 +271,13 @@ Map _parsePiPushToken(Uri uri) { // &serial=PIPU0006EF87 // &sslverify=1 - Map uriMap = {}; + final Map uriMap = {}; + final queryParameters = uri.queryParameters; uriMap[URI_TYPE] = uri.host; // If we do not support the version of this piauth url, we can stop here. - String? pushVersionAsString = uri.queryParameters['v']; + String? pushVersionAsString = queryParameters['v']; if (pushVersionAsString == null) { throw LocalizedArgumentError( @@ -304,17 +310,17 @@ Map _parsePiPushToken(Uri uri) { ); } - if (uri.queryParameters['image'] != null) { - uriMap[URI_IMAGE] = uri.queryParameters['image']; + if (queryParameters['image'] != null) { + uriMap[URI_IMAGE] = queryParameters['image']; } final (label, issuer) = _parseLabelAndIssuer(uri); uriMap[URI_LABEL] = label; uriMap[URI_ISSUER] = issuer; - uriMap[URI_SERIAL] = uri.queryParameters['serial']; + uriMap[URI_SERIAL] = queryParameters['serial']; ArgumentError.checkNotNull(uriMap[URI_SERIAL], 'serial'); - final String? url = uri.queryParameters['url']; + final String? url = queryParameters['url']; ArgumentError.checkNotNull(url, 'url'); try { uriMap[URI_ROLLOUT_URL] = Uri.parse(url!); @@ -327,7 +333,7 @@ Map _parsePiPushToken(Uri uri) { ); } - String ttlAsString = uri.queryParameters['ttl'] ?? '10'; + String ttlAsString = queryParameters['ttl'] ?? '10'; try { uriMap[URI_TTL] = int.parse(ttlAsString); } on FormatException { @@ -339,19 +345,34 @@ Map _parsePiPushToken(Uri uri) { ); } - uriMap[URI_ENROLLMENT_CREDENTIAL] = uri.queryParameters['enrollment_credential']; + uriMap[URI_ENROLLMENT_CREDENTIAL] = queryParameters['enrollment_credential']; ArgumentError.checkNotNull(uriMap[URI_ENROLLMENT_CREDENTIAL], 'enrollment_credential'); - uriMap[URI_SSL_VERIFY] = (uri.queryParameters['sslverify'] ?? '1') == '1'; + uriMap[URI_SSL_VERIFY] = (queryParameters['sslverify'] ?? '1') == '1'; // parse pin from response 'True' - if (uri.queryParameters['pin'] == 'True') { + if (queryParameters['pin'] == 'True') { uriMap[URI_PIN] = true; } + // Parse creator. + uriMap[URI_ORIGIN] = _parseCreatorToOrigin(uri); + return uriMap; } +TokenOriginData _parseCreatorToOrigin(Uri uri) { + final origin = TokenOriginSourceType.unknown.toTokenOrigin( + data: uri.toString(), + originName: getCurrentAppName(), + // If creator is present, it is a privacyIDEA token. If not it could be from an old version of the server too. + isPrivacyIdeaToken: uri.queryParameters['creator'] != null ? true : null, + creator: uri.queryParameters['creator'], + createdAt: DateTime.now(), + ); + return origin; +} + /// Parse the label and the issuer (if it exists) from the url. (String, String) _parseLabelAndIssuer(Uri uri) { String label = ''; @@ -389,5 +410,6 @@ String _parseIssuer(Uri uri) { } bool _is2StepURI(Uri uri) { - return uri.queryParameters['2step_salt'] != null || uri.queryParameters['2step_output'] != null || uri.queryParameters['2step_difficulty'] != null; + final queryParameters = uri.queryParameters; + return queryParameters['2step_salt'] != null || queryParameters['2step_output'] != null || queryParameters['2step_difficulty'] != null; } diff --git a/lib/processors/token_import_file_processor/aegis_import_file_processor.dart b/lib/processors/token_import_file_processor/aegis_import_file_processor.dart index 44943e9b2..eb428c2ce 100644 --- a/lib/processors/token_import_file_processor/aegis_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/aegis_import_file_processor.dart @@ -159,7 +159,7 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { URI_COUNTER: info[AEGIS_COUNTER], URI_PIN: info[AEGIS_PIN], URI_ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( - appName: TokenImportOrigins.aegisAuthenticator.appName, + originName: TokenImportOrigins.aegisAuthenticator.appName, isPrivacyIdeaToken: false, data: jsonEncode(entry), ), @@ -200,7 +200,7 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { URI_COUNTER: info[AEGIS_COUNTER], URI_PIN: info[AEGIS_PIN], URI_ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( - appName: TokenImportOrigins.aegisAuthenticator.appName, + originName: TokenImportOrigins.aegisAuthenticator.appName, isPrivacyIdeaToken: false, data: jsonEncode(entry), ), diff --git a/lib/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart b/lib/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart index 92ed3bfde..f8fe04004 100644 --- a/lib/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart @@ -295,7 +295,7 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { URI_ALGORITHM: algorithmMap[tokenMap[_AUTHENTICATOR_PRO_ALGORITHM] as int], URI_COUNTER: tokenMap[_AUTHENTICATOR_PRO_COUNTER] as int, URI_ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( - appName: TokenImportOrigins.authenticatorPro.appName, + originName: TokenImportOrigins.authenticatorPro.appName, isPrivacyIdeaToken: false, data: jsonEncode(tokenMap), ), diff --git a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart index c99d786ad..922262da5 100644 --- a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart @@ -118,7 +118,7 @@ class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { URI_COUNTER: tokenJson[_FREE_OTP_PLUS_COUNTER] + 1, // FreeOTP+ saves only in JSON as 0-based counter URI_PERIOD: tokenJson[_FREE_OTP_PLUS_PERIOD], URI_ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( - appName: TokenImportOrigins.freeOtpPlus.appName, + originName: TokenImportOrigins.freeOtpPlus.appName, isPrivacyIdeaToken: false, data: jsonEncode(tokenJson), ), diff --git a/lib/processors/token_import_file_processor/two_fas_import_file_processor.dart b/lib/processors/token_import_file_processor/two_fas_import_file_processor.dart index 046fd69ac..dc225f4ba 100644 --- a/lib/processors/token_import_file_processor/two_fas_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/two_fas_import_file_processor.dart @@ -150,7 +150,7 @@ class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { URI_PERIOD: twoFasOTP[TWOFAS_PERIOD], URI_COUNTER: twoFasOTP[TWOFAS_COUNTER], URI_ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( - appName: TokenImportOrigins.twoFasAuthenticator.appName, + originName: TokenImportOrigins.twoFasAuthenticator.appName, isPrivacyIdeaToken: false, data: jsonEncode(twoFasToken), ), diff --git a/lib/state_notifiers/token_notifier.dart b/lib/state_notifiers/token_notifier.dart index 7be86f3e2..8baf4e905 100644 --- a/lib/state_notifiers/token_notifier.dart +++ b/lib/state_notifiers/token_notifier.dart @@ -641,13 +641,13 @@ class TokenNotifier extends StateNotifier { return (failedTokens, unsuportedTokens); } - ///////////////////////////////////////////////////////////////////////////// - //////////////////////// Add New Tokens Methods ///////////////////////////// - ///////////////////////////////////////////////////////////////////////////// - /// Does not need to wait for updating functions because they doesn't depend on any state + /* //////////////////////////////////////////////////////////////////////////// + ///////////////////////// Add New Tokens Methods ////////////////////////////// + /////////////////////////////////////////////////////////////////////////////// + /// Does not need to wait for updating functions because they doesn't depend on any state */ - // The return value of a qrCode could be any object. In this case should be a String that is a valid URI. - // If it is not a valid URI, the user will be informed. + /// The return value of a qrCode could be any object. In this case should be a String that is a valid URI. + /// If it is not a valid URI, the user will be informed. Future handleQrCode(Object? qrCode) async { Uri uri; try { @@ -659,14 +659,22 @@ class TokenNotifier extends StateNotifier { return; } List tokens = await _tokensFromUri(uri); - tokens = tokens.map((e) => TokenOriginSourceType.qrScan.addOriginToToken(token: e, data: qrCode, isPrivacyIdeaToken: null)).toList(); + tokens = tokens + .map((e) => e.copyWith( + origin: e.origin?.copyWith(source: TokenOriginSourceType.qrScan) ?? + TokenOriginSourceType.qrScan.toTokenOrigin(data: uri.toString(), isPrivacyIdeaToken: null))) + .toList(); await _addOrReplaceTokens(tokens); await _handlePushTokensIfExist(); } Future handleLink(Uri uri) async { List tokens = await _tokensFromUri(uri); - tokens = tokens.map((e) => TokenOriginSourceType.link.addOriginToToken(token: e, data: uri.toString(), isPrivacyIdeaToken: null)).toList(); + tokens = tokens + .map((e) => e.copyWith( + origin: e.origin?.copyWith(source: TokenOriginSourceType.link) ?? + TokenOriginSourceType.link.toTokenOrigin(data: uri.toString(), isPrivacyIdeaToken: null))) + .toList(); await _addOrReplaceTokens(tokens); await _handlePushTokensIfExist(); } @@ -682,9 +690,9 @@ class TokenNotifier extends StateNotifier { } } - ////////////////////////////////////////////////////////////////////////////// - ///////////////////////// Helper Methods ///////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////// + /* ///////////////////////////////////////////////////////////////////////////// + /////////////////////////// Helper Methods ///////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////// */ Future _parseRollOutResponse(Response response) async { Logger.info('Parsing rollout response, try to extract public_key.', name: 'token_notifier.dart#_parseRollOutResponse'); diff --git a/lib/utils/image_converter.dart b/lib/utils/image_converter.dart index aefbb1bcb..50ac981a9 100644 --- a/lib/utils/image_converter.dart +++ b/lib/utils/image_converter.dart @@ -24,7 +24,7 @@ class ImageConverter { ImageFormatGroup.jpeg => ImageConverter._fromJPEG(image), ImageFormatGroup.nv21 => ImageConverter._fromNV21(image, rotation: rotation, mirror: isFrontCamera, cropLeft: cropLeft ?? 0, cropRight: cropRight ?? 0, cropTop: cropTop ?? 0, cropBottom: cropBottom ?? 0), - ImageFormatGroup.unknown => throw ArgumentError('Unknown image format'), + ImageFormatGroup.unknown => throw ArgumentError('Unknown image format', 'image.format.group'), }; } diff --git a/lib/utils/lock_auth.dart b/lib/utils/lock_auth.dart index fa7392fc5..6dd2cfa6f 100644 --- a/lib/utils/lock_auth.dart +++ b/lib/utils/lock_auth.dart @@ -16,11 +16,14 @@ import 'view_utils.dart'; bool _authenticationInProgress = false; /// Sends a request to the OS to authenticate the user. Returns true if the user was authenticated, false otherwise. -Future lockAuth({required String localizedReason}) async { +/// If the device does not support authentication or authentication is not set up, a dialog is shown to the user. +/// If [autoAuthIfUnsupported] is set to true and the device does not support authentication, the function will return true. +Future lockAuth({required String localizedReason, bool autoAuthIfUnsupported = false}) async { bool didAuthenticate = false; LocalAuthentication localAuth = LocalAuthentication(); - - if (kIsWeb || !(await localAuth.isDeviceSupported())) { + final isDeviceSupported = await localAuth.isDeviceSupported(); + if (!isDeviceSupported && autoAuthIfUnsupported) return true; + if (kIsWeb || !isDeviceSupported) { await showAsyncDialog( builder: (context) { return DefaultDialog( diff --git a/lib/utils/riverpod_providers.dart b/lib/utils/riverpod_providers.dart index 89c18c1c1..d9f08d350 100644 --- a/lib/utils/riverpod_providers.dart +++ b/lib/utils/riverpod_providers.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:app_links/app_links.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/rendering.dart'; @@ -199,7 +197,6 @@ class SortableNotifier extends StateNotifier> { SortableNotifier({List initState = const []}) : super(initState); void handleNewList(List newList) { - log('T type: ${newList.runtimeType}', name: 'SortableNotifier#handleNewList'); var newState = List.from(state); newState.removeWhere((element) => element is T); newState.addAll(newList); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 60f21fdb5..7d0a08387 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -26,8 +26,10 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:privacyidea_authenticator/mains/main_netknights.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; -import '../utils/logger.dart'; +import 'customization/application_customization.dart' show ApplicationCustomization; /// Inserts [char] at the position [pos] in the given String ([str]), /// and returns the resulting String. @@ -122,6 +124,8 @@ bool doesThrow(Function() f) { } } +String getCurrentAppName() => PrivacyIDEAAuthenticator.currentCustomization?.appName ?? ApplicationCustomization.defaultCustomization.appName; + dynamic tryJsonDecode(String json) { try { return jsonDecode(json); diff --git a/lib/views/add_token_manually_view/add_token_manually_view.dart b/lib/views/add_token_manually_view/add_token_manually_view.dart index b5861de54..f2215b0d0 100644 --- a/lib/views/add_token_manually_view/add_token_manually_view.dart +++ b/lib/views/add_token_manually_view/add_token_manually_view.dart @@ -196,7 +196,7 @@ class _AddTokenManuallyViewState extends ConsumerState { uriMap.addAll({ URI_ORIGIN: TokenOriginSourceType.manually.toTokenOrigin( data: jsonEncode(uriMap), - appName: PrivacyIDEAAuthenticator.currentCustomization?.appName, + originName: PrivacyIDEAAuthenticator.currentCustomization?.appName, isPrivacyIdeaToken: false, ), }); diff --git a/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart b/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart index 7327fb948..b9362fa9e 100644 --- a/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart +++ b/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; import '../../../model/mixins/sortable_mixin.dart'; import '../../../model/token_folder.dart'; @@ -60,25 +61,20 @@ class _MainViewTokensListState extends ConsumerState { final allowToRefresh = allSortables.any((element) => element is PushToken); bool filterPushTokens = ref.watch(settingsProvider).hidePushTokens && allowToRefresh; - final sortables = []; - + final showSortables = []; // List of sortables that should be shown in the list for (var element in allSortables) { + Logger.warning('Element: $element', name: 'main_view_tokens_list.dart#build'); if (element is! Token) { - sortables.add(element); - continue; - } - if (filterPushTokens == false && element.folderId == null) { - sortables.add(element); + showSortables.add(element); continue; } + if (element is PushToken && filterPushTokens == true) continue; + if (element.folderId != null) continue; + showSortables.add(element); } - // final List tokens = allSortables.whereType().toList(); - // final tokensWithNoFolder = tokens.withoutFolder(exclude: filterPushTokens ? [PushToken] : []); - - // List sortables = [...tokenFolders, ...tokensWithNoFolder]; return Stack( children: [ - if (sortables.isEmpty) const NoTokenScreen(), + if (showSortables.isEmpty) const NoTokenScreen(), DeactivateableRefreshIndicator( allowToRefresh: allowToRefresh, onRefresh: () async => LoadingIndicator.show(context, () async => PushProvider.instance?.pollForChallenges(isManually: true)), @@ -99,20 +95,24 @@ class _MainViewTokensListState extends ConsumerState { TokenIntroduction( child: Column( children: [ - ...MainViewTokensList.buildSortableWidgets(sortables, draggingSortable), + ...MainViewTokensList.buildSortableWidgets(showSortables, draggingSortable), ], ), ), ...(draggingSortable != null) ? [ DragTargetDivider( - dependingFolder: null, previousSortable: sortables.last, nextSortable: null, isLastDivider: true, bottomPaddingIfLast: 80), + dependingFolder: null, + previousSortable: showSortables.last, + nextSortable: null, + isLastDivider: true, + bottomPaddingIfLast: 80), Expanded( child: Opacity( opacity: 0, child: DragTargetDivider( dependingFolder: null, - previousSortable: sortables.last, + previousSortable: showSortables.last, nextSortable: null, isLastDivider: true, bottomPaddingIfLast: 0, diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/actions/edit_day_password_token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/actions/edit_day_password_token_action.dart index 1daf7cff2..5dba75405 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/actions/edit_day_password_token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/actions/edit_day_password_token_action.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; @@ -10,8 +8,8 @@ import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod_providers.dart'; -import '../../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; +import '../../default_token_actions/default_edit_action_dialog.dart'; import '../../token_action.dart'; class EditDayPassowrdTokenAction extends TokenAction { @@ -52,103 +50,23 @@ class EditDayPassowrdTokenAction extends TokenAction { ), )); - void _showDialog() { - final tokenLabel = TextEditingController(text: token.label); - final imageUrl = TextEditingController(text: token.tokenImage); - final period = token.period; - final algorithm = token.algorithm; - - showDialog( - useRootNavigator: false, - context: globalNavigatorKey.currentContext!, - builder: (BuildContext context) => BackdropFilter( - filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), - child: DefaultDialog( - scrollable: true, - title: Text( - AppLocalizations.of(context)!.editToken, - overflow: TextOverflow.fade, - softWrap: false, - ), - actions: [ - TextButton( - child: Text( - AppLocalizations.of(context)!.cancel, - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () { - Navigator.of(context).pop(); - }, + void _showDialog() => showDialog( + useRootNavigator: false, + context: globalNavigatorKey.currentContext!, + builder: (BuildContext context) => DefaultEditActionDialog( + token: token, + additionalChildren: [ + TextFormField( + initialValue: token.algorithm.name, + decoration: InputDecoration(labelText: AppLocalizations.of(context)!.algorithm), + enabled: false, ), - TextButton( - child: Text( - AppLocalizations.of(context)!.save, - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () async { - globalRef - ?.read(tokenProvider.notifier) - .updateToken(token, (p0) => p0.copyWith(label: tokenLabel.text, tokenImage: imageUrl.text, period: period, algorithm: algorithm)); - Navigator.of(context).pop(); - }), - ], - content: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - TextFormField( - controller: tokenLabel, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.name), - validator: (value) { - if (value!.isEmpty) { - return AppLocalizations.of(context)!.name; - } - return null; - }, - ), - TextFormField( - controller: imageUrl, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.imageUrl), - validator: (value) { - if (value!.isEmpty) { - return AppLocalizations.of(context)!.imageUrl; - } - return null; - }, - ), - TextFormField( - initialValue: algorithm.name, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.algorithm), - enabled: false, - ), - TextFormField( - initialValue: period.toString().split('.').first, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.period), - enabled: false, - ), - if (token.origin != null) - TextFormField( - initialValue: token.origin!.appName, - decoration: const InputDecoration(labelText: 'Origin'), - enabled: false, - ), - TextFormField( - initialValue: token.isPrivacyIdeaToken == false ? 'Yes' : 'No', - decoration: const InputDecoration(labelText: 'Is exportable?'), - enabled: false, - ), - ], - ), + TextFormField( + initialValue: token.period.toString().split('.').first, + decoration: InputDecoration(labelText: AppLocalizations.of(context)!.period), + enabled: false, ), - ), + ], ), - ), - ); - } + ); } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action.dart index eceac546e..206ab295a 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/enums/introduction.dart'; @@ -10,7 +11,6 @@ import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; import '../../../../../utils/riverpod_providers.dart'; -import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; import '../token_action.dart'; @@ -30,7 +30,7 @@ class DefaultEditAction extends TokenAction { _showDialog(); }, child: FocusedItemAsOverlay( - tooltipWhenFocused: AppLocalizations.of(context)!.renameToken, + tooltipWhenFocused: AppLocalizations.of(context)!.editToken, childIsMoving: true, isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.editToken), onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.editToken), @@ -40,7 +40,7 @@ class DefaultEditAction extends TokenAction { children: [ const Icon(Icons.edit), Text( - AppLocalizations.of(context)!.rename, + AppLocalizations.of(context)!.save, overflow: TextOverflow.fade, softWrap: false, ), @@ -50,61 +50,27 @@ class DefaultEditAction extends TokenAction { } void _showDialog() { - TextEditingController nameInputController = TextEditingController(text: token.label); showDialog( - useRootNavigator: false, - context: globalNavigatorKey.currentContext!, - builder: (BuildContext context) { - return DefaultDialog( - scrollable: true, - title: Text( - AppLocalizations.of(context)!.renameToken, - overflow: TextOverflow.fade, - softWrap: false, - ), - content: TextFormField( - autofocus: true, - controller: nameInputController, - onChanged: (value) {}, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.name), - validator: (value) { - if (value!.isEmpty) { - return AppLocalizations.of(context)!.name; - } - return null; - }, - ), - actions: [ - TextButton( - child: Text( - AppLocalizations.of(context)!.cancel, - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () => Navigator.of(context).pop(), - ), - TextButton( - child: Text( - AppLocalizations.of(context)!.rename, - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () { - final newLabel = nameInputController.text.trim(); - if (newLabel.isEmpty) return; - globalRef?.read(tokenProvider.notifier).updateToken(token, (p0) => p0.copyWith(label: newLabel)); - - Logger.info( - 'Renamed token:', - name: 'token_widget_base.dart#TextButton#renameClicked', - error: '\'${token.label}\' changed to \'$newLabel\'', - ); - - Navigator.of(context).pop(); - }, - ), - ], - ); - }); + useRootNavigator: false, + context: globalNavigatorKey.currentContext!, + builder: (BuildContext context) { + return DefaultEditActionDialog( + token: token, + onSaveButtonPressed: ({required newLabel, newImageUrl}) async { + if (newLabel.isEmpty) return; + final edited = await globalRef?.read(tokenProvider.notifier).updateToken(token, (p0) => p0.copyWith(label: newLabel, tokenImage: newImageUrl)); + if (edited == null) { + Logger.error('Token editing failed', name: 'DefaultEditAction#_showDialog'); + return; + } + Logger.info( + 'Token edited: ${token.label} -> ${edited.label}, ${token.tokenImage} -> ${edited.tokenImage}', + name: 'DefaultEditAction#_showDialog', + ); + if (context.mounted) Navigator.of(context).pop(); + }, + ); + }, + ); } } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart new file mode 100644 index 000000000..088ce6843 --- /dev/null +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart @@ -0,0 +1,148 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; + +import '../../../../../model/tokens/token.dart'; +import '../../../../../utils/logger.dart'; +import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../widgets/dialog_widgets/default_dialog.dart'; + +class DefaultEditActionDialog extends ConsumerStatefulWidget { + final Token token; + final List? additionalChildren; + + /// Should return false if an input is invalid. Name and image URL are validated regardless of whether the function is set or not. + final bool additionalSaveValidation; + final FutureOr Function({required String newLabel, required String? newImageUrl})? onSaveButtonPressed; + const DefaultEditActionDialog({ + required this.token, + this.onSaveButtonPressed, + this.additionalChildren, + this.additionalSaveValidation = true, + super.key, + }); + + @override + ConsumerState createState() => _DefaultEditActionDialogState(); +} + +class _DefaultEditActionDialogState extends ConsumerState { + late final TextEditingController nameInputController = TextEditingController(text: widget.token.label); + late final TextEditingController imageUrlController = TextEditingController(text: widget.token.tokenImage); + bool _nameIsValid = true; + bool _imageUrlIsValid = true; + late final bool _additionalSaveValidation = widget.additionalSaveValidation; + bool get _canSave => _nameIsValid && _imageUrlIsValid && _additionalSaveValidation; + + String? _validateName(String? value) { + if (value == null || value.isNotEmpty) return null; + return AppLocalizations.of(context)!.mustNotBeEmpty(AppLocalizations.of(context)!.name); + } + + String? _validateImageUrl(String? value) { + if (value == null) return null; + final uri = Uri.tryParse(value); + if (value.isNotEmpty && (uri == null || uri.host.isEmpty || uri.scheme.isEmpty || uri.path.isEmpty)) { + return AppLocalizations.of(context)!.exampleUrl; + } + return null; + } + + @override + Widget build(BuildContext context) { + final appLocalizations = AppLocalizations.of(context)!; + return DefaultDialog( + scrollable: true, + title: Text( + appLocalizations.renameToken, + overflow: TextOverflow.fade, + softWrap: false, + ), + content: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + key: Key('${widget.token.id}_editName'), + autovalidateMode: AutovalidateMode.onUserInteraction, + controller: nameInputController, + onChanged: (value) => setState(() => _nameIsValid = _validateName(value) == null), + decoration: InputDecoration(labelText: appLocalizations.name), + validator: _validateName, + ), + TextFormField( + key: Key('${widget.token.id}_editImageUrl'), + autovalidateMode: AutovalidateMode.onUserInteraction, + controller: imageUrlController, + onChanged: (value) => setState(() => _imageUrlIsValid = _validateImageUrl(value) == null), + decoration: InputDecoration(labelText: appLocalizations.imageUrl), + validator: _validateImageUrl, + ), + if (widget.additionalChildren != null) ...widget.additionalChildren!, + TextFormField( + initialValue: widget.token.origin?.appName ?? 'appLocalizations.unknown', + decoration: const InputDecoration(labelText: 'Origin'), + readOnly: true, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).disabledColor)), + TextFormField( + initialValue: widget.token.isPrivacyIdeaToken == false ? 'Yes' : 'No', + decoration: const InputDecoration(labelText: 'Is exportable?'), + readOnly: true, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).disabledColor), + ), + ], + ), + ), + ), + actions: [ + TextButton( + child: Text( + appLocalizations.cancel, + overflow: TextOverflow.fade, + softWrap: false, + ), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + onPressed: !_canSave + ? null + : widget.onSaveButtonPressed != null + ? () { + widget.onSaveButtonPressed!( + newLabel: nameInputController.text, + newImageUrl: imageUrlController.text, + ); + } + : () async { + final newLabel = nameInputController.text; + final newImageUrl = imageUrlController.text; + if (newLabel.isEmpty) return; + final edited = await globalRef + ?.read(tokenProvider.notifier) + .updateToken(widget.token, (p0) => p0.copyWith(label: newLabel, tokenImage: newImageUrl)); + if (edited == null) { + Logger.error('Token editing failed', name: 'DefaultEditAction#_showDialog'); + return; + } + Logger.info( + 'Token edited: ${widget.token.label} -> ${edited.label}, ${widget.token.tokenImage} -> ${edited.tokenImage}', + name: 'DefaultEditAction#_showDialog', + ); + if (context.mounted) Navigator.of(context).pop(); + }, + child: Text( + appLocalizations.save, + overflow: TextOverflow.fade, + softWrap: false, + ), + ), + ], + ); + } +} diff --git a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart index fc1a65bf1..cc6280d89 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; @@ -11,8 +9,8 @@ import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod_providers.dart'; -import '../../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; +import '../../default_token_actions/default_edit_action_dialog.dart'; import '../../token_action.dart'; class EditHOTPTokenAction extends TokenAction { @@ -53,93 +51,18 @@ class EditHOTPTokenAction extends TokenAction { ), )); - void _showDialog() { - final tokenLabel = TextEditingController(text: token.label); - final imageUrl = TextEditingController(text: token.tokenImage); - final algorithm = token.algorithm; - - showDialog( - useRootNavigator: false, - context: globalNavigatorKey.currentContext!, - builder: (BuildContext context) => BackdropFilter( - filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), - child: DefaultDialog( - scrollable: true, - title: Text(AppLocalizations.of(context)!.editToken), - actions: [ - TextButton( - child: Text( - AppLocalizations.of(context)!.cancel, - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () { - Navigator.of(context).pop(); - }, + void _showDialog() => showDialog( + useRootNavigator: false, + context: globalNavigatorKey.currentContext!, + builder: (BuildContext context) => DefaultEditActionDialog( + token: token, + additionalChildren: [ + TextFormField( + initialValue: token.algorithm.name, + decoration: InputDecoration(labelText: AppLocalizations.of(context)!.algorithm), + enabled: false, ), - TextButton( - child: Text( - AppLocalizations.of(context)!.save, - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () async { - globalRef - ?.read(tokenProvider.notifier) - .updateToken(token, (p0) => p0.copyWith(label: tokenLabel.text, tokenImage: imageUrl.text, algorithm: algorithm)); - Navigator.of(context).pop(); - }), ], - content: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - TextFormField( - controller: tokenLabel, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.name), - validator: (value) { - if (value!.isEmpty) { - return AppLocalizations.of(context)!.name; - } - return null; - }, - ), - TextFormField( - controller: imageUrl, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.imageUrl), - validator: (value) { - if (value!.isEmpty) { - return AppLocalizations.of(context)!.imageUrl; - } - return null; - }, - ), - TextFormField( - initialValue: algorithm.name, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.algorithm), - enabled: false, - ), - if (token.origin != null) - TextFormField( - initialValue: token.origin!.appName, - decoration: const InputDecoration(labelText: 'Origin'), - enabled: false, - ), - TextFormField( - initialValue: token.isPrivacyIdeaToken == false ? 'Yes' : 'No', - decoration: const InputDecoration(labelText: 'Is exportable?'), - enabled: false, - ), - ], - ), - ), - ), ), - ), - ); - } + ); } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart index 7e1df7177..b1692caa4 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart'; import '../../../../../../l10n/app_localizations.dart'; import '../../../../../../model/enums/introduction.dart'; @@ -9,7 +10,6 @@ import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod_providers.dart'; -import '../../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../../../widgets/enable_text_form_field_after_many_taps.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../token_action.dart'; @@ -23,129 +23,92 @@ class EditPushTokenAction extends TokenAction { }); @override - CustomSlidableAction build(BuildContext context, WidgetRef ref) => CustomSlidableAction( - backgroundColor: Theme.of(context).extension()!.editColor, - foregroundColor: Theme.of(context).extension()!.foregroundColor, - onPressed: (context) async { - if (token.isLocked && await lockAuth(localizedReason: AppLocalizations.of(context)!.editLockedToken) == false) { - return; - } - _showDialog(); - }, - child: FocusedItemAsOverlay( - tooltipWhenFocused: AppLocalizations.of(context)!.introEditToken, - childIsMoving: true, - alignment: Alignment.bottomCenter, - isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.editToken), - onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.editToken), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon(Icons.edit), - Text( - AppLocalizations.of(context)!.edit, - overflow: TextOverflow.fade, - softWrap: false, - ), - ], - ), - )); + CustomSlidableAction build(BuildContext context, WidgetRef ref) { + final appLocalizations = AppLocalizations.of(context)!; + return CustomSlidableAction( + backgroundColor: Theme.of(context).extension()!.editColor, + foregroundColor: Theme.of(context).extension()!.foregroundColor, + onPressed: (context) async { + if (token.isLocked && await lockAuth(localizedReason: appLocalizations.editLockedToken) == false) { + return; + } + _showDialog(); + }, + child: FocusedItemAsOverlay( + tooltipWhenFocused: appLocalizations.introEditToken, + childIsMoving: true, + alignment: Alignment.bottomCenter, + isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.editToken), + onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.editToken), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon(Icons.edit), + Text( + appLocalizations.edit, + overflow: TextOverflow.fade, + softWrap: false, + ), + ], + ), + )); + } - void _showDialog() { - final tokenLabel = TextEditingController(text: token.label); - final pushUrl = TextEditingController(text: token.url.toString()); - final imageUrl = TextEditingController(text: token.tokenImage); - final tokenSersial = token.serial; - final publicTokenKey = token.publicTokenKey; + String? _validatePushEndpointUrl(String? value, BuildContext context) { + if (value == null || value.isEmpty) return AppLocalizations.of(context)!.mustNotBeEmpty(AppLocalizations.of(context)!.pushEndpointUrl); + final uri = Uri.tryParse(value); + if (uri == null || uri.host.isEmpty || uri.scheme.isEmpty || uri.path.isEmpty) { + return AppLocalizations.of(context)!.exampleUrl; + } + return null; + } - showDialog( - useRootNavigator: false, - context: globalNavigatorKey.currentContext!, - builder: (BuildContext context) => DefaultDialog( - scrollable: true, - title: Text( - AppLocalizations.of(context)!.editToken, - overflow: TextOverflow.fade, - softWrap: false, - ), - actions: [ - TextButton( - child: Text( - AppLocalizations.of(context)!.cancel, - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () { + void _showDialog() => showDialog( + useRootNavigator: false, + context: globalNavigatorKey.currentContext!, + builder: (BuildContext context) { + final pushUrl = TextEditingController(text: token.url.toString()); + final appLocalizations = AppLocalizations.of(context)!; + return DefaultEditActionDialog( + token: token, + onSaveButtonPressed: ({required newLabel, newImageUrl}) async { + globalRef?.read(tokenProvider.notifier).updateToken( + token, + (p0) => p0.copyWith( + label: newLabel, + url: Uri.parse(pushUrl.text), + tokenImage: newImageUrl, + ), + ); Navigator.of(context).pop(); }, - ), - TextButton( - child: Text( - AppLocalizations.of(context)!.save, - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () async { - globalRef?.read(tokenProvider.notifier).updateToken( - token, - (p0) => p0.copyWith( - label: tokenLabel.text, - url: Uri.parse(pushUrl.text), - tokenImage: imageUrl.text, - ), - ); - Navigator.of(context).pop(); - }), - ], - content: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ + additionalChildren: [ TextFormField( - initialValue: tokenSersial, - decoration: const InputDecoration(labelText: 'Serial'), - enabled: false, + initialValue: token.serial, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).disabledColor), + decoration: const InputDecoration( + labelText: 'Serial', + ), + readOnly: true, ), EnableTextFormFieldAfterManyTaps( - controller: pushUrl, - decoration: const InputDecoration(labelText: 'URL'), - validator: (value) { - if (value!.isEmpty) return 'URL'; - return null; - }), - TextFormField( - controller: tokenLabel, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.name), - validator: (value) { - if (value!.isEmpty) { - return AppLocalizations.of(context)!.name; - } - return null; - }, - ), - TextFormField( - controller: imageUrl, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.imageUrl), - validator: (value) { - if (value!.isEmpty) return AppLocalizations.of(context)!.imageUrl; - return null; - }, + controller: pushUrl, + decoration: InputDecoration(labelText: appLocalizations.pushEndpointUrl), + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (value) => _validatePushEndpointUrl(value, context), ), const SizedBox(height: 10), ExpansionTile( title: Text( - AppLocalizations.of(context)!.publicKey, - style: Theme.of(context).textTheme.bodyMedium, + appLocalizations.publicKey, + style: Theme.of(context).textTheme.titleMedium, overflow: TextOverflow.fade, softWrap: false, ), children: [ - Text( - publicTokenKey ?? AppLocalizations.of(context)!.noPublicKey, + SelectableText( + token.publicTokenKey ?? appLocalizations.noPublicKey, style: Theme.of(context).textTheme.bodyMedium, ), ], @@ -153,33 +116,20 @@ class EditPushTokenAction extends TokenAction { const Divider(), ExpansionTile( title: Text( - AppLocalizations.of(context)!.firebaseToken, - style: Theme.of(context).textTheme.bodyMedium, + appLocalizations.firebaseToken, + style: Theme.of(context).textTheme.titleMedium, overflow: TextOverflow.fade, softWrap: false, ), children: [ - Text( - token.fbToken != null ? token.fbToken.toString() : AppLocalizations.of(context)!.noFbToken, + SelectableText( + token.fbToken != null ? token.fbToken.toString() : appLocalizations.noFbToken, style: Theme.of(context).textTheme.bodyMedium, ) ], ), - if (token.origin != null) - TextFormField( - initialValue: token.origin!.appName, - decoration: const InputDecoration(labelText: 'Origin'), - enabled: false, - ), - TextFormField( - initialValue: token.isPrivacyIdeaToken == false ? 'Yes' : 'No', - decoration: const InputDecoration(labelText: 'Is exportable?'), - enabled: false, - ), ], - ), - ), - ), - ); - } + ); + }, + ); } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/actions/edit_totp_token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/actions/edit_totp_token_action.dart index 81b31d1b8..08275ed36 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/actions/edit_totp_token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/actions/edit_totp_token_action.dart @@ -8,8 +8,8 @@ import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod_providers.dart'; -import '../../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; +import '../../default_token_actions/default_edit_action_dialog.dart'; import '../../token_action.dart'; class EditTOTPTokenAction extends TokenAction { @@ -50,100 +50,23 @@ class EditTOTPTokenAction extends TokenAction { ), )); - void _showDialog() { - final tokenLabel = TextEditingController(text: token.label); - final imageUrl = TextEditingController(text: token.tokenImage); - final period = token.period; - final algorithm = token.algorithm; - - showDialog( - useRootNavigator: false, - context: globalNavigatorKey.currentContext!, - builder: (BuildContext context) => DefaultDialog( - scrollable: true, - title: Text( - AppLocalizations.of(context)!.editToken, - overflow: TextOverflow.fade, - softWrap: false, - ), - actions: [ - TextButton( - child: Text( - AppLocalizations.of(context)!.cancel, - overflow: TextOverflow.fade, - softWrap: false, + void _showDialog() => showDialog( + useRootNavigator: false, + context: globalNavigatorKey.currentContext!, + builder: (BuildContext context) => DefaultEditActionDialog( + token: token, + additionalChildren: [ + TextFormField( + initialValue: token.algorithm.name, + decoration: InputDecoration(labelText: AppLocalizations.of(context)!.algorithm), + enabled: false, ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: Text( - AppLocalizations.of(context)!.save, - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () async { - globalRef - ?.read(tokenProvider.notifier) - .updateToken(token, (p0) => p0.copyWith(label: tokenLabel.text, tokenImage: imageUrl.text, period: period, algorithm: algorithm)); - Navigator.of(context).pop(); - }), - ], - content: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - TextFormField( - controller: tokenLabel, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.name), - validator: (value) { - if (value!.isEmpty) { - return AppLocalizations.of(context)!.name; - } - return null; - }, - ), - TextFormField( - controller: imageUrl, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.imageUrl), - validator: (value) { - if (value!.isEmpty) { - return AppLocalizations.of(context)!.imageUrl; - } - return null; - }, - ), - TextFormField( - initialValue: algorithm.name, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.algorithm), - enabled: false, - ), - TextFormField( - initialValue: period.toString().split('.').first, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.period), - enabled: false, - ), - if (token.origin != null) - TextFormField( - initialValue: token.origin!.appName, - decoration: const InputDecoration(labelText: 'Origin'), - enabled: false, - ), - TextFormField( - initialValue: token.isPrivacyIdeaToken == false ? 'Yes' : 'No', - decoration: const InputDecoration(labelText: 'Is exportable?'), - enabled: false, - ), - ], + TextFormField( + initialValue: token.period.toString().split('.').first, + decoration: InputDecoration(labelText: AppLocalizations.of(context)!.period), + enabled: false, ), - ), + ], ), - ), - ); - } + ); } diff --git a/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart b/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart index d26467352..8bd271535 100644 --- a/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart +++ b/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart @@ -129,6 +129,7 @@ class _QRScannerWidgetState extends State { } void _navigatorReturn(String qrCode) { + Logger.warning('Scanned QR Code: $qrCode', name: 'QRScannerWidget#_navigatorReturn'); if (!mounted) return; Navigator.of(context).maybePop(qrCode); } diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_export_type_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_export_type_dialog.dart index 52eed0795..443e820d4 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_export_type_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_export_type_dialog.dart @@ -39,7 +39,10 @@ class SelectExportTypeDialog extends StatelessWidget { final isExported = await showDialog( context: context, builder: (context) => SelectTokensDialog( - exportDialogBuilder: (tokens) => ExportTokensToFileDialog(tokens: tokens), + exportDialogBuilder: (tokens) { + if (tokens.isEmpty) return DefaultDialog(content: Text(AppLocalizations.of(context)!.noTokensToExport)); + return ExportTokensToFileDialog(tokens: tokens); + }, ), ); if (isExported == true && context.mounted) Navigator.of(context).pop(isExported); @@ -50,7 +53,11 @@ class SelectExportTypeDialog extends StatelessWidget { context: context, builder: (context) => SelectTokensDialog( multiSelect: false, - exportDialogBuilder: (tokens) => ShowQrCodeDialog(token: tokens.first), + exportDialogBuilder: (tokens) { + if (tokens.isEmpty) return DefaultDialog(content: Text(AppLocalizations.of(context)!.noTokensToExport)); + + return ShowQrCodeDialog(token: tokens.first); + }, ), ); if (isExported == true && context.mounted) Navigator.of(context).pop(isExported); diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart index a236a0a14..dc55adc03 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart @@ -126,9 +126,7 @@ class _SelectTokensDialogState extends ConsumerState { void _showExportDialog(Iterable tokens) async { if (tokens.isEmpty) return; - final tokenFolder = ref.read(tokenFolderProvider).folders.where((folder) => folder.isLocked).toList(); - final containsLocked = tokens.any((token) => token.isLocked || tokenFolder.any((folder) => folder.folderId == token.folderId)); - final authenticated = (!containsLocked || await lockAuth(localizedReason: AppLocalizations.of(context)!.exportLockedTokenReason)); + final authenticated = (await lockAuth(localizedReason: AppLocalizations.of(context)!.exportLockedTokenReason, autoAuthIfUnsupported: true)); if (!authenticated || !mounted) return; final isExported = await showDialog( context: context, diff --git a/lib/widgets/enable_text_form_field_after_many_taps.dart b/lib/widgets/enable_text_form_field_after_many_taps.dart index 47c7d1585..32b803f1b 100644 --- a/lib/widgets/enable_text_form_field_after_many_taps.dart +++ b/lib/widgets/enable_text_form_field_after_many_taps.dart @@ -1,17 +1,18 @@ import 'dart:async'; - import 'package:flutter/material.dart'; class EnableTextFormFieldAfterManyTaps extends StatefulWidget { final TextEditingController controller; final InputDecoration decoration; + final AutovalidateMode? autovalidateMode; final String? Function(String?)? validator; final int maxTaps; const EnableTextFormFieldAfterManyTaps({ required this.controller, required this.decoration, - this.maxTaps = 7, + this.maxTaps = 6, + this.autovalidateMode, this.validator, super.key, }); @@ -24,24 +25,34 @@ class _EnableTextFormFieldAfterManyTapsState extends State GestureDetector( - onTap: () { - timer?.cancel(); - timer = Timer(const Duration(milliseconds: 500), () { - counter = 0; - }); - counter++; - if (counter == widget.maxTaps) { - setState(() { - enabled = true; - }); - } - }, + onDoubleTap: !enabled ? () => tapped(2) : null, child: TextFormField( - enabled: enabled, + key: Key('${widget.controller.hashCode}_enableTextFormFieldAfterManyTaps'), + readOnly: !enabled, + onTap: !enabled ? () => tapped(1) : null, + style: enabled ? null : Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).disabledColor), controller: widget.controller, decoration: widget.decoration, + autovalidateMode: widget.autovalidateMode, validator: widget.validator, ), ); diff --git a/test/unit_test/model/extensions/enums/token_origin_source_type_extension_test.dart b/test/unit_test/model/extensions/enums/token_origin_source_type_extension_test.dart index 567c40e1d..1c69e894b 100644 --- a/test/unit_test/model/extensions/enums/token_origin_source_type_extension_test.dart +++ b/test/unit_test/model/extensions/enums/token_origin_source_type_extension_test.dart @@ -21,7 +21,7 @@ void _testTokenOriginSourceTypeExtension() { ); final TokenOriginData tokenOriginData = TokenOriginSourceType.qrScan.toTokenOrigin( data: 'data', - appName: 'appName', + originName: 'appName', isPrivacyIdeaToken: true, createdAt: DateTime.fromMicrosecondsSinceEpoch(1622160000000), ); @@ -39,6 +39,7 @@ void _testTokenOriginSourceTypeExtension() { data: 'data', appName: 'appName', isPrivacyIdeaToken: true, + creator: 'creator', createdAt: DateTime.fromMicrosecondsSinceEpoch(1622160000000), ); final tokenMatch = token.copyWith(origin: tokenOriginDataMatch); diff --git a/test/unit_test/model/push_request_test.dart b/test/unit_test/model/push_request_test.dart index 91990c500..e9862c056 100644 --- a/test/unit_test/model/push_request_test.dart +++ b/test/unit_test/model/push_request_test.dart @@ -187,6 +187,7 @@ void _testPushRequest() { "data": "otpauth://pipush/PIPU00064CF0?url=https%3A//192.168.56.103/ttype/push&ttl=10&issuer=privacyIDEA&enrollment_credential=9d3100908d3c76a948b6041c8338def8b15ec06a&v=1&serial=PIPU00064CF0&sslverify=0", "isPrivacyIdeaToken": null, + "creator": null, "createdAt": "2024-04-11T15:19:53.567296", "piServerVersion": null }, diff --git a/test/unit_test/model/token_import/token_origin_data_test.dart b/test/unit_test/model/token_import/token_origin_data_test.dart index 643d311a8..1575ed551 100644 --- a/test/unit_test/model/token_import/token_origin_data_test.dart +++ b/test/unit_test/model/token_import/token_origin_data_test.dart @@ -18,6 +18,7 @@ void _testTokenOriginData() { appName: 'appName', isPrivacyIdeaToken: true, createdAt: DateTime.now(), + creator: 'creator', piServerVersion: const Version(1, 0, 0), ); expect(tokenOriginData.source, TokenOriginSourceType.manually); @@ -34,15 +35,16 @@ void _testTokenOriginData() { appName: 'appName', isPrivacyIdeaToken: true, createdAt: DateTime.now(), + creator: 'creator', piServerVersion: const Version(1, 0, 0), ); final copy = tokenOriginData.copyWith( source: TokenOriginSourceType.qrScan, data: 'data2', appName: 'appName2', - isPrivacyIdeaToken: false, + isPrivacyIdeaToken: () => false, createdAt: DateTime.now().add(const Duration(days: 1)), - piServerVersion: const Version(1, 0, 1), + piServerVersion: () => const Version(1, 0, 1), ); expect(copy.source, TokenOriginSourceType.qrScan); expect(copy.data, 'data2'); diff --git a/test/unit_test/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor_test.dart b/test/unit_test/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor_test.dart index 3e111d643..af2fdcdf7 100644 --- a/test/unit_test/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor_test.dart +++ b/test/unit_test/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor_test.dart @@ -5,6 +5,7 @@ import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; import 'package:privacyidea_authenticator/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart'; +import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; void main() { _testOtpAuthProcessor(); @@ -29,7 +30,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type, equals('TOTP')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final totpToken = token0 as TOTPToken; @@ -53,7 +54,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type, equals('TOTP')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final totpToken = token0 as TOTPToken; @@ -77,7 +78,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type, equals('TOTP')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final totpToken = token0 as TOTPToken; @@ -101,7 +102,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type, equals('TOTP')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final totpToken = token0 as TOTPToken; @@ -139,7 +140,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type, equals('TOTP')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final totpToken = token0 as TOTPToken; @@ -166,7 +167,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type, equals('HOTP')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final hotpToken = token0 as HOTPToken; @@ -190,7 +191,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type, equals('HOTP')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final hotpToken = token0 as HOTPToken; @@ -214,7 +215,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type, equals('HOTP')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final hotpToken = token0 as HOTPToken; @@ -238,7 +239,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type, equals('HOTP')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final hotpToken = token0 as HOTPToken; @@ -276,7 +277,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type, equals('HOTP')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final hotpToken = token0 as HOTPToken; @@ -316,7 +317,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type.toLowerCase(), equals('daypassword')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final dayPasswordToken = token0 as DayPasswordToken; @@ -341,7 +342,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type.toLowerCase(), equals('daypassword')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final dayPasswordToken = token0 as DayPasswordToken; @@ -366,7 +367,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type.toLowerCase(), equals('daypassword')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final dayPasswordToken = token0 as DayPasswordToken; @@ -391,7 +392,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('account')); expect(token0.type.toLowerCase(), equals('daypassword')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final dayPasswordToken = token0 as DayPasswordToken; @@ -433,7 +434,7 @@ void _testOtpAuthProcessor() { expect(token0.label, equals('PIPU0000D79E')); expect(token0.type.toLowerCase(), equals('pipush')); expect(token0.origin, isNotNull); - expect(token0.origin!.appName, isNull); + expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); expect(token0.origin!.isPrivacyIdeaToken, isNull); expect(token0.origin!.data, equals(uriString)); final pushToken = token0 as PushToken;