Skip to content

Commit dc77dd5

Browse files
committed
chore: Various fixes and improvements.
1 parent 047a561 commit dc77dd5

9 files changed

Lines changed: 122 additions & 70 deletions

File tree

lib/i18n/de/error.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"noPasswordVerificationMethodAvailable": "Die App kann nicht mit Ihrem Passwort entsperrt werden, da die Integrität der App-Daten verändert wurde. Bitte ändern Sie Ihr Master-Passwort, um fortzufahren.\nBeachten Sie, dass Ihre TOTP-Codes weiterhin manuell entschlüsselt werden müssen."
3939
},
4040
"backend": {
41+
"invalidJsonResponse": "Ungültige Antwort vom $route (HTTP $statusCode) : $body.",
4142
"noEmailToConfirm": "Keine E-Mail zum Bestätigen.",
4243
"invalidSession": "Ihre Sitzung ist ungültig. Bitte versuchen Sie sich erneut einzuloggen.",
4344
"noSession": "Sie müssen sich einloggen, um fortzufahren.",

lib/i18n/en/error.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"noPasswordVerificationMethodAvailable": "Cannot unlock the app with your password because the app data integrity has been altered. Please change your master password to continue.\nNote that your TOTPs will still need to be decrypted manually."
3939
},
4040
"backend": {
41+
"invalidJsonResponse": "Invalid response from $route (HTTP $statusCode) : $body.",
4142
"noEmailToConfirm": "No email to confirm.",
4243
"invalidSession": "Your session is invalid. Please try to log-in again.",
4344
"noSession": "You must be logged-in to proceed.",

lib/i18n/fr/error.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"noPasswordVerificationMethodAvailable": "Impossible de déverrouiller l'application avec votre mot de passe car les données de l'application ont été altérées. Veuillez changer votre mot de passe maître pour continuer.\nVeuillez noter que vos TOTPs devront être déchiffrés manuellement."
3939
},
4040
"backend": {
41+
"invalidJsonResponse": "Réponse invalide du $route (HTTP $statusCode) : $body.",
4142
"noEmailToConfirm": "Aucun email à confirmer.",
4243
"invalidSession": "Votre session est invalide. Veuillez réessayer de vous connecter.",
4344
"noSession": "Vous devez être connecté pour continuer.",

lib/i18n/it/error.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"noPasswordVerificationMethodAvailable": "Impossibile sbloccare l'applicazione con la tua password perché le informazioni sull'applicazione sono state alterate. Cambia la tua password principale per continuare.\nNota che i tuoi TOTPs dovranno essere decifrati manualmente."
3939
},
4040
"backend": {
41+
"invalidJsonResponse": "Risposta non valida dal $route (HTTP $statusCode) : $body.",
4142
"noEmailToConfirm": "Nessun email da confermare.",
4243
"invalidSession": "La tua sessione non è valida. Riprova a effettuare il login.",
4344
"noSession": "Devi effettuare il login per continuare.",

lib/i18n/pt/error.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"noPasswordVerificationMethodAvailable": "Falha ao desbloquear a aplicação com sua senha porque as informações da aplicação foram alteradas. Altere sua senha mestra para continuar.\nNota que seus TOTPs serão descriptografados manualmente."
3939
},
4040
"backend": {
41+
"invalidJsonResponse": "Resposta inválida do $route (HTTP $statusCode) : $body.",
4142
"noEmailToConfirm": "Nenhum e-mail para confirmar.",
4243
"invalidSession": "Sua sessão é inválida. Tente novamente para fazer login.",
4344
"noSession": "Você deve estar logado para prosseguir.",

lib/model/backend/request/request.dart

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'dart:convert';
22

33
import 'package:http/http.dart' as http;
4+
import 'package:open_authenticator/i18n/localizable_exception.dart';
5+
import 'package:open_authenticator/i18n/translations.g.dart';
46
import 'package:open_authenticator/model/backend/authentication/providers/provider.dart';
57
import 'package:open_authenticator/model/backend/request/error.dart';
68
import 'package:open_authenticator/model/backend/request/response.dart';
@@ -25,21 +27,52 @@ sealed class BackendRequest<T extends BackendResponse> {
2527

2628
/// Converts the response to a backend response.
2729
T toResponse(http.Response response) {
28-
Map<String, dynamic> json = jsonDecode(response.body);
29-
if (!json['success']) {
30-
throw BackendRequestError.fromJson(
30+
try {
31+
Map<String, dynamic> json = jsonDecode(response.body);
32+
if (!json.containsKey('success') || !json.containsKey('data')) {
33+
throw InvalidJsonResponse(
34+
route: route,
35+
statusCode: response.statusCode,
36+
body: response.body,
37+
);
38+
}
39+
if (json['success'] != true) {
40+
throw BackendRequestError.fromJson(
41+
route: route,
42+
statusCode: response.statusCode,
43+
json: json,
44+
);
45+
}
46+
return _toResponseIfNoError(json['data']);
47+
} on FormatException {
48+
throw InvalidJsonResponse(
3149
route: route,
3250
statusCode: response.statusCode,
33-
json: json,
51+
body: response.body,
3452
);
3553
}
36-
return _toResponseIfNoError(json['data']);
3754
}
3855

3956
/// Converts the response to a backend response.
4057
T _toResponseIfNoError(dynamic data);
4158
}
4259

60+
/// Thrown when the response is invalid.
61+
class InvalidJsonResponse extends LocalizableException {
62+
/// Creates a new invalid JSON response exception instance.
63+
InvalidJsonResponse({
64+
required String route,
65+
required int statusCode,
66+
required String body,
67+
}) : super(
68+
localizedErrorMessage: translations.error.backend.invalidJsonResponse(
69+
route: route,
70+
statusCode: statusCode,
71+
body: body,
72+
),
73+
);
74+
}
75+
4376
/// Represents a backend request with a body.
4477
mixin BackendWithBodyRequest<T extends BackendResponse> on BackendRequest<T> {
4578
/// The body.

lib/model/backup.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ final backupStoreProvider = AsyncNotifierProvider.autoDispose<BackupStore, List<
2323
/// Contains all backups.
2424
class BackupStore extends AsyncNotifier<List<Backup>> {
2525
/// The backup filename regex.
26-
static const String _kBackupFilenameRegex = r'\d{10}\.bak';
26+
static const String _kBackupFilenameRegex = r'\d{13}\.bak';
2727

2828
@override
2929
FutureOr<List<Backup>> build() => _listBackups();
@@ -36,7 +36,7 @@ class BackupStore extends AsyncNotifier<List<Backup>> {
3636
DateTime? dateTime = _fromBackupFilename(backupFile);
3737
Backup backup = Backup._(ref: ref, dateTime: dateTime ?? DateTime.now());
3838
List<Backup> backups = [...(await future), backup]..sort();
39-
Directory directory = await getBackupsDirectory();
39+
Directory directory = await getBackupsDirectory(create: true);
4040
backupFile.copySync(join(directory.path, backup.filename));
4141
if (!ref.mounted) {
4242
return const ResultCancelled();
@@ -167,7 +167,7 @@ class Backup implements Comparable<Backup> {
167167
totps.add(decryptedTotp ?? totp);
168168
}
169169
}
170-
if (totps.isEmpty) {
170+
if (totps.isEmpty && jsonTotps.isNotEmpty) {
171171
throw InvalidPasswordException();
172172
}
173173
return await _ref.read(totpRepositoryProvider.notifier).replaceBy(totps);

lib/widgets/blur.dart

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,28 @@ class Blur extends StatelessWidget {
3535
});
3636

3737
@override
38-
Widget build(BuildContext context) => ClipRRect(
39-
borderRadius: borderRadius ?? BorderRadius.zero,
40-
child: Stack(
41-
children: [
42-
?below,
43-
Positioned.fill(
44-
child: BackdropFilter(
45-
filter: context.theme.dialogRouteStyle.barrierFilter?.call(1) ?? ImageFilter.blur(sigmaX: 5, sigmaY: 5),
46-
child: Container(
47-
decoration: BoxDecoration(
48-
color: context.theme.colors.background.withValues(alpha: colorOpacity),
38+
Widget build(BuildContext context) => PopScope(
39+
canPop: above == null || below == null,
40+
child: ClipRRect(
41+
borderRadius: borderRadius ?? BorderRadius.zero,
42+
child: Stack(
43+
children: [
44+
?below,
45+
Positioned.fill(
46+
child: BackdropFilter(
47+
filter: context.theme.dialogRouteStyle.barrierFilter?.call(1) ?? ImageFilter.blur(sigmaX: 5, sigmaY: 5),
48+
child: Container(
49+
decoration: BoxDecoration(
50+
color: context.theme.colors.background.withValues(alpha: colorOpacity),
51+
),
52+
alignment: alignment,
53+
child: overlay,
4954
),
50-
alignment: alignment,
51-
child: overlay,
5255
),
5356
),
54-
),
55-
?above,
56-
],
57+
?above,
58+
],
59+
),
5760
),
5861
);
5962
}

lib/widgets/dialog/text_input_dialog.dart

Lines changed: 57 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -109,62 +109,73 @@ class _TextInputDialogState extends State<TextInputDialog> {
109109
});
110110

111111
/// Whether the input is valid.
112-
late bool valid = widget.validator == null ? true : (widget.validator!.call(value) != null);
112+
late bool valid = widget.validator == null ? true : (widget.validator!(value) == null);
113113

114114
@override
115-
Widget build(BuildContext context) => AppDialog(
116-
title: Text(widget.title),
117-
actions: [
118-
ClickableButton(
119-
onPress: valid ? (() => Navigator.pop(context, value)) : null,
120-
child: ButtonText(MaterialLocalizations.of(context).okButtonLabel),
121-
),
122-
ClickableButton(
123-
variant: .secondary,
124-
onPress: () => Navigator.pop(context),
125-
child: ButtonText(MaterialLocalizations.of(context).cancelButtonLabel),
126-
),
127-
],
128-
children: [
129-
Text(
130-
widget.message,
131-
textAlign: TextAlign.left,
132-
),
133-
if (widget.children != null) ...widget.children!,
134-
if (widget.password)
135-
PasswordFormField(
136-
control: .managed(controller: valueController),
137-
autofocus: true,
138-
onSubmit: (value) => Navigator.pop(context, value),
139-
textInputAction: TextInputAction.go,
140-
validator: widget.validator,
141-
keyboardType: widget.keyboardType,
142-
autovalidateMode: AutovalidateMode.always,
143-
inputFormatters: widget.inputFormatters,
144-
textCapitalization: widget.textCapitalization ?? .none,
145-
)
146-
else
147-
FTextFormField(
148-
control: .managed(controller: valueController),
149-
autofocus: true,
150-
onSubmit: (value) => Navigator.pop(context, value),
151-
textInputAction: TextInputAction.go,
152-
validator: widget.validator,
153-
keyboardType: widget.keyboardType,
154-
autovalidateMode: AutovalidateMode.always,
155-
inputFormatters: widget.inputFormatters,
156-
textCapitalization: widget.textCapitalization ?? .none,
115+
Widget build(BuildContext context) {
116+
void onSubmit(String value) {
117+
bool valid = onChanged(value);
118+
if (valid) {
119+
Navigator.pop(context, value);
120+
}
121+
}
122+
123+
return AppDialog(
124+
title: Text(widget.title),
125+
actions: [
126+
ClickableButton(
127+
onPress: valid ? (() => Navigator.pop(context, value)) : null,
128+
child: ButtonText(MaterialLocalizations.of(context).okButtonLabel),
157129
),
158-
],
159-
);
130+
ClickableButton(
131+
variant: .secondary,
132+
onPress: () => Navigator.pop(context),
133+
child: ButtonText(MaterialLocalizations.of(context).cancelButtonLabel),
134+
),
135+
],
136+
children: [
137+
Text(
138+
widget.message,
139+
textAlign: TextAlign.left,
140+
),
141+
if (widget.children != null) ...widget.children!,
142+
if (widget.password)
143+
PasswordFormField(
144+
control: .managed(controller: valueController),
145+
autofocus: true,
146+
onSubmit: onSubmit,
147+
textInputAction: TextInputAction.go,
148+
validator: widget.validator,
149+
keyboardType: widget.keyboardType,
150+
autovalidateMode: AutovalidateMode.always,
151+
inputFormatters: widget.inputFormatters,
152+
textCapitalization: widget.textCapitalization ?? .none,
153+
)
154+
else
155+
FTextFormField(
156+
control: .managed(controller: valueController),
157+
autofocus: true,
158+
onSubmit: onSubmit,
159+
textInputAction: TextInputAction.go,
160+
validator: widget.validator,
161+
keyboardType: widget.keyboardType,
162+
autovalidateMode: AutovalidateMode.always,
163+
inputFormatters: widget.inputFormatters,
164+
textCapitalization: widget.textCapitalization ?? .none,
165+
),
166+
],
167+
);
168+
}
160169

161170
/// Triggered when changed.
162-
void onChanged(String newValue) {
171+
bool onChanged(String newValue) {
163172
value = newValue;
164173
if (widget.validator != null) {
165174
bool result = widget.validator!(newValue) == null;
166175
setState(() => valid = result);
176+
return result;
167177
}
178+
return true;
168179
}
169180
}
170181

0 commit comments

Comments
 (0)