Skip to content

Commit db48590

Browse files
committed
feat: Improved error handling (for instance : if crypto store cannot be loaded).
1 parent 6af1af0 commit db48590

File tree

17 files changed

+572
-299
lines changed

17 files changed

+572
-299
lines changed

lib/i18n/en/app_unlock.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,15 @@
99
"enable": "Authenticate to enable local authentication.",
1010
"disable": "Authenticate to disable local authentication."
1111
},
12-
"masterPasswordDialogMessage": "Please enter your master password to unlock the app."
12+
"masterPasswordDialogMessage": "Please enter your master password to unlock the app.",
13+
"cannotUnlock": {
14+
"localAuthentication": {
15+
"deviceNotSupported": "Cannot unlock the app because your device is not supported by local authentication. Disable this locking method with the button below.",
16+
"button": "Disable"
17+
},
18+
"masterPassword": {
19+
"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.",
20+
"button": "Change master password"
21+
}
22+
}
1323
}

lib/i18n/en/home.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@
1111
"title": "Add manually",
1212
"subtitle": "Manually enter your TOTP details (eg. secret, label, issuer, ...)."
1313
}
14+
},
15+
"noCryptoStore": {
16+
"message": "An error occurred while loading your crypto store. This means that we cannot do any encryption / decryption operation. Please restart the app or enter a new master password using the button below.",
17+
"resetButton": "Reset master password"
1418
}
1519
}

lib/i18n/fr/app_unlock.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,15 @@
99
"enable": "Authentifiez-vous pour activer l'authentification locale.",
1010
"disable": "Authentifiez-vous pour désactiver l'authentification locale."
1111
},
12-
"masterPasswordDialogMessage": "Veuillez entrer votre mot de passe maître pour déverrouiller l'application."
12+
"masterPasswordDialogMessage": "Veuillez entrer votre mot de passe maître pour déverrouiller l'application.",
13+
"cannotUnlock": {
14+
"localAuthentication": {
15+
"deviceNotSupported": "Impossible de déverouiller l'application car l'authentification locale n'est pas supportée. Désactivez cette méthode de verrouillage avec le bouton ci-dessous.",
16+
"button": "Désactiver"
17+
},
18+
"masterPassword": {
19+
"noPasswordVerificationMethodAvailable": "Impossible de déverouiller l'application parce que l'intégrité des données de l'application a été altérée.\nVeuillez modifier votre mot de passe maître pour continuer.\nNotez que vos TOTPs devront tout de même être déchiffrés manuellement.",
20+
"button": "Changer le mot de passe maître"
21+
}
22+
}
1323
}

lib/i18n/fr/home.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@
1111
"title": "Ajouter manuellement",
1212
"subtitle": "Entrer manuellement les détails du TOTP (ex. secret, étiquette, émetteur, ...)."
1313
}
14+
},
15+
"noCryptoStore": {
16+
"message": "Une erreur est survenue durant la lecture du crypto store. Cela veut dire qu'aucune opération de chiffrement / déchiffrement n'est possible. Veuillez redémarrer l'application ou entrer un nouveau mot de passe maître en utilisant le bouton ci-dessous.",
17+
"resetButton": "Réinitialiser le mot de passe maître"
1418
}
1519
}

lib/main.dart

Lines changed: 127 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:open_authenticator/i18n/translations.g.dart';
1414
import 'package:open_authenticator/model/app_links.dart';
1515
import 'package:open_authenticator/model/authentication/providers/email_link.dart';
1616
import 'package:open_authenticator/model/authentication/providers/provider.dart';
17+
import 'package:open_authenticator/model/crypto.dart';
1718
import 'package:open_authenticator/model/settings/show_intro.dart';
1819
import 'package:open_authenticator/model/settings/theme.dart';
1920
import 'package:open_authenticator/model/totp/repository.dart';
@@ -102,108 +103,141 @@ class OpenAuthenticatorApp extends ConsumerWidget {
102103
Widget build(BuildContext context, WidgetRef ref) {
103104
AsyncValue<bool> showIntro = ref.watch(showIntroSettingsEntryProvider);
104105
AsyncValue<ThemeMode> theme = ref.watch(themeSettingsEntryProvider);
106+
Locale locale = TranslationProvider.of(context).flutterLocale;
107+
return switch (showIntro) {
108+
AsyncData(:bool value) => _createMaterialApp(
109+
showIntroState: 'data',
110+
theme: theme,
111+
locale: locale,
112+
initialRoute: value ? IntroPage.name : HomePage.name,
113+
),
114+
AsyncError(:final error) => _createMaterialApp(
115+
showIntroState: 'error',
116+
theme: theme,
117+
locale: locale,
118+
home: Center(
119+
child: Text('Error : $error.'),
120+
),
121+
),
122+
_ => _createMaterialApp(
123+
showIntroState: 'loading',
124+
theme: theme,
125+
locale: locale,
126+
home: const CenteredCircularProgressIndicator(),
127+
),
128+
};
129+
}
130+
131+
/// Creates a [MaterialApp] widget.
132+
Widget _createMaterialApp({
133+
required String showIntroState,
134+
required AsyncValue<ThemeMode> theme,
135+
required Locale locale,
136+
String? initialRoute,
137+
Widget? home,
138+
}) {
105139
ColorScheme light = ColorScheme.fromSeed(
106140
seedColor: Colors.green,
107141
);
108142
ColorScheme dark = ColorScheme.fromSeed(
109143
seedColor: Colors.green,
110144
brightness: Brightness.dark,
111145
);
112-
return switch (showIntro) {
113-
AsyncData(:bool value) => MaterialApp(
114-
title: App.appName,
115-
locale: TranslationProvider.of(context).flutterLocale,
116-
localizationsDelegates: const [
117-
GlobalMaterialLocalizations.delegate,
118-
GlobalWidgetsLocalizations.delegate,
119-
GlobalCupertinoLocalizations.delegate,
120-
],
121-
supportedLocales: AppLocaleUtils.supportedLocales,
122-
themeMode: theme.value,
123-
darkTheme: ThemeData(
124-
brightness: Brightness.dark,
125-
appBarTheme: AppBarTheme(
126-
systemOverlayStyle: SystemUiOverlayStyle(
127-
statusBarIconBrightness: Brightness.light,
128-
systemNavigationBarColor: dark.surface,
129-
),
130-
shape: const RoundedRectangleBorder(),
131-
surfaceTintColor: Colors.green,
132-
),
133-
colorScheme: dark,
134-
// iconButtonTheme: IconButtonThemeData(
135-
// style: ButtonStyle(
136-
// foregroundColor: MaterialStatePropertyAll(Colors.green.shade300),
137-
// ),
138-
// ),
139-
buttonTheme: const ButtonThemeData(
140-
alignedDropdown: true,
141-
),
142-
floatingActionButtonTheme: FloatingActionButtonThemeData(
143-
shape: const CircleBorder(),
144-
backgroundColor: Colors.green.shade700,
145-
foregroundColor: Colors.green.shade50,
146-
),
146+
return MaterialApp(
147+
key: ValueKey('materialApp.$showIntroState'),
148+
title: App.appName,
149+
locale: locale,
150+
localizationsDelegates: const [
151+
GlobalMaterialLocalizations.delegate,
152+
GlobalWidgetsLocalizations.delegate,
153+
GlobalCupertinoLocalizations.delegate,
154+
],
155+
supportedLocales: AppLocaleUtils.supportedLocales,
156+
themeMode: theme.value,
157+
darkTheme: ThemeData(
158+
brightness: Brightness.dark,
159+
appBarTheme: AppBarTheme(
160+
systemOverlayStyle: SystemUiOverlayStyle(
161+
statusBarIconBrightness: Brightness.light,
162+
systemNavigationBarColor: dark.surface,
147163
),
148-
theme: ThemeData(
149-
colorScheme: light,
150-
appBarTheme: AppBarTheme(
151-
systemOverlayStyle: SystemUiOverlayStyle(
152-
statusBarIconBrightness: Brightness.dark,
153-
systemNavigationBarColor: light.surface,
154-
),
155-
shape: const RoundedRectangleBorder(),
156-
),
157-
buttonTheme: const ButtonThemeData(
158-
alignedDropdown: true,
159-
),
160-
floatingActionButtonTheme: FloatingActionButtonThemeData(
161-
shape: const CircleBorder(),
162-
backgroundColor: Colors.green.shade50,
163-
foregroundColor: Colors.green.shade700,
164-
),
165-
inputDecorationTheme: InputDecorationTheme(
166-
disabledBorder: UnderlineInputBorder(
167-
borderSide: BorderSide(color: Colors.grey.shade400),
168-
),
169-
),
170-
dividerTheme: const DividerThemeData(
171-
color: Colors.black12,
172-
),
164+
shape: const RoundedRectangleBorder(),
165+
surfaceTintColor: Colors.green,
166+
),
167+
colorScheme: dark,
168+
// iconButtonTheme: IconButtonThemeData(
169+
// style: ButtonStyle(
170+
// foregroundColor: MaterialStatePropertyAll(Colors.green.shade300),
171+
// ),
172+
// ),
173+
buttonTheme: const ButtonThemeData(
174+
alignedDropdown: true,
175+
),
176+
floatingActionButtonTheme: FloatingActionButtonThemeData(
177+
shape: const CircleBorder(),
178+
backgroundColor: Colors.green.shade700,
179+
foregroundColor: Colors.green.shade50,
180+
),
181+
),
182+
theme: ThemeData(
183+
colorScheme: light,
184+
appBarTheme: AppBarTheme(
185+
systemOverlayStyle: SystemUiOverlayStyle(
186+
statusBarIconBrightness: Brightness.dark,
187+
systemNavigationBarColor: light.surface,
173188
),
174-
routes: {
175-
IntroPage.name: (_) => _RouteWidget(
176-
child: const IntroPage(),
177-
),
178-
HomePage.name: (_) => _RouteWidget(
179-
listen: currentPlatform.isMobile || kDebugMode,
180-
rateMyApp: true,
181-
child: const HomePage(),
182-
),
183-
ScanPage.name: (_) => const _RouteWidget(
184-
child: ScanPage(),
185-
),
186-
SettingsPage.name: (_) => const _RouteWidget(
187-
child: SettingsPage(),
188-
),
189-
TotpPage.name: (context) {
190-
Map<String, dynamic>? arguments = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>?;
191-
return _RouteWidget(
192-
child: TotpPage(
193-
totp: arguments?[kRouteParameterTotp],
194-
add: arguments?[kRouteParameterAddTotp],
195-
),
196-
);
197-
},
198-
ContributorPlanFallbackPaywallPage.name: (_) => const _RouteWidget(
199-
child: ContributorPlanFallbackPaywallPage(),
200-
),
201-
},
202-
initialRoute: value ? IntroPage.name : HomePage.name,
189+
shape: const RoundedRectangleBorder(),
203190
),
204-
AsyncError(:final error) => Text('Error : $error.'),
205-
_ => const CenteredCircularProgressIndicator(),
206-
};
191+
buttonTheme: const ButtonThemeData(
192+
alignedDropdown: true,
193+
),
194+
floatingActionButtonTheme: FloatingActionButtonThemeData(
195+
shape: const CircleBorder(),
196+
backgroundColor: Colors.green.shade50,
197+
foregroundColor: Colors.green.shade700,
198+
),
199+
inputDecorationTheme: InputDecorationTheme(
200+
disabledBorder: UnderlineInputBorder(
201+
borderSide: BorderSide(color: Colors.grey.shade400),
202+
),
203+
),
204+
dividerTheme: const DividerThemeData(
205+
color: Colors.black12,
206+
),
207+
),
208+
routes: home == null
209+
? {
210+
IntroPage.name: (_) => _RouteWidget(
211+
child: const IntroPage(),
212+
),
213+
HomePage.name: (_) => _RouteWidget(
214+
listen: true,
215+
rateMyApp: true,
216+
child: const HomePage(),
217+
),
218+
ScanPage.name: (_) => const _RouteWidget(
219+
child: ScanPage(),
220+
),
221+
SettingsPage.name: (_) => const _RouteWidget(
222+
child: SettingsPage(),
223+
),
224+
TotpPage.name: (context) {
225+
Map<String, dynamic>? arguments = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>?;
226+
return _RouteWidget(
227+
child: TotpPage(
228+
totp: arguments?[kRouteParameterTotp],
229+
add: arguments?[kRouteParameterAddTotp],
230+
),
231+
);
232+
},
233+
ContributorPlanFallbackPaywallPage.name: (_) => const _RouteWidget(
234+
child: ContributorPlanFallbackPaywallPage(),
235+
),
236+
}
237+
: {},
238+
initialRoute: home == null ? initialRoute : null,
239+
home: home,
240+
);
207241
}
208242
}
209243

@@ -212,7 +246,7 @@ class _RouteWidget extends ConsumerStatefulWidget {
212246
/// The route widget.
213247
final Widget child;
214248

215-
/// Listen to [appLinksListenerProvider], [totpLimitProvider] and [appUnlockStateProvider].
249+
/// Listen to [appLinksListenerProvider], [totpLimitProvider], [appUnlockStateProvider] and [cryptoStoreProvider].
216250
final bool listen;
217251

218252
/// Whether to initialize and run RateMyApp.

0 commit comments

Comments
 (0)