mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-26 10:33:15 +03:00
Merge branch 'main' into bump/andorid-deps-20230821
This commit is contained in:
commit
acb97f941a
@ -32,10 +32,12 @@ Future<T?> showBlurDialog<T>({
|
|||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required Widget Function(BuildContext) builder,
|
required Widget Function(BuildContext) builder,
|
||||||
RouteSettings? routeSettings,
|
RouteSettings? routeSettings,
|
||||||
|
Color barrierColor = const Color(0x80000000),
|
||||||
}) async =>
|
}) async =>
|
||||||
await showGeneralDialog<T>(
|
await showGeneralDialog<T>(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: true,
|
barrierDismissible: true,
|
||||||
|
barrierColor: barrierColor,
|
||||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||||
pageBuilder: (ctx, anim1, anim2) => builder(ctx),
|
pageBuilder: (ctx, anim1, anim2) => builder(ctx),
|
||||||
transitionDuration: const Duration(milliseconds: 150),
|
transitionDuration: const Duration(milliseconds: 150),
|
||||||
|
@ -224,7 +224,11 @@ class AppPage extends StatelessWidget {
|
|||||||
child: IconButton(
|
child: IconButton(
|
||||||
key: actionsIconButtonKey,
|
key: actionsIconButtonKey,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showBlurDialog(context: context, builder: keyActionsBuilder!);
|
showBlurDialog(
|
||||||
|
context: context,
|
||||||
|
barrierColor: Colors.transparent,
|
||||||
|
builder: keyActionsBuilder!,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
icon: keyActionsBadge
|
icon: keyActionsBadge
|
||||||
? const Badge(
|
? const Badge(
|
||||||
|
@ -26,7 +26,8 @@ class FsDialog extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
return Dialog.fullscreen(
|
return Dialog.fullscreen(
|
||||||
backgroundColor: Theme.of(context).colorScheme.background.withAlpha(100),
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.background.withOpacity(0.7),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
@ -59,6 +59,7 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
OpenIntent: CallbackAction<OpenIntent>(
|
OpenIntent: CallbackAction<OpenIntent>(
|
||||||
onInvoke: (_) => showBlurDialog(
|
onInvoke: (_) => showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierColor: Colors.transparent,
|
||||||
builder: (context) => CredentialDialog(cred),
|
builder: (context) => CredentialDialog(cred),
|
||||||
)),
|
)),
|
||||||
DeleteIntent: CallbackAction<DeleteIntent>(
|
DeleteIntent: CallbackAction<DeleteIntent>(
|
||||||
@ -91,6 +92,7 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
OpenIntent: CallbackAction<OpenIntent>(
|
OpenIntent: CallbackAction<OpenIntent>(
|
||||||
onInvoke: (_) => showBlurDialog(
|
onInvoke: (_) => showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierColor: Colors.transparent,
|
||||||
builder: (context) => FingerprintDialog(fp),
|
builder: (context) => FingerprintDialog(fp),
|
||||||
)),
|
)),
|
||||||
EditIntent: CallbackAction<EditIntent>(
|
EditIntent: CallbackAction<EditIntent>(
|
||||||
|
@ -48,6 +48,12 @@
|
|||||||
"item": {}
|
"item": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"s_definition": "{item}:",
|
||||||
|
"@s_definition" : {
|
||||||
|
"placeholders": {
|
||||||
|
"item": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"s_about": "About",
|
"s_about": "About",
|
||||||
"s_appearance": "Appearance",
|
"s_appearance": "Appearance",
|
||||||
@ -212,6 +218,12 @@
|
|||||||
"retries": {}
|
"retries": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"l_wrong_puk_attempts_remaining": "Wrong PUK, {retries} attempt(s) remaining",
|
||||||
|
"@l_wrong_puk_attempts_remaining" : {
|
||||||
|
"placeholders": {
|
||||||
|
"retries": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"s_fido_pin_protection": "FIDO PIN protection",
|
"s_fido_pin_protection": "FIDO PIN protection",
|
||||||
"l_fido_pin_protection_optional": "Optional FIDO PIN protection",
|
"l_fido_pin_protection_optional": "Optional FIDO PIN protection",
|
||||||
"l_enter_fido2_pin": "Enter the FIDO2 PIN for your YubiKey",
|
"l_enter_fido2_pin": "Enter the FIDO2 PIN for your YubiKey",
|
||||||
@ -231,6 +243,7 @@
|
|||||||
"s_pin_required": "PIN required",
|
"s_pin_required": "PIN required",
|
||||||
"p_pin_required_desc": "The action you are about to perform requires the PIV PIN to be entered.",
|
"p_pin_required_desc": "The action you are about to perform requires the PIV PIN to be entered.",
|
||||||
"l_piv_pin_blocked": "Blocked, use PUK to reset",
|
"l_piv_pin_blocked": "Blocked, use PUK to reset",
|
||||||
|
"l_piv_pin_puk_blocked": "Blocked, factory reset needed",
|
||||||
"p_enter_new_piv_pin_puk": "Enter a new {name} to set. Must be 6-8 characters.",
|
"p_enter_new_piv_pin_puk": "Enter a new {name} to set. Must be 6-8 characters.",
|
||||||
"@p_enter_new_piv_pin_puk" : {
|
"@p_enter_new_piv_pin_puk" : {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@ -418,32 +431,11 @@
|
|||||||
"l_import_desc": "Import a key and/or certificate",
|
"l_import_desc": "Import a key and/or certificate",
|
||||||
"l_delete_certificate": "Delete certificate",
|
"l_delete_certificate": "Delete certificate",
|
||||||
"l_delete_certificate_desc": "Remove the certificate from your YubiKey",
|
"l_delete_certificate_desc": "Remove the certificate from your YubiKey",
|
||||||
"l_subject_issuer": "Subject: {subject}, Issuer: {issuer}",
|
"s_issuer": "Issuer",
|
||||||
"@l_subject_issuer" : {
|
"s_serial": "Serial",
|
||||||
"placeholders": {
|
"s_certificate_fingerprint": "Fingerprint",
|
||||||
"subject": {},
|
"s_valid_from": "Valid from",
|
||||||
"issuer": {}
|
"s_valid_to": "Valid to",
|
||||||
}
|
|
||||||
},
|
|
||||||
"l_serial": "Serial: {serial}",
|
|
||||||
"@l_serial" : {
|
|
||||||
"placeholders": {
|
|
||||||
"serial": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"l_certificate_fingerprint": "Fingerprint: {fingerprint}",
|
|
||||||
"@l_certificate_fingerprint" : {
|
|
||||||
"placeholders": {
|
|
||||||
"fingerprint": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"l_valid": "Valid: {not_before} - {not_after}",
|
|
||||||
"@l_valid" : {
|
|
||||||
"placeholders": {
|
|
||||||
"not_before": {},
|
|
||||||
"not_after": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"l_no_certificate": "No certificate loaded",
|
"l_no_certificate": "No certificate loaded",
|
||||||
"l_key_no_certificate": "Key without certificate loaded",
|
"l_key_no_certificate": "Key without certificate loaded",
|
||||||
"s_generate_key": "Generate key",
|
"s_generate_key": "Generate key",
|
||||||
|
@ -121,40 +121,51 @@ class AccountDialog extends ConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 48, bottom: 16),
|
padding:
|
||||||
child: Row(
|
const EdgeInsets.symmetric(horizontal: 12, vertical: 32),
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
IconTheme(
|
Padding(
|
||||||
data: IconTheme.of(context).copyWith(size: 24),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
child: helper.buildCodeIcon(),
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
IconTheme(
|
||||||
|
data: IconTheme.of(context).copyWith(size: 24),
|
||||||
|
child: helper.buildCodeIcon(),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8.0),
|
||||||
|
DefaultTextStyle.merge(
|
||||||
|
style: const TextStyle(fontSize: 28),
|
||||||
|
child: helper.buildCodeLabel(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8.0),
|
Text(
|
||||||
DefaultTextStyle.merge(
|
helper.title,
|
||||||
style: const TextStyle(fontSize: 28),
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
child: helper.buildCodeLabel(),
|
softWrap: true,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
|
if (subtitle != null)
|
||||||
|
Text(
|
||||||
|
subtitle,
|
||||||
|
softWrap: true,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
// This is what ListTile uses for subtitle
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall!
|
||||||
|
.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
|
||||||
helper.title,
|
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
|
||||||
softWrap: true,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
if (subtitle != null)
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
softWrap: true,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
// This is what ListTile uses for subtitle
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
color: Theme.of(context).textTheme.bodySmall!.color,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
ActionListSection.fromMenuActions(
|
ActionListSection.fromMenuActions(
|
||||||
context,
|
context,
|
||||||
AppLocalizations.of(context)!.s_actions,
|
AppLocalizations.of(context)!.s_actions,
|
||||||
|
@ -89,6 +89,7 @@ class _AccountViewState extends ConsumerState<AccountView> {
|
|||||||
OpenIntent: CallbackAction<OpenIntent>(onInvoke: (_) async {
|
OpenIntent: CallbackAction<OpenIntent>(onInvoke: (_) async {
|
||||||
await showBlurDialog(
|
await showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierColor: Colors.transparent,
|
||||||
builder: (context) => AccountDialog(credential),
|
builder: (context) => AccountDialog(credential),
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
|
@ -24,7 +24,7 @@ part 'models.g.dart';
|
|||||||
|
|
||||||
const defaultManagementKey = '010203040506070801020304050607080102030405060708';
|
const defaultManagementKey = '010203040506070801020304050607080102030405060708';
|
||||||
const defaultManagementKeyType = ManagementKeyType.tdes;
|
const defaultManagementKeyType = ManagementKeyType.tdes;
|
||||||
const defaultKeyType = KeyType.rsa2048;
|
const defaultKeyType = KeyType.eccp256;
|
||||||
const defaultGenerateType = GenerateType.certificate;
|
const defaultGenerateType = GenerateType.certificate;
|
||||||
|
|
||||||
enum GenerateType {
|
enum GenerateType {
|
||||||
|
@ -39,6 +39,7 @@ Widget pivBuildActions(BuildContext context, DevicePath devicePath,
|
|||||||
|
|
||||||
final pinBlocked = pivState.pinAttempts == 0;
|
final pinBlocked = pivState.pinAttempts == 0;
|
||||||
final pukAttempts = pivState.metadata?.pukMetadata.attemptsRemaining;
|
final pukAttempts = pivState.metadata?.pukMetadata.attemptsRemaining;
|
||||||
|
final alertIcon = Icon(Icons.warning_amber, color: colors.tertiary);
|
||||||
|
|
||||||
return FsDialog(
|
return FsDialog(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -50,35 +51,46 @@ Widget pivBuildActions(BuildContext context, DevicePath devicePath,
|
|||||||
key: keys.managePinAction,
|
key: keys.managePinAction,
|
||||||
title: l10n.s_pin,
|
title: l10n.s_pin,
|
||||||
subtitle: pinBlocked
|
subtitle: pinBlocked
|
||||||
? l10n.l_piv_pin_blocked
|
? (pukAttempts != 0
|
||||||
|
? l10n.l_piv_pin_blocked
|
||||||
|
: l10n.l_piv_pin_puk_blocked)
|
||||||
: l10n.l_attempts_remaining(pivState.pinAttempts),
|
: l10n.l_attempts_remaining(pivState.pinAttempts),
|
||||||
icon: const Icon(Icons.pin_outlined),
|
icon: const Icon(Icons.pin_outlined),
|
||||||
onTap: (context) {
|
trailing: pinBlocked ? alertIcon : null,
|
||||||
Navigator.of(context).pop();
|
onTap: !(pinBlocked && pukAttempts == 0)
|
||||||
showBlurDialog(
|
? (context) {
|
||||||
context: context,
|
Navigator.of(context).pop();
|
||||||
builder: (context) => ManagePinPukDialog(
|
showBlurDialog(
|
||||||
devicePath,
|
context: context,
|
||||||
target:
|
builder: (context) => ManagePinPukDialog(
|
||||||
pinBlocked ? ManageTarget.unblock : ManageTarget.pin,
|
devicePath,
|
||||||
),
|
target: pinBlocked
|
||||||
);
|
? ManageTarget.unblock
|
||||||
}),
|
: ManageTarget.pin,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null),
|
||||||
ActionListItem(
|
ActionListItem(
|
||||||
key: keys.managePukAction,
|
key: keys.managePukAction,
|
||||||
title: l10n.s_puk,
|
title: l10n.s_puk,
|
||||||
subtitle: pukAttempts != null
|
subtitle: pukAttempts != null
|
||||||
? l10n.l_attempts_remaining(pukAttempts)
|
? (pukAttempts == 0
|
||||||
|
? l10n.l_piv_pin_puk_blocked
|
||||||
|
: l10n.l_attempts_remaining(pukAttempts))
|
||||||
: null,
|
: null,
|
||||||
icon: const Icon(Icons.pin_outlined),
|
icon: const Icon(Icons.pin_outlined),
|
||||||
onTap: (context) {
|
trailing: pukAttempts == 0 ? alertIcon : null,
|
||||||
Navigator.of(context).pop();
|
onTap: pukAttempts != 0
|
||||||
showBlurDialog(
|
? (context) {
|
||||||
context: context,
|
Navigator.of(context).pop();
|
||||||
builder: (context) => ManagePinPukDialog(devicePath,
|
showBlurDialog(
|
||||||
target: ManageTarget.puk),
|
context: context,
|
||||||
);
|
builder: (context) => ManagePinPukDialog(devicePath,
|
||||||
}),
|
target: ManageTarget.puk),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null),
|
||||||
ActionListItem(
|
ActionListItem(
|
||||||
key: keys.manageManagementKeyAction,
|
key: keys.manageManagementKeyAction,
|
||||||
title: l10n.s_management_key,
|
title: l10n.s_management_key,
|
||||||
@ -88,9 +100,7 @@ Widget pivBuildActions(BuildContext context, DevicePath devicePath,
|
|||||||
? l10n.l_pin_protected_key
|
? l10n.l_pin_protected_key
|
||||||
: l10n.l_change_management_key),
|
: l10n.l_change_management_key),
|
||||||
icon: const Icon(Icons.key_outlined),
|
icon: const Icon(Icons.key_outlined),
|
||||||
trailing: usingDefaultMgmtKey
|
trailing: usingDefaultMgmtKey ? alertIcon : null,
|
||||||
? Icon(Icons.warning_amber, color: colors.tertiary)
|
|
||||||
: null,
|
|
||||||
onTap: (context) {
|
onTap: (context) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
showBlurDialog(
|
showBlurDialog(
|
||||||
|
@ -114,7 +114,11 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
|
|||||||
: l10n.s_current_puk,
|
: l10n.s_current_puk,
|
||||||
prefixIcon: const Icon(Icons.password_outlined),
|
prefixIcon: const Icon(Icons.password_outlined),
|
||||||
errorText: _currentIsWrong
|
errorText: _currentIsWrong
|
||||||
? l10n.l_wrong_pin_attempts_remaining(_attemptsRemaining)
|
? (widget.target == ManageTarget.puk
|
||||||
|
? l10n.l_wrong_pin_attempts_remaining(
|
||||||
|
_attemptsRemaining)
|
||||||
|
: l10n.l_wrong_puk_attempts_remaining(
|
||||||
|
_attemptsRemaining))
|
||||||
: null,
|
: null,
|
||||||
errorMaxLines: 3),
|
errorMaxLines: 3),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
|
@ -70,6 +70,7 @@ class PivScreen extends ConsumerWidget {
|
|||||||
CallbackAction<OpenIntent>(onInvoke: (_) async {
|
CallbackAction<OpenIntent>(onInvoke: (_) async {
|
||||||
await showBlurDialog(
|
await showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierColor: Colors.transparent,
|
||||||
builder: (context) => SlotDialog(e.slot),
|
builder: (context) => SlotDialog(e.slot),
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
@ -104,7 +105,7 @@ class _CertificateListItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
title: slot.getDisplayName(l10n),
|
title: slot.getDisplayName(l10n),
|
||||||
subtitle: certInfo != null
|
subtitle: certInfo != null
|
||||||
? l10n.l_subject_issuer(certInfo.subject, certInfo.issuer)
|
? certInfo.subject
|
||||||
: pivSlot.hasKey == true
|
: pivSlot.hasKey == true
|
||||||
? l10n.l_key_no_certificate
|
? l10n.l_key_no_certificate
|
||||||
: l10n.l_no_certificate,
|
: l10n.l_no_certificate,
|
||||||
|
@ -1,10 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Yubico.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../../app/message.dart';
|
||||||
import '../../app/state.dart';
|
import '../../app/state.dart';
|
||||||
import '../../app/views/fs_dialog.dart';
|
import '../../app/views/fs_dialog.dart';
|
||||||
import '../../app/views/action_list.dart';
|
import '../../app/views/action_list.dart';
|
||||||
|
import '../../widgets/tooltip_if_truncated.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import 'actions.dart';
|
import 'actions.dart';
|
||||||
@ -29,6 +48,8 @@ class SlotDialog extends ConsumerWidget {
|
|||||||
final subtitleStyle = textTheme.bodyMedium!.copyWith(
|
final subtitleStyle = textTheme.bodyMedium!.copyWith(
|
||||||
color: textTheme.bodySmall!.color,
|
color: textTheme.bodySmall!.color,
|
||||||
);
|
);
|
||||||
|
final clipboard = ref.watch(clipboardProvider);
|
||||||
|
final withContext = ref.read(withContextProvider);
|
||||||
|
|
||||||
final pivState = ref.watch(pivStateProvider(node.path)).valueOrNull;
|
final pivState = ref.watch(pivStateProvider(node.path)).valueOrNull;
|
||||||
final slotData = ref.watch(pivSlotsProvider(node.path).select((value) =>
|
final slotData = ref.watch(pivSlotsProvider(node.path).select((value) =>
|
||||||
@ -40,6 +61,32 @@ class SlotDialog extends ConsumerWidget {
|
|||||||
return const FsDialog(child: CircularProgressIndicator());
|
return const FsDialog(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TableRow detailRow(String title, String value) {
|
||||||
|
return TableRow(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
l10n.s_definition(title),
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8.0),
|
||||||
|
GestureDetector(
|
||||||
|
onDoubleTap: () async {
|
||||||
|
await clipboard.setText(value);
|
||||||
|
if (!clipboard.platformGivesFeedback()) {
|
||||||
|
await withContext((context) async {
|
||||||
|
showMessage(context, l10n.p_target_copied_clipboard(title));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: TooltipIfTruncated(
|
||||||
|
text: value,
|
||||||
|
style: subtitleStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final certInfo = slotData.certInfo;
|
final certInfo = slotData.certInfo;
|
||||||
return registerPivActions(
|
return registerPivActions(
|
||||||
node.path,
|
node.path,
|
||||||
@ -52,7 +99,7 @@ class SlotDialog extends ConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 48, bottom: 32),
|
padding: const EdgeInsets.only(top: 48, bottom: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
@ -62,31 +109,29 @@ class SlotDialog extends ConsumerWidget {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
if (certInfo != null) ...[
|
if (certInfo != null) ...[
|
||||||
Text(
|
Padding(
|
||||||
l10n.l_subject_issuer(
|
padding: const EdgeInsets.all(16),
|
||||||
certInfo.subject, certInfo.issuer),
|
child: Table(
|
||||||
softWrap: true,
|
defaultColumnWidth: const IntrinsicColumnWidth(),
|
||||||
textAlign: TextAlign.center,
|
columnWidths: const {2: FlexColumnWidth()},
|
||||||
style: subtitleStyle,
|
children: [
|
||||||
),
|
detailRow(l10n.s_subject, certInfo.subject),
|
||||||
Text(
|
detailRow(l10n.s_issuer, certInfo.issuer),
|
||||||
l10n.l_serial(certInfo.serial),
|
detailRow(l10n.s_serial, certInfo.serial),
|
||||||
softWrap: true,
|
detailRow(l10n.s_certificate_fingerprint,
|
||||||
textAlign: TextAlign.center,
|
certInfo.fingerprint),
|
||||||
style: subtitleStyle,
|
detailRow(
|
||||||
),
|
l10n.s_valid_from,
|
||||||
Text(
|
DateFormat.yMMMEd().format(
|
||||||
l10n.l_certificate_fingerprint(certInfo.fingerprint),
|
DateTime.parse(certInfo.notValidBefore)),
|
||||||
softWrap: true,
|
),
|
||||||
textAlign: TextAlign.center,
|
detailRow(
|
||||||
style: subtitleStyle,
|
l10n.s_valid_to,
|
||||||
),
|
DateFormat.yMMMEd().format(
|
||||||
Text(
|
DateTime.parse(certInfo.notValidAfter)),
|
||||||
l10n.l_valid(
|
),
|
||||||
certInfo.notValidBefore, certInfo.notValidAfter),
|
],
|
||||||
softWrap: true,
|
),
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: subtitleStyle,
|
|
||||||
),
|
),
|
||||||
] else ...[
|
] else ...[
|
||||||
Padding(
|
Padding(
|
||||||
@ -98,8 +143,8 @@ class SlotDialog extends ConsumerWidget {
|
|||||||
style: subtitleStyle,
|
style: subtitleStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 16),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -33,14 +33,21 @@ class AppTheme {
|
|||||||
).copyWith(
|
).copyWith(
|
||||||
primary: primaryBlue,
|
primary: primaryBlue,
|
||||||
//secondary: accentGreen,
|
//secondary: accentGreen,
|
||||||
|
secondary: Colors.grey.shade400,
|
||||||
|
onSecondary: Colors.grey.shade800,
|
||||||
tertiary: amber.withOpacity(0.7),
|
tertiary: amber.withOpacity(0.7),
|
||||||
|
error: darkRed,
|
||||||
|
onError: Colors.white.withOpacity(0.9),
|
||||||
),
|
),
|
||||||
textTheme: TextTheme(
|
textTheme: TextTheme(
|
||||||
bodySmall: TextStyle(color: Colors.grey.shade900),
|
bodySmall: TextStyle(color: Colors.grey.shade600),
|
||||||
),
|
),
|
||||||
dialogTheme: const DialogTheme(
|
dialogTheme: const DialogTheme(
|
||||||
surfaceTintColor: Colors.white70,
|
surfaceTintColor: Colors.white70,
|
||||||
),
|
),
|
||||||
|
tooltipTheme: const TooltipThemeData(
|
||||||
|
waitDuration: Duration(seconds: 1),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
static ThemeData get darkTheme => ThemeData(
|
static ThemeData get darkTheme => ThemeData(
|
||||||
@ -52,8 +59,7 @@ class AppTheme {
|
|||||||
).copyWith(
|
).copyWith(
|
||||||
primary: primaryGreen,
|
primary: primaryGreen,
|
||||||
//onPrimary: Colors.grey.shade900,
|
//onPrimary: Colors.grey.shade900,
|
||||||
//secondary: accentGreen,
|
secondary: Colors.grey.shade400,
|
||||||
//secondary: const Color(0xff5d7d90),
|
|
||||||
//onSecondary: Colors.grey.shade900,
|
//onSecondary: Colors.grey.shade900,
|
||||||
//primaryContainer: Colors.grey.shade800,
|
//primaryContainer: Colors.grey.shade800,
|
||||||
//onPrimaryContainer: Colors.grey.shade100,
|
//onPrimaryContainer: Colors.grey.shade100,
|
||||||
@ -67,6 +73,9 @@ class AppTheme {
|
|||||||
dialogTheme: DialogTheme(
|
dialogTheme: DialogTheme(
|
||||||
surfaceTintColor: Colors.grey.shade700,
|
surfaceTintColor: Colors.grey.shade700,
|
||||||
),
|
),
|
||||||
|
tooltipTheme: const TooltipThemeData(
|
||||||
|
waitDuration: Duration(seconds: 1),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/* TODO: Remove this. It is left here as a reference as we adjust styles to work with Flutter 3.7.
|
/* TODO: Remove this. It is left here as a reference as we adjust styles to work with Flutter 3.7.
|
||||||
|
51
lib/widgets/tooltip_if_truncated.dart
Normal file
51
lib/widgets/tooltip_if_truncated.dart
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Yubico.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class TooltipIfTruncated extends StatelessWidget {
|
||||||
|
final String text;
|
||||||
|
final TextStyle style;
|
||||||
|
const TooltipIfTruncated(
|
||||||
|
{super.key, required this.text, required this.style});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final textWidget = Text(
|
||||||
|
text,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
softWrap: false,
|
||||||
|
style: style,
|
||||||
|
);
|
||||||
|
final TextPainter textPainter = TextPainter(
|
||||||
|
text: TextSpan(text: text, style: style),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
maxLines: 1,
|
||||||
|
)..layout(minWidth: 0, maxWidth: constraints.maxWidth);
|
||||||
|
return textPainter.didExceedMaxLines
|
||||||
|
? Tooltip(
|
||||||
|
margin: const EdgeInsets.all(16),
|
||||||
|
message: text,
|
||||||
|
child: textWidget,
|
||||||
|
)
|
||||||
|
: textWidget;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user