mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-26 10:33:15 +03:00
Merge PR #204
This commit is contained in:
commit
3290a5c65e
@ -29,7 +29,7 @@
|
|||||||
"oath_digits": "digits",
|
"oath_digits": "digits",
|
||||||
"oath_success_delete_account": "Account deleted",
|
"oath_success_delete_account": "Account deleted",
|
||||||
"oath_delete": "Delete",
|
"oath_delete": "Delete",
|
||||||
"oath_warning_this_will_delete_from_key": "Warning! This action will delete the account from your YubiKey.",
|
"oath_warning_this_will_delete_account_from_key": "Warning! This action will delete the account from your YubiKey.",
|
||||||
"oath_warning_disable_this_cred": "You will no longer be able to generate OTPs for this account. Make sure to first disable this credential from the website to avoid being locked out of your account.",
|
"oath_warning_disable_this_cred": "You will no longer be able to generate OTPs for this account. Make sure to first disable this credential from the website to avoid being locked out of your account.",
|
||||||
"oath_account": "Account",
|
"oath_account": "Account",
|
||||||
"oath_password_set": "Password set",
|
"oath_password_set": "Password set",
|
||||||
@ -44,6 +44,35 @@
|
|||||||
"oath_enter_new_password": "Enter your new password. A password may contain letters, numbers and special characters.",
|
"oath_enter_new_password": "Enter your new password. A password may contain letters, numbers and special characters.",
|
||||||
"oath_new_password": "New password",
|
"oath_new_password": "New password",
|
||||||
"oath_confirm_password": "Confirm password",
|
"oath_confirm_password": "Confirm password",
|
||||||
|
"oath_authenticator": "Authenticator",
|
||||||
|
"oath_reset_oath": "Reset OATH",
|
||||||
|
"oath_no_accounts": "No accounts",
|
||||||
|
"oath_search_accounts": "Search accounts",
|
||||||
|
"oath_set_password": "Set password",
|
||||||
|
"oath_failed_remember_pw": "Failed to remember password",
|
||||||
|
"oath_enter_oath_pw": "Enter the OATH password for your YubiKey",
|
||||||
|
"oath_password": "Password",
|
||||||
|
"oath_keystore_unavailable": "OS Keystore unavailable",
|
||||||
|
"oath_remember_password": "Remember password",
|
||||||
|
"oath_unlock": "Unlock",
|
||||||
|
"oath_warning_will_change_account_displayed": "This will change how the account is displayed in the list.",
|
||||||
|
"oath_account_must_have_name": "Your account must have a name",
|
||||||
|
"oath_name_exists": "This name already exists for the Issuer",
|
||||||
|
"oath_account_renamed": "Account renamed",
|
||||||
|
"oath_rename": "Rename {label}?",
|
||||||
|
"@oath_rename" : {
|
||||||
|
"placeholders": {
|
||||||
|
"label": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oath_factory_reset": "Factory reset",
|
||||||
|
"oath_oath_application_reset": "OATH application reset",
|
||||||
|
"oath_reset": "Reset",
|
||||||
|
"oath_warning_will_delete_accounts": "Warning! This will irrevocably delete all OATH TOTP/HOTP accounts from your YubiKey.",
|
||||||
|
"oath_warning_disable_these_creds": "Your OATH credentials, as well as any password set, will be removed from this YubiKey. Make sure to first disable these from their respective web sites to avoid being locked out of your accounts.",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ class DeleteAccountDialog extends ConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(AppLocalizations.of(context)!
|
Text(AppLocalizations.of(context)!
|
||||||
.oath_warning_this_will_delete_from_key),
|
.oath_warning_this_will_delete_account_from_key),
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.oath_warning_disable_this_cred,
|
AppLocalizations.of(context)!.oath_warning_disable_this_cred,
|
||||||
style: Theme.of(context).textTheme.bodyText1,
|
style: Theme.of(context).textTheme.bodyText1,
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.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 '../../app/message.dart';
|
import '../../app/message.dart';
|
||||||
@ -28,12 +29,12 @@ class OathScreen extends ConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return ref.watch(oathStateProvider(devicePath)).when(
|
return ref.watch(oathStateProvider(devicePath)).when(
|
||||||
loading: () => AppPage(
|
loading: () => AppPage(
|
||||||
title: const Text('Authenticator'),
|
title: Text(AppLocalizations.of(context)!.oath_authenticator),
|
||||||
centered: true,
|
centered: true,
|
||||||
child: const AppLoadingScreen(),
|
child: const AppLoadingScreen(),
|
||||||
),
|
),
|
||||||
error: (error, _) => AppFailurePage(
|
error: (error, _) => AppFailurePage(
|
||||||
title: const Text('Authenticator'),
|
title: Text(AppLocalizations.of(context)!.oath_authenticator),
|
||||||
cause: error,
|
cause: error,
|
||||||
),
|
),
|
||||||
data: (oathState) => oathState.locked
|
data: (oathState) => oathState.locked
|
||||||
@ -51,10 +52,10 @@ class _LockedView extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) => AppPage(
|
Widget build(BuildContext context, WidgetRef ref) => AppPage(
|
||||||
title: const Text('Authenticator'),
|
title: Text(AppLocalizations.of(context)!.oath_authenticator),
|
||||||
keyActions: [
|
keyActions: [
|
||||||
buildMenuItem(
|
buildMenuItem(
|
||||||
title: const Text('Manage password'),
|
title: Text(AppLocalizations.of(context)!.oath_manage_password),
|
||||||
leading: const Icon(Icons.password),
|
leading: const Icon(Icons.password),
|
||||||
action: () {
|
action: () {
|
||||||
showBlurDialog(
|
showBlurDialog(
|
||||||
@ -65,7 +66,7 @@ class _LockedView extends ConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
buildMenuItem(
|
buildMenuItem(
|
||||||
title: const Text('Reset OATH'),
|
title: Text(AppLocalizations.of(context)!.oath_reset_oath),
|
||||||
leading: const Icon(Icons.delete),
|
leading: const Icon(Icons.delete),
|
||||||
action: () {
|
action: () {
|
||||||
showBlurDialog(
|
showBlurDialog(
|
||||||
@ -119,9 +120,9 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
|||||||
final credentials = ref.watch(credentialsProvider);
|
final credentials = ref.watch(credentialsProvider);
|
||||||
if (credentials?.isEmpty == true) {
|
if (credentials?.isEmpty == true) {
|
||||||
return MessagePage(
|
return MessagePage(
|
||||||
title: const Text('Authenticator'),
|
title: Text(AppLocalizations.of(context)!.oath_authenticator),
|
||||||
graphic: noAccounts,
|
graphic: noAccounts,
|
||||||
header: 'No accounts',
|
header: AppLocalizations.of(context)!.oath_no_accounts,
|
||||||
keyActions: _buildActions(
|
keyActions: _buildActions(
|
||||||
context,
|
context,
|
||||||
credentials: null,
|
credentials: null,
|
||||||
@ -153,11 +154,11 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
|||||||
controller: searchController,
|
controller: searchController,
|
||||||
focusNode: searchFocus,
|
focusNode: searchFocus,
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Search accounts',
|
hintText: AppLocalizations.of(context)!.oath_search_accounts,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
prefixIcon: Icon(Icons.search_outlined),
|
prefixIcon: const Icon(Icons.search_outlined),
|
||||||
prefixIconConstraints: BoxConstraints(
|
prefixIconConstraints: const BoxConstraints(
|
||||||
minHeight: 30,
|
minHeight: 30,
|
||||||
minWidth: 30,
|
minWidth: 30,
|
||||||
),
|
),
|
||||||
@ -190,7 +191,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
|||||||
final capacity = widget.oathState.version.isAtLeast(4) ? 32 : null;
|
final capacity = widget.oathState.version.isAtLeast(4) ? 32 : null;
|
||||||
return [
|
return [
|
||||||
buildMenuItem(
|
buildMenuItem(
|
||||||
title: const Text('Add account'),
|
title: Text(AppLocalizations.of(context)!.oath_add_account),
|
||||||
leading: const Icon(Icons.person_add_alt_1),
|
leading: const Icon(Icons.person_add_alt_1),
|
||||||
trailing: capacity != null ? '$used/$capacity' : null,
|
trailing: capacity != null ? '$used/$capacity' : null,
|
||||||
action: capacity == null || capacity > used
|
action: capacity == null || capacity > used
|
||||||
@ -208,8 +209,9 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
buildMenuItem(
|
buildMenuItem(
|
||||||
title: Text(
|
title: Text(widget.oathState.hasKey
|
||||||
widget.oathState.hasKey ? 'Manage password' : 'Set password'),
|
? AppLocalizations.of(context)!.oath_manage_password
|
||||||
|
: AppLocalizations.of(context)!.oath_set_password),
|
||||||
leading: const Icon(Icons.password),
|
leading: const Icon(Icons.password),
|
||||||
action: () {
|
action: () {
|
||||||
showBlurDialog(
|
showBlurDialog(
|
||||||
@ -219,7 +221,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
buildMenuItem(
|
buildMenuItem(
|
||||||
title: const Text('Reset OATH'),
|
title: Text(AppLocalizations.of(context)!.oath_reset_oath),
|
||||||
leading: const Icon(Icons.delete),
|
leading: const Icon(Icons.delete),
|
||||||
action: () {
|
action: () {
|
||||||
showBlurDialog(
|
showBlurDialog(
|
||||||
@ -260,7 +262,8 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> {
|
|||||||
_passwordController.clear();
|
_passwordController.clear();
|
||||||
});
|
});
|
||||||
} else if (_remember && !result.second) {
|
} else if (_remember && !result.second) {
|
||||||
showMessage(context, 'Failed to remember password');
|
showMessage(
|
||||||
|
context, AppLocalizations.of(context)!.oath_failed_remember_pw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,8 +277,8 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
Text(
|
||||||
'Enter the OATH password for your YubiKey',
|
AppLocalizations.of(context)!.oath_enter_oath_pw,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 16.0),
|
||||||
TextField(
|
TextField(
|
||||||
@ -284,8 +287,10 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> {
|
|||||||
obscureText: _isObscure,
|
obscureText: _isObscure,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
labelText: 'Password',
|
labelText: AppLocalizations.of(context)!.oath_password,
|
||||||
errorText: _passwordIsWrong ? 'Wrong password' : null,
|
errorText: _passwordIsWrong
|
||||||
|
? AppLocalizations.of(context)!.oath_wrong_password
|
||||||
|
: null,
|
||||||
helperText: '', // Prevents resizing when errorText shown
|
helperText: '', // Prevents resizing when errorText shown
|
||||||
prefixIcon: const Icon(Icons.password_outlined),
|
prefixIcon: const Icon(Icons.password_outlined),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
@ -310,14 +315,16 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8.0),
|
const SizedBox(height: 8.0),
|
||||||
keystoreFailed
|
keystoreFailed
|
||||||
? const ListTile(
|
? ListTile(
|
||||||
leading: Icon(Icons.warning_amber_rounded),
|
leading: const Icon(Icons.warning_amber_rounded),
|
||||||
title: Text('OS Keystore unavailable'),
|
title: Text(
|
||||||
|
AppLocalizations.of(context)!.oath_keystore_unavailable),
|
||||||
dense: true,
|
dense: true,
|
||||||
minLeadingWidth: 0,
|
minLeadingWidth: 0,
|
||||||
)
|
)
|
||||||
: CheckboxListTile(
|
: CheckboxListTile(
|
||||||
title: const Text('Remember password'),
|
title:
|
||||||
|
Text(AppLocalizations.of(context)!.oath_remember_password),
|
||||||
dense: true,
|
dense: true,
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: _remember,
|
value: _remember,
|
||||||
@ -332,7 +339,7 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> {
|
|||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
label: const Text('Unlock'),
|
label: Text(AppLocalizations.of(context)!.oath_unlock),
|
||||||
icon: const Icon(Icons.lock_open),
|
icon: const Icon(Icons.lock_open),
|
||||||
onPressed: _passwordController.text.isNotEmpty ? _submit : null,
|
onPressed: _passwordController.text.isNotEmpty ? _submit : null,
|
||||||
),
|
),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
|||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.of(context).pop(renamed);
|
Navigator.of(context).pop(renamed);
|
||||||
showMessage(context, 'Account renamed');
|
showMessage(context, AppLocalizations.of(context)!.oath_account_renamed);
|
||||||
} on CancellationException catch (_) {
|
} on CancellationException catch (_) {
|
||||||
// ignored
|
// ignored
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -68,7 +69,7 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
|||||||
}
|
}
|
||||||
showMessage(
|
showMessage(
|
||||||
context,
|
context,
|
||||||
'Failed adding account: $errorMessage',
|
'${AppLocalizations.of(context)!.oath_fail_add_account}: $errorMessage',
|
||||||
duration: const Duration(seconds: 4),
|
duration: const Duration(seconds: 4),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -111,30 +112,30 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
|||||||
final isValid = isUnique && isValidFormat;
|
final isValid = isUnique && isValidFormat;
|
||||||
|
|
||||||
return ResponsiveDialog(
|
return ResponsiveDialog(
|
||||||
title: const Text('Rename account'),
|
title: Text(AppLocalizations.of(context)!.oath_rename_account),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: didChange && isValid ? _submit : null,
|
onPressed: didChange && isValid ? _submit : null,
|
||||||
child: const Text('Save'),
|
child: Text(AppLocalizations.of(context)!.oath_save),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Rename $label?'),
|
Text(AppLocalizations.of(context)!.oath_rename(label)),
|
||||||
const Text(
|
Text(AppLocalizations.of(context)!
|
||||||
'This will change how the account is displayed in the list.'),
|
.oath_warning_will_change_account_displayed),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
initialValue: _issuer,
|
initialValue: _issuer,
|
||||||
enabled: issuerRemaining > 0,
|
enabled: issuerRemaining > 0,
|
||||||
maxLength: issuerRemaining > 0 ? issuerRemaining : null,
|
maxLength: issuerRemaining > 0 ? issuerRemaining : null,
|
||||||
buildCounter: buildByteCounterFor(_issuer),
|
buildCounter: buildByteCounterFor(_issuer),
|
||||||
inputFormatters: [limitBytesLength(issuerRemaining)],
|
inputFormatters: [limitBytesLength(issuerRemaining)],
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
labelText: 'Issuer (optional)',
|
labelText: AppLocalizations.of(context)!.oath_issuer_optional,
|
||||||
helperText: '', // Prevents dialog resizing when disabled
|
helperText: '', // Prevents dialog resizing when disabled
|
||||||
prefixIcon: Icon(Icons.business_outlined),
|
prefixIcon: const Icon(Icons.business_outlined),
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@ -150,12 +151,12 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
|||||||
buildCounter: buildByteCounterFor(_account),
|
buildCounter: buildByteCounterFor(_account),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
labelText: 'Account name',
|
labelText: AppLocalizations.of(context)!.oath_account_name,
|
||||||
helperText: '', // Prevents dialog resizing when disabled
|
helperText: '', // Prevents dialog resizing when disabled
|
||||||
errorText: !isValidFormat
|
errorText: !isValidFormat
|
||||||
? 'Your account must have a name'
|
? AppLocalizations.of(context)!.oath_account_must_have_name
|
||||||
: !isUnique
|
: !isUnique
|
||||||
? 'This name already exists for the Issuer'
|
? AppLocalizations.of(context)!.oath_name_exists
|
||||||
: null,
|
: null,
|
||||||
prefixIcon: const Icon(Icons.people_alt_outlined),
|
prefixIcon: const Icon(Icons.people_alt_outlined),
|
||||||
),
|
),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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 '../../app/message.dart';
|
import '../../app/message.dart';
|
||||||
@ -14,25 +15,25 @@ class ResetDialog extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return ResponsiveDialog(
|
return ResponsiveDialog(
|
||||||
title: const Text('Factory reset'),
|
title: Text(AppLocalizations.of(context)!.oath_factory_reset),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await ref.read(oathStateProvider(devicePath).notifier).reset();
|
await ref.read(oathStateProvider(devicePath).notifier).reset();
|
||||||
await ref.read(withContextProvider)((context) async {
|
await ref.read(withContextProvider)((context) async {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
showMessage(context, 'OATH application reset');
|
showMessage(context,
|
||||||
|
AppLocalizations.of(context)!.oath_oath_application_reset);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: const Text('Reset'),
|
child: Text(AppLocalizations.of(context)!.oath_reset),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
Text(AppLocalizations.of(context)!.oath_warning_will_delete_accounts),
|
||||||
'Warning! This will irrevocably delete all OATH TOTP/HOTP accounts from your YubiKey.'),
|
|
||||||
Text(
|
Text(
|
||||||
'Your OATH credentials, as well as any password set, will be removed from this YubiKey. Make sure to first disable these from their respective web sites to avoid being locked out of your accounts.',
|
AppLocalizations.of(context)!.oath_warning_disable_these_creds,
|
||||||
style: Theme.of(context).textTheme.bodyText1,
|
style: Theme.of(context).textTheme.bodyText1,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user