mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-27 20:25:28 +03:00
Update OATH views for new AppPage changes.
This commit is contained in:
parent
1c807dcda7
commit
750f182bab
@ -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()]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -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),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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()],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -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);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
Loading…
Reference in New Issue
Block a user