More work on integrating M3 Chips in the UI.

This commit is contained in:
Dain Nilsson 2022-09-01 14:00:33 +02:00
parent 53b1254145
commit f9067d9bdd
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
5 changed files with 64 additions and 47 deletions

View File

@ -14,6 +14,7 @@ import 'core/state.dart';
import 'desktop/state.dart';
import 'version.dart';
import 'widgets/responsive_dialog.dart';
import 'widgets/choice_filter_chip.dart';
final _log = Logger('about');
@ -141,8 +142,8 @@ class AboutPage extends ConsumerWidget {
const LoggingPanel(),
if (isDesktop) ...[
const SizedBox(height: 12.0),
OutlinedButton.icon(
icon: const Icon(Icons.bug_report_outlined),
ActionChip(
avatar: const Icon(Icons.bug_report_outlined),
label: const Text('Run diagnostics'),
onPressed: () async {
_log.info('Running diagnostics...');
@ -174,31 +175,33 @@ class LoggingPanel extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
final logLevel = ref.watch(logLevelProvider);
return Wrap(
alignment: WrapAlignment.center,
spacing: 4.0,
runSpacing: 8.0,
children: [
const SizedBox(height: 12.0),
DropdownButtonFormField<Level>(
decoration: const InputDecoration(
labelText: 'Log level',
border: OutlineInputBorder(),
ChoiceFilterChip<Level>(
avatar: Icon(
Icons.insights,
color: Theme.of(context).colorScheme.primary,
),
value: ref.watch(logLevelProvider),
items: Levels.LEVELS
.map((e) => DropdownMenuItem(
value: e,
child: Text(e.name.toUpperCase()),
))
.toList(),
value: logLevel,
items: Levels.LEVELS,
selected: logLevel != Level.INFO,
labelBuilder: (value) => Text(
'Log level: ${value.name[0]}${value.name.substring(1).toLowerCase()}'),
itemBuilder: (value) =>
Text('${value.name[0]}${value.name.substring(1).toLowerCase()}'),
onChanged: (level) {
ref.read(logLevelProvider.notifier).setLogLevel(level!);
ref.read(logLevelProvider.notifier).setLogLevel(level);
_log.debug('Log level set to $level');
showMessage(context, 'Log level set to $level');
},
),
const SizedBox(height: 12.0),
OutlinedButton.icon(
icon: const Icon(Icons.copy),
label: const Text('Copy log to clipboard'),
ActionChip(
avatar: const Icon(Icons.copy),
label: const Text('Copy log'),
onPressed: () async {
_log.info('Copying log to clipboard ($version)...');
final logs = await ref.read(logLevelProvider.notifier).getLogs();

View File

@ -159,6 +159,7 @@ class _PinEntryFormState extends ConsumerState<_PinEntryForm> {
suffixIcon: IconButton(
icon: Icon(
_isObscure ? Icons.visibility : Icons.visibility_off,
color: IconTheme.of(context).color,
),
onPressed: () {
setState(() {

View File

@ -5,12 +5,12 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:yubico_authenticator/cancellation_exception.dart';
import '../../app/logging.dart';
import '../../app/message.dart';
import '../../app/models.dart';
import '../../app/state.dart';
import '../../cancellation_exception.dart';
import '../../desktop/models.dart';
import '../../widgets/choice_filter_chip.dart';
import '../../widgets/file_drop_target.dart';
@ -57,6 +57,16 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
_scanQrCode(QrScanner qrScanner) async {
try {
setState(() {
// If we have a previous scan result stored, clear it
if (_qrState == _QrScanState.success) {
_issuerController.text = '';
_accountController.text = '';
_secretController.text = '';
_oathType = defaultOathType;
_hashAlgorithm = defaultHashAlgorithm;
_periodController.text = '$defaultPeriod';
_digits = defaultDigits;
}
_qrState = _QrScanState.scanning;
});
final otpauth = await qrScanner.scanQr();
@ -266,6 +276,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
suffixIcon: IconButton(
icon: Icon(
_isObscure ? Icons.visibility : Icons.visibility_off,
color: IconTheme.of(context).color,
),
onPressed: () {
setState(() {
@ -293,30 +304,19 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
if (qrScanner != null)
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Row(
children: [
if (_qrState != _QrScanState.scanning) ...[
OutlinedButton.icon(
style: OutlinedButton.styleFrom(
fixedSize: const Size.fromWidth(132)),
onPressed: () {
_scanQrCode(qrScanner);
},
icon: const Icon(Icons.qr_code),
label: const Text('Scan QR code'),
)
] else ...[
OutlinedButton(
style: OutlinedButton.styleFrom(
fixedSize: const Size.fromWidth(132)),
onPressed: null,
child: const SizedBox.square(
dimension: 16.0,
child: CircularProgressIndicator()),
)
]
],
),
child: ActionChip(
autofocus: true,
avatar: _qrState != _QrScanState.scanning
? (_qrState == _QrScanState.success
? const Icon(Icons.qr_code)
: const Icon(Icons.qr_code_scanner_outlined))
: const CircularProgressIndicator(strokeWidth: 2.0),
label: _qrState == _QrScanState.success
? const Text('Scanned QR code')
: const Text('Scan QR code'),
onPressed: () {
_scanQrCode(qrScanner);
}),
),
const Divider(),
Wrap(
@ -337,6 +337,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
ChoiceFilterChip<OathType>(
items: OathType.values,
value: _oathType,
selected: _oathType != defaultOathType,
itemBuilder: (value) => Text(value.displayName),
onChanged: _qrState != _QrScanState.success
? (value) {
@ -349,6 +350,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
ChoiceFilterChip<HashAlgorithm>(
items: HashAlgorithm.values,
value: _hashAlgorithm,
selected: _hashAlgorithm != defaultHashAlgorithm,
itemBuilder: (value) => Text(value.displayName),
onChanged: _qrState != _QrScanState.success
? (value) {
@ -363,6 +365,8 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
items: _periodValues,
value:
int.tryParse(_periodController.text) ?? defaultPeriod,
selected:
int.tryParse(_periodController.text) != defaultPeriod,
itemBuilder: ((value) => Text('$value sec')),
onChanged: _qrState != _QrScanState.success
? (period) {
@ -375,6 +379,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
ChoiceFilterChip<int>(
items: _digitsValues,
value: _digits,
selected: _digits != defaultDigits,
itemBuilder: (value) => Text('$value digits'),
onChanged: _qrState != _QrScanState.success
? (digits) {

View File

@ -288,6 +288,7 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> {
suffixIcon: IconButton(
icon: Icon(
_isObscure ? Icons.visibility : Icons.visibility_off,
color: IconTheme.of(context).color,
),
onPressed: () {
setState(() {

View File

@ -6,13 +6,19 @@ class ChoiceFilterChip<T> extends StatefulWidget {
final T value;
final List<T> items;
final Widget Function(T value) itemBuilder;
final Widget Function(T value)? labelBuilder;
final void Function(T value)? onChanged;
final Widget? avatar;
final bool selected;
const ChoiceFilterChip({
super.key,
required this.value,
required this.items,
required this.itemBuilder,
required this.onChanged,
this.avatar,
this.selected = false,
this.labelBuilder,
});
@override
@ -25,11 +31,12 @@ class _ChoiceFilterChipState<T> extends State<ChoiceFilterChip<T>> {
@override
Widget build(BuildContext context) {
return FilterChip(
avatar: widget.avatar,
labelPadding: const EdgeInsets.only(left: 4),
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
widget.itemBuilder(widget.value),
(widget.labelBuilder ?? widget.itemBuilder).call(widget.value),
Padding(
padding: const EdgeInsets.only(left: 6),
child: Icon(
@ -39,7 +46,7 @@ class _ChoiceFilterChipState<T> extends State<ChoiceFilterChip<T>> {
),
],
),
selected: true,
selected: widget.selected,
showCheckmark: false,
onSelected: widget.onChanged != null
? (_) async {