From e7d3ab9ae43bc57159b366dd03bec2a1e4d8edbd Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Fri, 8 Mar 2024 16:48:13 +0100 Subject: [PATCH 1/2] Use "badge" icon --- lib/app/views/navigation.dart | 2 +- lib/app/views/reset_dialog.dart | 2 +- lib/piv/views/piv_screen.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/app/views/navigation.dart b/lib/app/views/navigation.dart index 02278c9f..627ee891 100644 --- a/lib/app/views/navigation.dart +++ b/lib/app/views/navigation.dart @@ -93,7 +93,7 @@ extension on Application { Application.passkeys => Symbols.passkey, Application.fingerprints => Symbols.fingerprint, Application.slots => Symbols.touch_app, - Application.certificates => Symbols.approval, + Application.certificates => Symbols.badge, Application.management => Symbols.construction, Application.home => Symbols.home }; diff --git a/lib/app/views/reset_dialog.dart b/lib/app/views/reset_dialog.dart index 37625332..223b9c18 100644 --- a/lib/app/views/reset_dialog.dart +++ b/lib/app/views/reset_dialog.dart @@ -48,7 +48,7 @@ extension on Capability { IconData get _icon => switch (this) { Capability.oath => Symbols.supervisor_account, Capability.fido2 => Symbols.passkey, - Capability.piv => Symbols.approval, + Capability.piv => Symbols.badge, _ => throw UnsupportedError('Icon not defined'), }; } diff --git a/lib/piv/views/piv_screen.dart b/lib/piv/views/piv_screen.dart index dda623cd..a4a06c3e 100644 --- a/lib/piv/views/piv_screen.dart +++ b/lib/piv/views/piv_screen.dart @@ -226,7 +226,7 @@ class _CertificateListItem extends ConsumerWidget { leading: CircleAvatar( foregroundColor: colorScheme.onSecondary, backgroundColor: colorScheme.secondary, - child: const Icon(Symbols.approval), + child: const Icon(Symbols.badge), ), title: slot.getDisplayName(l10n), subtitle: certInfo != null From 99ec84910c8277b13a3a644cdfe50febc8b7552f Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Fri, 8 Mar 2024 17:35:50 +0100 Subject: [PATCH 2/2] Refactor Application enum Rename to Section, remove "management", rename "webauthn". --- lib/android/init.dart | 4 +- lib/android/state.dart | 12 ++--- lib/app/features.dart | 3 +- lib/app/models.dart | 39 ++++++--------- lib/app/state.dart | 69 +++++++++++++------------- lib/app/views/app_failure_page.dart | 6 +-- lib/app/views/device_error_screen.dart | 4 +- lib/app/views/main_page.dart | 33 ++++++------ lib/app/views/navigation.dart | 63 +++++++++++------------ lib/fido/views/passkeys_screen.dart | 8 +-- lib/fido/views/webauthn_page.dart | 2 +- lib/home/views/key_actions.dart | 15 +++--- lib/l10n/app_de.arb | 2 +- lib/l10n/app_en.arb | 2 +- lib/l10n/app_fr.arb | 2 +- lib/l10n/app_ja.arb | 2 +- lib/l10n/app_pl.arb | 2 +- 17 files changed, 128 insertions(+), 140 deletions(-) diff --git a/lib/android/init.dart b/lib/android/init.dart index 8ab04cbf..c670b693 100644 --- a/lib/android/init.dart +++ b/lib/android/init.dart @@ -64,8 +64,8 @@ Future initialize() async { oathStateProvider.overrideWithProvider(androidOathStateProvider.call), credentialListProvider .overrideWithProvider(androidCredentialListProvider.call), - currentAppProvider.overrideWith((ref) => AndroidSubPageNotifier( - ref.watch(supportedAppsProvider), ref.watch(prefProvider))), + currentSectionProvider.overrideWith((ref) => AndroidSectionNotifier( + ref.watch(supportedSectionsProvider), ref.watch(prefProvider))), managementStateProvider.overrideWithProvider(androidManagementState.call), currentDeviceProvider.overrideWith( () => AndroidCurrentDeviceNotifier(), diff --git a/lib/android/state.dart b/lib/android/state.dart index bff41235..98989a9f 100644 --- a/lib/android/state.dart +++ b/lib/android/state.dart @@ -90,18 +90,18 @@ final androidSupportedThemesProvider = StateProvider>((ref) { } }); -class AndroidSubPageNotifier extends CurrentAppNotifier { - AndroidSubPageNotifier(super.supportedApps, super.prefs) { +class AndroidSectionNotifier extends CurrentSectionNotifier { + AndroidSectionNotifier(super._supportedSections, super.prefs) { _handleSubPage(state); } @override - void setCurrentApp(Application app) { - super.setCurrentApp(app); - _handleSubPage(app); + void setCurrentSection(Section section) { + super.setCurrentSection(section); + _handleSubPage(section); } - void _handleSubPage(Application subPage) async { + void _handleSubPage(Section subPage) async { await _contextChannel.invokeMethod('setContext', {'index': subPage.index}); } } diff --git a/lib/app/features.dart b/lib/app/features.dart index 95e437f7..c9f6eca3 100644 --- a/lib/app/features.dart +++ b/lib/app/features.dart @@ -16,11 +16,12 @@ import '../core/state.dart'; +final home = root.feature('home'); final oath = root.feature('oath'); final fido = root.feature('fido'); final piv = root.feature('piv'); final otp = root.feature('otp'); + final management = root.feature('management'); -final home = root.feature('home'); final fingerprints = fido.feature('fingerprints'); diff --git a/lib/app/models.dart b/lib/app/models.dart index 041e3212..1f45ebab 100755 --- a/lib/app/models.dart +++ b/lib/app/models.dart @@ -31,43 +31,32 @@ const _listEquality = ListEquality(); enum Availability { enabled, disabled, unsupported } -enum Application { +enum Section { home(), accounts([Capability.oath]), - webauthn([Capability.u2f]), + securityKey([Capability.u2f]), fingerprints([Capability.fido2]), passkeys([Capability.fido2]), certificates([Capability.piv]), - slots([Capability.otp]), - management(); + slots([Capability.otp]); final List capabilities; - const Application([this.capabilities = const []]); + const Section([this.capabilities = const []]); String getDisplayName(AppLocalizations l10n) => switch (this) { - Application.home => l10n.s_home, - Application.accounts => l10n.s_accounts, - Application.webauthn => l10n.s_webauthn, - Application.fingerprints => l10n.s_fingerprints, - Application.passkeys => l10n.s_passkeys, - Application.certificates => l10n.s_certificates, - Application.slots => l10n.s_slots, - _ => name.substring(0, 1).toUpperCase() + name.substring(1), + Section.home => l10n.s_home, + Section.accounts => l10n.s_accounts, + Section.securityKey => l10n.s_security_key, + Section.fingerprints => l10n.s_fingerprints, + Section.passkeys => l10n.s_passkeys, + Section.certificates => l10n.s_certificates, + Section.slots => l10n.s_slots, }; Availability getAvailability(YubiKeyData data) { - if (this == Application.management) { - final version = data.info.version; - final available = (version.major > 4 || // YK5 and up - (version.major == 4 && version.minor >= 1) || // YK4.1 and up - version.major == 3); // NEO - // Management can't be disabled - return available ? Availability.enabled : Availability.unsupported; - } - // TODO: Require credman for passkeys? - if (this == Application.fingerprints) { + if (this == Section.fingerprints) { if (!const {FormFactor.usbABio, FormFactor.usbCBio} .contains(data.info.formFactor)) { return Availability.unsupported; @@ -79,8 +68,8 @@ enum Application { final int enabled = data.info.config.enabledCapabilities[data.node.transport] ?? 0; - // Don't show WebAuthn if we have FIDO2 - if (this == Application.webauthn && + // Don't show securityKey if we have FIDO2 + if (this == Section.securityKey && Capability.fido2.value & supported != 0) { return Availability.unsupported; } diff --git a/lib/app/state.dart b/lib/app/state.dart index 04865b53..6b1ddbc4 100755 --- a/lib/app/state.dart +++ b/lib/app/state.dart @@ -38,23 +38,24 @@ const officialLocales = [ Locale('en', ''), ]; -extension on Application { +extension on Section { Feature get _feature => switch (this) { - Application.home => features.home, - Application.accounts => features.oath, - Application.webauthn => features.fido, - Application.passkeys => features.fido, - Application.fingerprints => features.fingerprints, - Application.slots => features.otp, - Application.certificates => features.piv, - Application.management => features.management, + Section.home => features.home, + Section.accounts => features.oath, + Section.securityKey => features.fido, + Section.passkeys => features.fido, + Section.fingerprints => features.fingerprints, + Section.slots => features.otp, + Section.certificates => features.piv, }; } -final supportedAppsProvider = Provider>( +final supportedSectionsProvider = Provider>( (ref) { final hasFeature = ref.watch(featureProvider); - return Application.values.where((app) => hasFeature(app._feature)).toList(); + return Section.values + .where((section) => hasFeature(section._feature)) + .toList(); }, ); @@ -201,62 +202,62 @@ abstract class CurrentDeviceNotifier extends Notifier { setCurrentDevice(DeviceNode? device); } -final currentAppProvider = - StateNotifierProvider((ref) { - final notifier = CurrentAppNotifier( - ref.watch(supportedAppsProvider), ref.watch(prefProvider)); +final currentSectionProvider = + StateNotifierProvider((ref) { + final notifier = CurrentSectionNotifier( + ref.watch(supportedSectionsProvider), ref.watch(prefProvider)); ref.listen>(currentDeviceDataProvider, (_, data) { notifier._notifyDeviceChanged(data.whenOrNull(data: ((data) => data))); }, fireImmediately: true); return notifier; }); -class CurrentAppNotifier extends StateNotifier { - final List _supportedApps; - static const String _key = 'APP_STATE_LAST_APP'; +class CurrentSectionNotifier extends StateNotifier
{ + final List
_supportedSections; + static const String _key = 'APP_STATE_LAST_SECTION'; final SharedPreferences _prefs; - CurrentAppNotifier(this._supportedApps, this._prefs) - : super(_fromName(_prefs.getString(_key), _supportedApps)); + CurrentSectionNotifier(this._supportedSections, this._prefs) + : super(_fromName(_prefs.getString(_key), _supportedSections)); - void setCurrentApp(Application app) { - state = app; - _prefs.setString(_key, app.name); + void setCurrentSection(Section section) { + state = section; + _prefs.setString(_key, section.name); } void _notifyDeviceChanged(YubiKeyData? data) { if (data == null) { - state = _supportedApps.first; + state = _supportedSections.first; return; } String? lastAppName = _prefs.getString(_key); if (lastAppName != null && lastAppName != state.name) { // Try switching to saved app - state = Application.values.firstWhere((app) => app.name == lastAppName); + state = Section.values.firstWhere((app) => app.name == lastAppName); } - if (state == Application.passkeys && + if (state == Section.passkeys && state.getAvailability(data) != Availability.enabled) { - state = Application.webauthn; + state = Section.securityKey; } - if (state == Application.webauthn && + if (state == Section.securityKey && state.getAvailability(data) != Availability.enabled) { - state = Application.passkeys; + state = Section.passkeys; } if (state.getAvailability(data) != Availability.unsupported) { // Keep current app return; } - state = _supportedApps.firstWhere( + state = _supportedSections.firstWhere( (app) => app.getAvailability(data) == Availability.enabled, - orElse: () => _supportedApps.first, + orElse: () => _supportedSections.first, ); } - static Application _fromName(String? name, List supportedApps) => - supportedApps.firstWhere((element) => element.name == name, - orElse: () => supportedApps.first); + static Section _fromName(String? name, List
supportedSections) => + supportedSections.firstWhere((element) => element.name == name, + orElse: () => supportedSections.first); } abstract class QrScanner { diff --git a/lib/app/views/app_failure_page.dart b/lib/app/views/app_failure_page.dart index 3a3eb7f5..e2ada04f 100755 --- a/lib/app/views/app_failure_page.dart +++ b/lib/app/views/app_failure_page.dart @@ -64,9 +64,9 @@ class AppFailurePage extends ConsumerWidget { case 'fido': if (Platform.isWindows && !ref.watch(rpcStateProvider.select((state) => state.isAdmin))) { - final currentApp = ref.read(currentAppProvider); - title = currentApp.getDisplayName(l10n); - capabilities = currentApp.capabilities; + final currentSection = ref.read(currentSectionProvider); + title = currentSection.getDisplayName(l10n); + capabilities = currentSection.capabilities; header = l10n.l_admin_privileges_required; message = l10n.p_webauthn_elevated_permissions_required; centered = false; diff --git a/lib/app/views/device_error_screen.dart b/lib/app/views/device_error_screen.dart index 2b793927..fe97c8bc 100755 --- a/lib/app/views/device_error_screen.dart +++ b/lib/app/views/device_error_screen.dart @@ -39,9 +39,9 @@ class DeviceErrorScreen extends ConsumerWidget { if (pid.usbInterfaces == UsbInterface.fido.value) { if (Platform.isWindows && !ref.watch(rpcStateProvider.select((state) => state.isAdmin))) { - final currentApp = ref.read(currentAppProvider); + final currentSection = ref.read(currentSectionProvider); return HomeMessagePage( - capabilities: currentApp.capabilities, + capabilities: currentSection.capabilities, header: l10n.l_admin_privileges_required, message: l10n.p_elevated_permissions_required, actionsBuilder: (context, expanded) => [ diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index c351c18b..a09537f0 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -118,8 +118,8 @@ class MainPage extends ConsumerWidget { } else { return ref.watch(currentDeviceDataProvider).when( data: (data) { - final app = ref.watch(currentAppProvider); - final capabilities = app.capabilities; + final section = ref.watch(currentSectionProvider); + final capabilities = section.capabilities; if (data.info.supportedCapabilities.isEmpty && data.name == 'Unrecognized device') { return HomeMessagePage( @@ -131,19 +131,20 @@ class MainPage extends ConsumerWidget { ), header: l10n.s_yk_not_recognized, ); - } else if (app.getAvailability(data) == + } else if (section.getAvailability(data) == Availability.unsupported) { return MessagePage( - title: app.getDisplayName(l10n), + title: section.getDisplayName(l10n), capabilities: capabilities, header: l10n.s_app_not_supported, message: l10n.l_app_not_supported_on_yk(capabilities .map((c) => c.getDisplayName(l10n)) .join(',')), ); - } else if (app.getAvailability(data) != Availability.enabled) { + } else if (section.getAvailability(data) != + Availability.enabled) { return MessagePage( - title: app.getDisplayName(l10n), + title: section.getDisplayName(l10n), capabilities: capabilities, header: l10n.s_app_disabled, message: l10n.l_app_disabled_desc(capabilities @@ -166,18 +167,14 @@ class MainPage extends ConsumerWidget { ); } - return switch (app) { - Application.home => HomeScreen(data), - Application.accounts => OathScreen(data.node.path), - Application.webauthn => const WebAuthnScreen(), - Application.passkeys => PasskeysScreen(data), - Application.fingerprints => FingerprintsScreen(data), - Application.certificates => PivScreen(data.node.path), - Application.slots => OtpScreen(data.node.path), - _ => MessagePage( - header: l10n.s_app_not_supported, - message: l10n.l_app_not_supported_desc, - ), + return switch (section) { + Section.home => HomeScreen(data), + Section.accounts => OathScreen(data.node.path), + Section.securityKey => const WebAuthnScreen(), + Section.passkeys => PasskeysScreen(data), + Section.fingerprints => FingerprintsScreen(data), + Section.certificates => PivScreen(data.node.path), + Section.slots => OtpScreen(data.node.path), }; }, loading: () => DeviceErrorScreen(deviceNode), diff --git a/lib/app/views/navigation.dart b/lib/app/views/navigation.dart index 627ee891..f03fac8b 100644 --- a/lib/app/views/navigation.dart +++ b/lib/app/views/navigation.dart @@ -86,27 +86,25 @@ class NavigationItem extends StatelessWidget { } } -extension on Application { +extension on Section { IconData get _icon => switch (this) { - Application.accounts => Symbols.supervisor_account, - Application.webauthn => Symbols.security_key, - Application.passkeys => Symbols.passkey, - Application.fingerprints => Symbols.fingerprint, - Application.slots => Symbols.touch_app, - Application.certificates => Symbols.badge, - Application.management => Symbols.construction, - Application.home => Symbols.home + Section.home => Symbols.home, + Section.accounts => Symbols.supervisor_account, + Section.securityKey => Symbols.security_key, + Section.passkeys => Symbols.passkey, + Section.fingerprints => Symbols.fingerprint, + Section.slots => Symbols.touch_app, + Section.certificates => Symbols.badge, }; Key get _key => switch (this) { - Application.accounts => oathAppDrawer, - Application.webauthn => u2fAppDrawer, - Application.passkeys => fidoPasskeysAppDrawer, - Application.fingerprints => fidoFingerprintsAppDrawer, - Application.slots => otpAppDrawer, - Application.certificates => pivAppDrawer, - Application.management => managementAppDrawer, - Application.home => homeDrawer, + Section.home => homeDrawer, + Section.accounts => oathAppDrawer, + Section.securityKey => u2fAppDrawer, + Section.passkeys => fidoPasskeysAppDrawer, + Section.fingerprints => fidoFingerprintsAppDrawer, + Section.slots => otpAppDrawer, + Section.certificates => pivAppDrawer, }; } @@ -119,19 +117,18 @@ class NavigationContent extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final l10n = AppLocalizations.of(context)!; - final supportedApps = ref.watch(supportedAppsProvider); + final supportedSections = ref.watch(supportedSectionsProvider); final data = ref.watch(currentDeviceDataProvider).valueOrNull; - final availableApps = data != null - ? supportedApps - .where( - (app) => app.getAvailability(data) != Availability.unsupported) + final availableSections = data != null + ? supportedSections + .where((section) => + section.getAvailability(data) != Availability.unsupported) .toList() : !isAndroid // TODO: Remove check when Home is implemented on Android - ? [Application.home] - : []; - availableApps.remove(Application.management); - final currentApp = ref.watch(currentAppProvider); + ? [Section.home] + :
[]; + final currentSection = ref.watch(currentSectionProvider); return Padding( padding: const EdgeInsets.all(8.0), @@ -147,21 +144,21 @@ class NavigationContent extends ConsumerWidget { child: Column( children: [ // Normal YubiKey Applications - ...availableApps.map((app) => NavigationItem( + ...availableSections.map((app) => NavigationItem( key: app._key, title: app.getDisplayName(l10n), - leading: - Icon(app._icon, fill: app == currentApp ? 1.0 : 0.0), + leading: Icon(app._icon, + fill: app == currentSection ? 1.0 : 0.0), collapsed: !extended, - selected: app == currentApp, - onTap: data == null && currentApp == Application.home || + selected: app == currentSection, + onTap: data == null && currentSection == Section.home || data != null && app.getAvailability(data) == Availability.enabled ? () { ref - .read(currentAppProvider.notifier) - .setCurrentApp(app); + .read(currentSectionProvider.notifier) + .setCurrentSection(app); if (shouldPop) { Navigator.of(context).pop(); } diff --git a/lib/fido/views/passkeys_screen.dart b/lib/fido/views/passkeys_screen.dart index 2c647fb0..5c85b936 100644 --- a/lib/fido/views/passkeys_screen.dart +++ b/lib/fido/views/passkeys_screen.dart @@ -106,8 +106,8 @@ class _FidoLockedPage extends ConsumerWidget { label: Text(l10n.s_setup_fingerprints), onPressed: () async { ref - .read(currentAppProvider.notifier) - .setCurrentApp(Application.fingerprints); + .read(currentSectionProvider.notifier) + .setCurrentSection(Section.fingerprints); }, avatar: const Icon(Symbols.fingerprint), ), @@ -242,8 +242,8 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> { label: Text(l10n.s_setup_fingerprints), onPressed: () async { ref - .read(currentAppProvider.notifier) - .setCurrentApp(Application.fingerprints); + .read(currentSectionProvider.notifier) + .setCurrentSection(Section.fingerprints); }, avatar: const Icon(Symbols.fingerprint), ) diff --git a/lib/fido/views/webauthn_page.dart b/lib/fido/views/webauthn_page.dart index 2efbc1b0..fcaef06d 100644 --- a/lib/fido/views/webauthn_page.dart +++ b/lib/fido/views/webauthn_page.dart @@ -27,7 +27,7 @@ class WebAuthnScreen extends StatelessWidget { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return MessagePage( - title: l10n.s_webauthn, + title: l10n.s_security_key, capabilities: const [Capability.u2f], header: l10n.l_ready_to_use, message: l10n.l_register_sk_on_websites, diff --git a/lib/home/views/key_actions.dart b/lib/home/views/key_actions.dart index dbb51857..255b6f36 100644 --- a/lib/home/views/key_actions.dart +++ b/lib/home/views/key_actions.dart @@ -27,6 +27,7 @@ import '../../app/shortcuts.dart'; import '../../app/state.dart'; import '../../app/views/action_list.dart'; import '../../app/views/reset_dialog.dart'; +import '../../core/models.dart'; import '../../core/state.dart'; import '../../management/views/management_screen.dart'; @@ -34,11 +35,13 @@ Widget homeBuildActions( BuildContext context, YubiKeyData? deviceData, WidgetRef ref) { final l10n = AppLocalizations.of(context)!; final hasFeature = ref.watch(featureProvider); - - final managementAvailability = - deviceData == null || !hasFeature(features.management) - ? Availability.unsupported - : Application.management.getAvailability(deviceData); + final managementAvailability = hasFeature(features.management) && + switch (deviceData?.info.version) { + Version version => (version.major > 4 || // YK5 and up + (version.major == 4 && version.minor >= 1) || // YK4.1 and up + version.major == 3), // NEO, + null => false, + }; return Column( children: [ @@ -46,7 +49,7 @@ Widget homeBuildActions( ActionListSection( l10n.s_device, children: [ - if (managementAvailability == Availability.enabled) + if (managementAvailability) ActionListItem( feature: features.management, icon: const Icon(Symbols.construction), diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index eb158dee..e2e7f0f8 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -66,7 +66,7 @@ "s_settings": "Einstellungen", "l_settings_desc": null, "s_certificates": null, - "s_webauthn": "WebAuthn", + "s_security_key": null, "s_slots": null, "s_help_and_about": "Hilfe und Über", "l_help_and_about_desc": null, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 034ee62c..27e3294c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -66,7 +66,7 @@ "s_settings": "Settings", "l_settings_desc": "Change application preferences", "s_certificates": "Certificates", - "s_webauthn": "WebAuthn", + "s_security_key": "Security Key", "s_slots": "Slots", "s_help_and_about": "Help and about", "l_help_and_about_desc": "Troubleshoot and support", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index bcd182ca..8c9eabff 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -66,7 +66,7 @@ "s_settings": "Paramètres", "l_settings_desc": null, "s_certificates": "Certificats", - "s_webauthn": "WebAuthn", + "s_security_key": null, "s_slots": null, "s_help_and_about": "Aide et à propos", "l_help_and_about_desc": null, diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index da406806..1bb9cb41 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -66,7 +66,7 @@ "s_settings": "設定", "l_settings_desc": null, "s_certificates": "証明書", - "s_webauthn": "WebAuthn", + "s_security_key": null, "s_slots": null, "s_help_and_about": "ヘルプと概要", "l_help_and_about_desc": null, diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 7ce3e7a0..887daa5c 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -66,7 +66,7 @@ "s_settings": "Ustawienia", "l_settings_desc": null, "s_certificates": "Certyfikaty", - "s_webauthn": "WebAuthn", + "s_security_key": null, "s_slots": "Sloty", "s_help_and_about": "Pomoc i informacje", "l_help_and_about_desc": null,