Merge branch 'main' into bump/andorid-deps-20230821

This commit is contained in:
Adam Velebil 2023-08-21 16:28:53 +02:00
commit acb97f941a
No known key found for this signature in database
GPG Key ID: C9B1E4A3CBBD2E10
14 changed files with 246 additions and 113 deletions

View File

@ -32,10 +32,12 @@ Future<T?> showBlurDialog<T>({
required BuildContext context,
required Widget Function(BuildContext) builder,
RouteSettings? routeSettings,
Color barrierColor = const Color(0x80000000),
}) async =>
await showGeneralDialog<T>(
context: context,
barrierDismissible: true,
barrierColor: barrierColor,
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
pageBuilder: (ctx, anim1, anim2) => builder(ctx),
transitionDuration: const Duration(milliseconds: 150),

View File

@ -224,7 +224,11 @@ class AppPage extends StatelessWidget {
child: IconButton(
key: actionsIconButtonKey,
onPressed: () {
showBlurDialog(context: context, builder: keyActionsBuilder!);
showBlurDialog(
context: context,
barrierColor: Colors.transparent,
builder: keyActionsBuilder!,
);
},
icon: keyActionsBadge
? const Badge(

View File

@ -26,7 +26,8 @@ class FsDialog extends StatelessWidget {
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Dialog.fullscreen(
backgroundColor: Theme.of(context).colorScheme.background.withAlpha(100),
backgroundColor:
Theme.of(context).colorScheme.background.withOpacity(0.7),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,

View File

@ -59,6 +59,7 @@ class FidoUnlockedPage extends ConsumerWidget {
OpenIntent: CallbackAction<OpenIntent>(
onInvoke: (_) => showBlurDialog(
context: context,
barrierColor: Colors.transparent,
builder: (context) => CredentialDialog(cred),
)),
DeleteIntent: CallbackAction<DeleteIntent>(
@ -91,6 +92,7 @@ class FidoUnlockedPage extends ConsumerWidget {
OpenIntent: CallbackAction<OpenIntent>(
onInvoke: (_) => showBlurDialog(
context: context,
barrierColor: Colors.transparent,
builder: (context) => FingerprintDialog(fp),
)),
EditIntent: CallbackAction<EditIntent>(

View File

@ -48,6 +48,12 @@
"item": {}
}
},
"s_definition": "{item}:",
"@s_definition" : {
"placeholders": {
"item": {}
}
},
"s_about": "About",
"s_appearance": "Appearance",
@ -212,6 +218,12 @@
"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",
"l_fido_pin_protection_optional": "Optional FIDO PIN protection",
"l_enter_fido2_pin": "Enter the FIDO2 PIN for your YubiKey",
@ -231,6 +243,7 @@
"s_pin_required": "PIN required",
"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_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" : {
"placeholders": {
@ -418,32 +431,11 @@
"l_import_desc": "Import a key and/or certificate",
"l_delete_certificate": "Delete certificate",
"l_delete_certificate_desc": "Remove the certificate from your YubiKey",
"l_subject_issuer": "Subject: {subject}, Issuer: {issuer}",
"@l_subject_issuer" : {
"placeholders": {
"subject": {},
"issuer": {}
}
},
"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": {}
}
},
"s_issuer": "Issuer",
"s_serial": "Serial",
"s_certificate_fingerprint": "Fingerprint",
"s_valid_from": "Valid from",
"s_valid_to": "Valid to",
"l_no_certificate": "No certificate loaded",
"l_key_no_certificate": "Key without certificate loaded",
"s_generate_key": "Generate key",

View File

@ -121,40 +121,51 @@ class AccountDialog extends ConsumerWidget {
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 48, bottom: 16),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 32),
child: Column(
children: [
IconTheme(
data: IconTheme.of(context).copyWith(size: 24),
child: helper.buildCodeIcon(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
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),
DefaultTextStyle.merge(
style: const TextStyle(fontSize: 28),
child: helper.buildCodeLabel(),
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,
),
),
],
),
),
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(
context,
AppLocalizations.of(context)!.s_actions,

View File

@ -89,6 +89,7 @@ class _AccountViewState extends ConsumerState<AccountView> {
OpenIntent: CallbackAction<OpenIntent>(onInvoke: (_) async {
await showBlurDialog(
context: context,
barrierColor: Colors.transparent,
builder: (context) => AccountDialog(credential),
);
return null;

View File

@ -24,7 +24,7 @@ part 'models.g.dart';
const defaultManagementKey = '010203040506070801020304050607080102030405060708';
const defaultManagementKeyType = ManagementKeyType.tdes;
const defaultKeyType = KeyType.rsa2048;
const defaultKeyType = KeyType.eccp256;
const defaultGenerateType = GenerateType.certificate;
enum GenerateType {

View File

@ -39,6 +39,7 @@ Widget pivBuildActions(BuildContext context, DevicePath devicePath,
final pinBlocked = pivState.pinAttempts == 0;
final pukAttempts = pivState.metadata?.pukMetadata.attemptsRemaining;
final alertIcon = Icon(Icons.warning_amber, color: colors.tertiary);
return FsDialog(
child: Column(
@ -50,35 +51,46 @@ Widget pivBuildActions(BuildContext context, DevicePath devicePath,
key: keys.managePinAction,
title: l10n.s_pin,
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),
icon: const Icon(Icons.pin_outlined),
onTap: (context) {
Navigator.of(context).pop();
showBlurDialog(
context: context,
builder: (context) => ManagePinPukDialog(
devicePath,
target:
pinBlocked ? ManageTarget.unblock : ManageTarget.pin,
),
);
}),
trailing: pinBlocked ? alertIcon : null,
onTap: !(pinBlocked && pukAttempts == 0)
? (context) {
Navigator.of(context).pop();
showBlurDialog(
context: context,
builder: (context) => ManagePinPukDialog(
devicePath,
target: pinBlocked
? ManageTarget.unblock
: ManageTarget.pin,
),
);
}
: null),
ActionListItem(
key: keys.managePukAction,
title: l10n.s_puk,
subtitle: pukAttempts != null
? l10n.l_attempts_remaining(pukAttempts)
? (pukAttempts == 0
? l10n.l_piv_pin_puk_blocked
: l10n.l_attempts_remaining(pukAttempts))
: null,
icon: const Icon(Icons.pin_outlined),
onTap: (context) {
Navigator.of(context).pop();
showBlurDialog(
context: context,
builder: (context) => ManagePinPukDialog(devicePath,
target: ManageTarget.puk),
);
}),
trailing: pukAttempts == 0 ? alertIcon : null,
onTap: pukAttempts != 0
? (context) {
Navigator.of(context).pop();
showBlurDialog(
context: context,
builder: (context) => ManagePinPukDialog(devicePath,
target: ManageTarget.puk),
);
}
: null),
ActionListItem(
key: keys.manageManagementKeyAction,
title: l10n.s_management_key,
@ -88,9 +100,7 @@ Widget pivBuildActions(BuildContext context, DevicePath devicePath,
? l10n.l_pin_protected_key
: l10n.l_change_management_key),
icon: const Icon(Icons.key_outlined),
trailing: usingDefaultMgmtKey
? Icon(Icons.warning_amber, color: colors.tertiary)
: null,
trailing: usingDefaultMgmtKey ? alertIcon : null,
onTap: (context) {
Navigator.of(context).pop();
showBlurDialog(

View File

@ -114,7 +114,11 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
: l10n.s_current_puk,
prefixIcon: const Icon(Icons.password_outlined),
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,
errorMaxLines: 3),
textInputAction: TextInputAction.next,

View File

@ -70,6 +70,7 @@ class PivScreen extends ConsumerWidget {
CallbackAction<OpenIntent>(onInvoke: (_) async {
await showBlurDialog(
context: context,
barrierColor: Colors.transparent,
builder: (context) => SlotDialog(e.slot),
);
return null;
@ -104,7 +105,7 @@ class _CertificateListItem extends StatelessWidget {
),
title: slot.getDisplayName(l10n),
subtitle: certInfo != null
? l10n.l_subject_issuer(certInfo.subject, certInfo.issuer)
? certInfo.subject
: pivSlot.hasKey == true
? l10n.l_key_no_certificate
: l10n.l_no_certificate,

View File

@ -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_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import '../../app/message.dart';
import '../../app/state.dart';
import '../../app/views/fs_dialog.dart';
import '../../app/views/action_list.dart';
import '../../widgets/tooltip_if_truncated.dart';
import '../models.dart';
import '../state.dart';
import 'actions.dart';
@ -29,6 +48,8 @@ class SlotDialog extends ConsumerWidget {
final subtitleStyle = textTheme.bodyMedium!.copyWith(
color: textTheme.bodySmall!.color,
);
final clipboard = ref.watch(clipboardProvider);
final withContext = ref.read(withContextProvider);
final pivState = ref.watch(pivStateProvider(node.path)).valueOrNull;
final slotData = ref.watch(pivSlotsProvider(node.path).select((value) =>
@ -40,6 +61,32 @@ class SlotDialog extends ConsumerWidget {
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;
return registerPivActions(
node.path,
@ -52,7 +99,7 @@ class SlotDialog extends ConsumerWidget {
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 48, bottom: 32),
padding: const EdgeInsets.only(top: 48, bottom: 16),
child: Column(
children: [
Text(
@ -62,31 +109,29 @@ class SlotDialog extends ConsumerWidget {
textAlign: TextAlign.center,
),
if (certInfo != null) ...[
Text(
l10n.l_subject_issuer(
certInfo.subject, certInfo.issuer),
softWrap: true,
textAlign: TextAlign.center,
style: subtitleStyle,
),
Text(
l10n.l_serial(certInfo.serial),
softWrap: true,
textAlign: TextAlign.center,
style: subtitleStyle,
),
Text(
l10n.l_certificate_fingerprint(certInfo.fingerprint),
softWrap: true,
textAlign: TextAlign.center,
style: subtitleStyle,
),
Text(
l10n.l_valid(
certInfo.notValidBefore, certInfo.notValidAfter),
softWrap: true,
textAlign: TextAlign.center,
style: subtitleStyle,
Padding(
padding: const EdgeInsets.all(16),
child: Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
columnWidths: const {2: FlexColumnWidth()},
children: [
detailRow(l10n.s_subject, certInfo.subject),
detailRow(l10n.s_issuer, certInfo.issuer),
detailRow(l10n.s_serial, certInfo.serial),
detailRow(l10n.s_certificate_fingerprint,
certInfo.fingerprint),
detailRow(
l10n.s_valid_from,
DateFormat.yMMMEd().format(
DateTime.parse(certInfo.notValidBefore)),
),
detailRow(
l10n.s_valid_to,
DateFormat.yMMMEd().format(
DateTime.parse(certInfo.notValidAfter)),
),
],
),
),
] else ...[
Padding(
@ -98,8 +143,8 @@ class SlotDialog extends ConsumerWidget {
style: subtitleStyle,
),
),
const SizedBox(height: 16),
],
const SizedBox(height: 16),
],
),
),

View File

@ -33,14 +33,21 @@ class AppTheme {
).copyWith(
primary: primaryBlue,
//secondary: accentGreen,
secondary: Colors.grey.shade400,
onSecondary: Colors.grey.shade800,
tertiary: amber.withOpacity(0.7),
error: darkRed,
onError: Colors.white.withOpacity(0.9),
),
textTheme: TextTheme(
bodySmall: TextStyle(color: Colors.grey.shade900),
bodySmall: TextStyle(color: Colors.grey.shade600),
),
dialogTheme: const DialogTheme(
surfaceTintColor: Colors.white70,
),
tooltipTheme: const TooltipThemeData(
waitDuration: Duration(seconds: 1),
),
);
static ThemeData get darkTheme => ThemeData(
@ -52,8 +59,7 @@ class AppTheme {
).copyWith(
primary: primaryGreen,
//onPrimary: Colors.grey.shade900,
//secondary: accentGreen,
//secondary: const Color(0xff5d7d90),
secondary: Colors.grey.shade400,
//onSecondary: Colors.grey.shade900,
//primaryContainer: Colors.grey.shade800,
//onPrimaryContainer: Colors.grey.shade100,
@ -67,6 +73,9 @@ class AppTheme {
dialogTheme: DialogTheme(
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.

View 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;
},
);
}
}