mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 02:01:36 +03:00
Merge PR #1429
This commit is contained in:
commit
52c613a14c
@ -64,8 +64,8 @@ Future<Widget> 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(),
|
||||
|
@ -90,18 +90,18 @@ final androidSupportedThemesProvider = StateProvider<List<ThemeMode>>((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});
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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<Capability> 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;
|
||||
}
|
||||
|
@ -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<List<Application>>(
|
||||
final supportedSectionsProvider = Provider<List<Section>>(
|
||||
(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<DeviceNode?> {
|
||||
setCurrentDevice(DeviceNode? device);
|
||||
}
|
||||
|
||||
final currentAppProvider =
|
||||
StateNotifierProvider<CurrentAppNotifier, Application>((ref) {
|
||||
final notifier = CurrentAppNotifier(
|
||||
ref.watch(supportedAppsProvider), ref.watch(prefProvider));
|
||||
final currentSectionProvider =
|
||||
StateNotifierProvider<CurrentSectionNotifier, Section>((ref) {
|
||||
final notifier = CurrentSectionNotifier(
|
||||
ref.watch(supportedSectionsProvider), ref.watch(prefProvider));
|
||||
ref.listen<AsyncValue<YubiKeyData>>(currentDeviceDataProvider, (_, data) {
|
||||
notifier._notifyDeviceChanged(data.whenOrNull(data: ((data) => data)));
|
||||
}, fireImmediately: true);
|
||||
return notifier;
|
||||
});
|
||||
|
||||
class CurrentAppNotifier extends StateNotifier<Application> {
|
||||
final List<Application> _supportedApps;
|
||||
static const String _key = 'APP_STATE_LAST_APP';
|
||||
class CurrentSectionNotifier extends StateNotifier<Section> {
|
||||
final List<Section> _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<Application> supportedApps) =>
|
||||
supportedApps.firstWhere((element) => element.name == name,
|
||||
orElse: () => supportedApps.first);
|
||||
static Section _fromName(String? name, List<Section> supportedSections) =>
|
||||
supportedSections.firstWhere((element) => element.name == name,
|
||||
orElse: () => supportedSections.first);
|
||||
}
|
||||
|
||||
abstract class QrScanner {
|
||||
|
@ -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;
|
||||
|
@ -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) => [
|
||||
|
@ -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),
|
||||
|
@ -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.approval,
|
||||
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]
|
||||
: <Application>[];
|
||||
availableApps.remove(Application.management);
|
||||
final currentApp = ref.watch(currentAppProvider);
|
||||
? [Section.home]
|
||||
: <Section>[];
|
||||
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();
|
||||
}
|
||||
|
@ -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'),
|
||||
};
|
||||
}
|
||||
|
@ -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),
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -26,6 +26,7 @@ import '../../app/models.dart';
|
||||
import '../../app/shortcuts.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';
|
||||
|
||||
@ -33,11 +34,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: [
|
||||
@ -45,7 +48,7 @@ Widget homeBuildActions(
|
||||
ActionListSection(
|
||||
l10n.s_device,
|
||||
children: [
|
||||
if (managementAvailability == Availability.enabled)
|
||||
if (managementAvailability)
|
||||
ActionListItem(
|
||||
feature: features.management,
|
||||
icon: const Icon(Symbols.construction),
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user