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 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),

View File

@ -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(

View File

@ -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,

View File

@ -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>(

View File

@ -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",

View File

@ -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,

View File

@ -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;

View File

@ -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 {

View File

@ -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(

View File

@ -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,

View File

@ -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,

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/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),
], ],
), ),
), ),

View File

@ -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.

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