Update OATH views for new AppPage changes.

This commit is contained in:
Dain Nilsson 2022-04-03 11:06:22 +02:00
parent 1c807dcda7
commit 750f182bab
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
4 changed files with 134 additions and 140 deletions

View File

@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -90,8 +92,8 @@ class AccountDialog extends ConsumerWidget with AccountMixin {
label: Text( label: Text(
formatCode(code), formatCode(code),
style: const TextStyle( style: const TextStyle(
fontSize: 32.0, fontSize: 32.0,
), fontFeatures: [FontFeature.tabularFigures()]),
), ),
), ),
) )

View File

@ -92,7 +92,7 @@ class _AccountListState extends ConsumerState<AccountList> {
pinnedCreds.followedBy(creds).map((e) => e.credential).toList(); pinnedCreds.followedBy(creds).map((e) => e.credential).toList();
_updateFocusNodes(); _updateFocusNodes();
return ListView( return Column(
children: [ children: [
if (pinnedCreds.isNotEmpty) if (pinnedCreds.isNotEmpty)
const ListTile( const ListTile(
@ -118,8 +118,6 @@ class _AccountListState extends ConsumerState<AccountList> {
focusNode: _focusNodes[entry.credential], focusNode: _focusNodes[entry.credential],
), ),
), ),
// Make sure FAB doesn't block content
const SizedBox(height: 72.0),
], ],
); );
} }

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -144,6 +145,7 @@ class AccountView extends ConsumerWidget with AccountMixin {
formatCode(code), formatCode(code),
style: const TextStyle( style: const TextStyle(
fontSize: 22.0, fontSize: 22.0,
fontFeatures: [FontFeature.tabularFigures()],
), ),
), ),
), ),

View File

@ -15,17 +15,25 @@ import 'manage_password_dialog.dart';
import 'reset_dialog.dart'; import 'reset_dialog.dart';
class OathScreen extends ConsumerWidget { class OathScreen extends ConsumerWidget {
final YubiKeyData deviceData; final DevicePath devicePath;
const OathScreen(this.deviceData, {Key? key}) : super(key: key); const OathScreen(this.devicePath, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(oathStateProvider(deviceData.node.path)).when( return ref.watch(oathStateProvider(devicePath)).when(
loading: () => AppPage(child: const AppLoadingScreen()), loading: () => AppPage(
error: (error, _) => AppPage(child: AppFailureScreen('$error')), title: const Text('Authenticator'),
centered: true,
child: const AppLoadingScreen(),
),
error: (error, _) => AppPage(
title: const Text('Authenticator'),
centered: true,
child: AppFailureScreen('$error'),
),
data: (oathState) => oathState.locked data: (oathState) => oathState.locked
? _LockedView(deviceData.node.path, oathState) ? _LockedView(devicePath, oathState)
: _UnlockedView(deviceData.node.path, oathState), : _UnlockedView(devicePath, oathState),
); );
} }
} }
@ -39,59 +47,48 @@ class _LockedView extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) => AppPage( Widget build(BuildContext context, WidgetRef ref) => AppPage(
child: ListView( title: const Text('Authenticator'),
children: [ child: Column(
const ListTile( children: [
title: Text( const ListTile(title: Text('Unlock')),
'Unlock', _UnlockForm(
devicePath,
keystore: oathState.keystore,
), ),
), ],
_UnlockForm( ),
keystore: oathState.keystore, floatingActionButton: FloatingActionButton.extended(
onSubmit: (password, remember) async { icon: const Icon(Icons.password),
final result = await ref label: const Text('Setup'),
.read(oathStateProvider(devicePath).notifier) backgroundColor: Theme.of(context).colorScheme.secondary,
.unlock(password, remember: remember); foregroundColor: Theme.of(context).colorScheme.onSecondary,
if (!result.first) { onPressed: () {
showMessage(context, 'Wrong password'); showBottomMenu(context, [
} else if (remember && !result.second) { MenuAction(
showMessage(context, 'Failed to remember password'); text: 'Change password',
} icon: const Icon(Icons.password),
}, action: (context) {
), showDialog(
Padding( context: context,
padding: builder: (context) =>
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), ManagePasswordDialog(devicePath, oathState),
child: Wrap( );
spacing: 4.0, },
runSpacing: 4.0, ),
children: [ MenuAction(
OutlinedButton.icon( text: 'Delete all data',
icon: const Icon(Icons.password), icon: const Icon(Icons.delete_outline),
label: Text( action: (context) {
oathState.hasKey ? 'Change password' : 'Set password'), showDialog(
onPressed: () { context: context,
showDialog( builder: (context) => ResetDialog(devicePath),
context: context, );
builder: (context) => },
ManagePasswordDialog(devicePath, oathState), ),
); ]);
}), },
OutlinedButton.icon( ),
icon: const Icon(Icons.delete_forever), );
label: const Text('Reset'),
onPressed: () {
showDialog(
context: context,
builder: (context) => ResetDialog(devicePath),
);
},
),
],
),
),
],
));
} }
class _UnlockedView extends ConsumerWidget { class _UnlockedView extends ConsumerWidget {
@ -131,110 +128,107 @@ class _UnlockedView extends ConsumerWidget {
), ),
child: AccountList(devicePath, oathState), child: AccountList(devicePath, oathState),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
icon: const Icon(Icons.add), icon: const Icon(Icons.person_add_alt),
label: const Text('Setup'), label: const Text('Setup'),
backgroundColor: Theme.of(context).colorScheme.secondary, backgroundColor: Theme.of(context).colorScheme.secondary,
foregroundColor: Theme.of(context).colorScheme.onSecondary, foregroundColor: Theme.of(context).colorScheme.onSecondary,
onPressed: () { onPressed: () {
showModalBottomSheet( showBottomMenu(context, [
context: context, MenuAction(
constraints: MediaQuery.of(context).size.width > 540 text: 'Add account',
? const BoxConstraints(maxWidth: 380) icon: const Icon(Icons.person_add_alt),
: null, action: (context) {
builder: (context) => Column( showDialog(
mainAxisSize: MainAxisSize.min, context: context,
children: [ builder: (context) => OathAddAccountPage(devicePath),
ListTile( );
leading: const Icon(Icons.add), },
title: const Text('Add account'),
onTap: () {
Navigator.pop(context);
showDialog(
context: context,
builder: (context) => OathAddAccountPage(devicePath),
);
},
),
ListTile(
leading: const Icon(Icons.password),
title: Text(
oathState.hasKey ? 'Change password' : 'Set password'),
onTap: () {
Navigator.pop(context);
showDialog(
context: context,
builder: (context) =>
ManagePasswordDialog(devicePath, oathState),
);
},
),
ListTile(
leading: const Icon(Icons.delete_forever),
title: const Text('Delete all data'),
onTap: () {
Navigator.pop(context);
showDialog(
context: context,
builder: (context) => ResetDialog(devicePath),
);
},
),
],
), ),
); MenuAction(
text: oathState.hasKey ? 'Change password' : 'Set password',
icon: const Icon(Icons.password),
action: (context) {
showDialog(
context: context,
builder: (context) =>
ManagePasswordDialog(devicePath, oathState),
);
},
),
MenuAction(
text: 'Delete all data',
icon: const Icon(Icons.delete_outline),
action: (context) {
showDialog(
context: context,
builder: (context) => ResetDialog(devicePath),
);
},
),
]);
}, },
), ),
); );
} }
class _UnlockForm extends StatefulWidget { class _UnlockForm extends ConsumerStatefulWidget {
final DevicePath _devicePath;
final KeystoreState keystore; final KeystoreState keystore;
final Function(String, bool) onSubmit; const _UnlockForm(this._devicePath, {Key? key, required this.keystore})
const _UnlockForm({Key? key, required this.keystore, required this.onSubmit})
: super(key: key); : super(key: key);
@override @override
State<StatefulWidget> createState() => _UnlockFormState(); ConsumerState<_UnlockForm> createState() => _UnlockFormState();
} }
class _UnlockFormState extends State<_UnlockForm> { class _UnlockFormState extends ConsumerState<_UnlockForm> {
// TODO: Use a TextEditingController so we can clear it on wrong entry final _passwordController = TextEditingController();
String _password = '';
bool _remember = false; bool _remember = false;
bool _wrong = false;
void _submit() async {
setState(() {
_wrong = false;
});
final result = await ref
.read(oathStateProvider(widget._devicePath).notifier)
.unlock(_passwordController.text, remember: _remember);
if (!result.first) {
setState(() {
_wrong = true;
_passwordController.clear();
});
showMessage(context, 'Wrong password');
} else if (_remember && !result.second) {
showMessage(context, 'Failed to remember password');
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final keystoreFailed = widget.keystore == KeystoreState.failed; final keystoreFailed = widget.keystore == KeystoreState.failed;
return Column( return Column(
//mainAxisAlignment: MainAxisAlignment.center,
//crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0), padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( const Text(
'Enter the password for your YubiKey. If you don\'t know your password, you\'ll need to reset the YubiKey.', 'Enter the password for your YubiKey. If you don\'t know your password, you\'ll need to reset the YubiKey.',
), ),
Padding( const SizedBox(height: 16.0),
padding: const EdgeInsets.symmetric(vertical: 16.0), TextField(
child: TextField( controller: _passwordController,
autofocus: true, autofocus: true,
obscureText: true, obscureText: true,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: 'Password', border: const OutlineInputBorder(),
border: OutlineInputBorder(), labelText: 'Password',
), errorText: _wrong ? 'Wrong password' : null,
onChanged: (value) { helperText: '', // Prevents resizing when errorText shown
setState(() {
_password = value;
});
},
onSubmitted: (value) {
widget.onSubmit(value, _remember);
},
), ),
onChanged: (_) => setState(() {}), // Update state on change
onSubmitted: (_) => _submit(),
), ),
], ],
), ),
@ -259,9 +253,7 @@ class _UnlockFormState extends State<_UnlockForm> {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: ElevatedButton( child: ElevatedButton(
child: const Text('Unlock'), child: const Text('Unlock'),
onPressed: () { onPressed: _passwordController.text.isNotEmpty ? _submit : null,
widget.onSubmit(_password, _remember);
},
), ),
), ),
], ],