mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-22 17:51:29 +03:00
Merge PR #25.
This commit is contained in:
commit
b92426b14c
@ -246,6 +246,31 @@ class CredentialListNotifier extends StateNotifier<List<OathPair>?> {
|
||||
return credential;
|
||||
}
|
||||
|
||||
Future<void> renameAccount(
|
||||
OathCredential credential,
|
||||
String? issuer,
|
||||
String name,
|
||||
) async {
|
||||
final result = await _session.command('rename', target: [
|
||||
'accounts',
|
||||
credential.id,
|
||||
], params: {
|
||||
'issuer': issuer,
|
||||
'name': name,
|
||||
});
|
||||
String credentialId = result['credential_id'];
|
||||
if (mounted) {
|
||||
final newState = state!.toList();
|
||||
final index = newState.indexWhere((e) => e.credential == credential);
|
||||
final oldPair = newState.removeAt(index);
|
||||
newState.add(OathPair(
|
||||
credential.copyWith(id: credentialId, issuer: issuer, name: name),
|
||||
oldPair.code,
|
||||
));
|
||||
state = newState;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteAccount(OathCredential credential) async {
|
||||
await _session.command('delete', target: ['accounts', credential.id]);
|
||||
if (mounted) {
|
||||
|
@ -9,6 +9,7 @@ import '../../app/models.dart';
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'delete_account_dialog.dart';
|
||||
import 'rename_account_dialog.dart';
|
||||
|
||||
final _expireProvider =
|
||||
StateNotifierProvider.autoDispose.family<_ExpireNotifier, bool, int>(
|
||||
@ -82,7 +83,14 @@ class AccountView extends ConsumerWidget {
|
||||
title: Text('Rename account'),
|
||||
),
|
||||
onTap: () {
|
||||
log.info('TODO');
|
||||
// This ensures the onTap handler finishes before the dialog is shown, otherwise the dialog is immediately closed instead of the popup.
|
||||
Future.delayed(Duration.zero, () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
RenameAccountDialog(deviceData.node, credential),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
|
123
lib/oath/views/rename_account_dialog.dart
Executable file
123
lib/oath/views/rename_account_dialog.dart
Executable file
@ -0,0 +1,123 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import '../../app/models.dart';
|
||||
import '../../app/state.dart';
|
||||
|
||||
class RenameAccountDialog extends ConsumerStatefulWidget {
|
||||
final DeviceNode device;
|
||||
final OathCredential credential;
|
||||
const RenameAccountDialog(this.device, this.credential, {Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() =>
|
||||
_RenameAccountDialogState();
|
||||
}
|
||||
|
||||
class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
||||
late TextEditingController _issuerController;
|
||||
late TextEditingController _nameController;
|
||||
_RenameAccountDialogState();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_issuerController =
|
||||
TextEditingController(text: widget.credential.issuer ?? '');
|
||||
_nameController = TextEditingController(text: widget.credential.name);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// If current device changes, we need to pop back to the main Page.
|
||||
ref.listen<DeviceNode?>(currentDeviceProvider, (previous, next) {
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
|
||||
final credential = widget.credential;
|
||||
|
||||
final label = credential.issuer != null
|
||||
? '${credential.issuer} (${credential.name})'
|
||||
: credential.name;
|
||||
|
||||
int remaining = 64; // 64 bytes are shared between issuer and name.
|
||||
if (credential.oathType == OathType.totp && credential.period != 30) {
|
||||
// Non-standard periods are stored as part of this data, as a "D/"- prefix.
|
||||
remaining -= '${credential.period}/'.length;
|
||||
}
|
||||
if (_issuerController.text.isNotEmpty) {
|
||||
// Issuer is separated from name with a ":", if present.
|
||||
remaining -= 1;
|
||||
}
|
||||
final issuerRemaining = remaining - _nameController.text.length;
|
||||
final nameRemaining = remaining - _issuerController.text.length;
|
||||
final isValid = _nameController.text.trim().isNotEmpty;
|
||||
|
||||
return AlertDialog(
|
||||
title: Text('Rename $label?'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'This will change how the account is displayed in the list.'),
|
||||
TextField(
|
||||
controller: _issuerController,
|
||||
enabled: issuerRemaining > 0,
|
||||
maxLength: issuerRemaining > 0 ? issuerRemaining : null,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Issuer',
|
||||
helperText: '', // Prevents dialog resizing when enabled = false
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {}); // Update maxLength
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
enabled: nameRemaining > 0,
|
||||
maxLength: nameRemaining > 0 ? nameRemaining : null,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Account name *',
|
||||
helperText: '', // Prevents dialog resizing when enabled = false
|
||||
errorText: isValid ? null : 'Your account must have a name',
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {}); // Update maxLength, isValid
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: isValid
|
||||
? () async {
|
||||
final issuer = _issuerController.text.trim();
|
||||
final name = _nameController.text.trim();
|
||||
await ref
|
||||
.read(credentialListProvider(widget.device.path).notifier)
|
||||
.renameAccount(
|
||||
credential, issuer.isNotEmpty ? issuer : null, name);
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Account renamed'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: const Text('Rename account'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user