mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 12:12:13 +00:00
display critical error instead of removing app data
This commit is contained in:
parent
db9d9022fd
commit
583368505d
13 changed files with 221 additions and 112 deletions
103
lib/app.dart
103
lib/app.dart
|
|
@ -17,6 +17,7 @@ import 'package:twonly/src/utils/pow.dart';
|
||||||
import 'package:twonly/src/visual/components/app_outdated.comp.dart';
|
import 'package:twonly/src/visual/components/app_outdated.comp.dart';
|
||||||
import 'package:twonly/src/visual/themes/dark.dart';
|
import 'package:twonly/src/visual/themes/dark.dart';
|
||||||
import 'package:twonly/src/visual/themes/light.dart';
|
import 'package:twonly/src/visual/themes/light.dart';
|
||||||
|
import 'package:twonly/src/visual/views/critical_error.view.dart';
|
||||||
import 'package:twonly/src/visual/views/home.view.dart';
|
import 'package:twonly/src/visual/views/home.view.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/onboarding.view.dart';
|
import 'package:twonly/src/visual/views/onboarding/onboarding.view.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/register.view.dart';
|
import 'package:twonly/src/visual/views/onboarding/register.view.dart';
|
||||||
|
|
@ -31,6 +32,7 @@ class App extends StatefulWidget {
|
||||||
|
|
||||||
class _AppState extends State<App> with WidgetsBindingObserver {
|
class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
bool wasPaused = false;
|
bool wasPaused = false;
|
||||||
|
Object? _storageError;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -42,11 +44,20 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
final user = await getUser();
|
try {
|
||||||
if (user != null && mounted) {
|
final user = await getUser();
|
||||||
context.read<PurchasesProvider>().updatePlan(
|
if (user != null && mounted) {
|
||||||
planFromString(user.subscriptionPlan),
|
context.read<PurchasesProvider>().updatePlan(
|
||||||
);
|
planFromString(user.subscriptionPlan),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Storage error in App.initAsync: $e');
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_storageError = e;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await apiService.connect();
|
await apiService.connect();
|
||||||
await apiService.listenToNetworkChanges();
|
await apiService.listenToNetworkChanges();
|
||||||
|
|
@ -78,20 +89,38 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: context.watch<SettingsChangeProvider>(),
|
listenable: context.watch<SettingsChangeProvider>(),
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
|
const localizationsDelegates = [
|
||||||
|
AppLocalizations.delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
];
|
||||||
|
|
||||||
|
const supportedLocales = [
|
||||||
|
Locale('en', ''),
|
||||||
|
Locale('de', ''),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_storageError != null) {
|
||||||
|
return MaterialApp(
|
||||||
|
scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey,
|
||||||
|
localizationsDelegates: localizationsDelegates,
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
supportedLocales: supportedLocales,
|
||||||
|
title: 'twonly',
|
||||||
|
theme: lightTheme,
|
||||||
|
darkTheme: darkTheme,
|
||||||
|
themeMode: context.watch<SettingsChangeProvider>().themeMode,
|
||||||
|
home: const CriticalErrorView(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
routerConfig: routerProvider,
|
routerConfig: routerProvider,
|
||||||
scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey,
|
scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey,
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: localizationsDelegates,
|
||||||
AppLocalizations.delegate,
|
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate,
|
|
||||||
GlobalCupertinoLocalizations.delegate,
|
|
||||||
],
|
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
supportedLocales: const [
|
supportedLocales: supportedLocales,
|
||||||
Locale('en', ''),
|
|
||||||
Locale('de', ''),
|
|
||||||
],
|
|
||||||
title: 'twonly',
|
title: 'twonly',
|
||||||
theme: lightTheme,
|
theme: lightTheme,
|
||||||
darkTheme: darkTheme,
|
darkTheme: darkTheme,
|
||||||
|
|
@ -116,6 +145,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
bool _isUserCreated = false;
|
bool _isUserCreated = false;
|
||||||
bool _showOnboarding = true;
|
bool _showOnboarding = true;
|
||||||
bool _isLoaded = false;
|
bool _isLoaded = false;
|
||||||
|
Object? _storageError;
|
||||||
bool _skipBackup = kDebugMode;
|
bool _skipBackup = kDebugMode;
|
||||||
bool _isTwonlyLocked = true;
|
bool _isTwonlyLocked = true;
|
||||||
|
|
||||||
|
|
@ -128,26 +158,31 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
_isUserCreated = await isUserCreated();
|
try {
|
||||||
|
_isUserCreated = await isUserCreated();
|
||||||
|
|
||||||
if (_isUserCreated) {
|
if (_isUserCreated) {
|
||||||
if (_isTwonlyLocked) {
|
if (_isTwonlyLocked) {
|
||||||
// do not change in case twonly was already unlocked at some point
|
// do not change in case twonly was already unlocked at some point
|
||||||
_isTwonlyLocked = userService.currentUser.screenLockEnabled;
|
_isTwonlyLocked = userService.currentUser.screenLockEnabled;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// This means the user is in the onboarding screen, so start with the Proof of Work.
|
|
||||||
|
|
||||||
final (proof, disabled) = await apiService.getProofOfWork();
|
|
||||||
if (proof != null) {
|
|
||||||
Log.info('Starting with proof of work calculation.');
|
|
||||||
_proofOfWork = (
|
|
||||||
calculatePoW(proof.prefix, proof.difficulty.toInt()),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
_proofOfWork = (null, disabled);
|
// This means the user is in the onboarding screen, so start with the Proof of Work.
|
||||||
|
|
||||||
|
final (proof, disabled) = await apiService.getProofOfWork();
|
||||||
|
if (proof != null) {
|
||||||
|
Log.info('Starting with proof of work calculation.');
|
||||||
|
_proofOfWork = (
|
||||||
|
calculatePoW(proof.prefix, proof.difficulty.toInt()),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_proofOfWork = (null, disabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Storage error in AppMainWidget.initAsync: $e');
|
||||||
|
_storageError = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -161,6 +196,10 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
return Center(child: Container());
|
return Center(child: Container());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_storageError != null) {
|
||||||
|
return const CriticalErrorView();
|
||||||
|
}
|
||||||
|
|
||||||
late Widget child;
|
late Widget child;
|
||||||
|
|
||||||
if (_isUserCreated) {
|
if (_isUserCreated) {
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,13 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
|
|
||||||
_planSub = apiService.onPlanUpdated.listen(updatePlan);
|
_planSub = apiService.onPlanUpdated.listen(updatePlan);
|
||||||
_connSub = apiService.onConnectionStateUpdated.listen((_) async {
|
_connSub = apiService.onConnectionStateUpdated.listen((_) async {
|
||||||
final user = await getUser();
|
try {
|
||||||
if (user != null) {
|
final user = await getUser();
|
||||||
updatePlan(planFromString(user.subscriptionPlan));
|
if (user != null) {
|
||||||
|
updatePlan(planFromString(user.subscriptionPlan));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -90,16 +94,22 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
storeState = StoreState.available;
|
storeState = StoreState.available;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
final user = await getUser();
|
try {
|
||||||
if (user != null && isPayingUser(planFromString(user.subscriptionPlan))) {
|
final user = await getUser();
|
||||||
Log.info('Started IPA timer for verification.');
|
if (user != null && isPayingUser(planFromString(user.subscriptionPlan))) {
|
||||||
globalForceIpaCheck = Timer(const Duration(seconds: 5), () async {
|
Log.info('Started IPA timer for verification.');
|
||||||
Log.info('Force Ipa check was not stopped. Requesting forced check...');
|
globalForceIpaCheck = Timer(const Duration(seconds: 5), () async {
|
||||||
await apiService.forceIpaCheck();
|
Log.info(
|
||||||
});
|
'Force Ipa check was not stopped. Requesting forced check...',
|
||||||
}
|
);
|
||||||
|
await apiService.forceIpaCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await iapConnection.restorePurchases();
|
await iapConnection.restorePurchases();
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> buy(PurchasableProduct product) async {
|
Future<void> buy(PurchasableProduct product) async {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
late ThemeMode _themeMode;
|
late ThemeMode _themeMode;
|
||||||
|
|
@ -8,8 +9,13 @@ class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
ThemeMode get themeMode => _themeMode;
|
ThemeMode get themeMode => _themeMode;
|
||||||
|
|
||||||
Future<void> loadSettings() async {
|
Future<void> loadSettings() async {
|
||||||
_themeMode = (await getUser())?.themeMode ?? ThemeMode.system;
|
try {
|
||||||
notifyListeners();
|
_themeMode = (await getUser())?.themeMode ?? ThemeMode.system;
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
_themeMode = ThemeMode.system;
|
||||||
|
Log.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateThemeMode(ThemeMode? newThemeMode) async {
|
Future<void> updateThemeMode(ThemeMode? newThemeMode) async {
|
||||||
|
|
|
||||||
|
|
@ -416,23 +416,22 @@ class ApiService {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> tryAuthenticateWithToken(int userId) async {
|
Future<bool> tryAuthenticateWithToken() async {
|
||||||
final apiAuthToken = await SecureStorage.instance.read(
|
final apiAuthToken = await SecureStorage.instance.read(
|
||||||
key: SecureStorageKeys.apiAuthToken,
|
key: SecureStorageKeys.apiAuthToken,
|
||||||
);
|
);
|
||||||
final user = await getUser();
|
|
||||||
|
|
||||||
if (apiAuthToken != null && user != null) {
|
if (apiAuthToken != null) {
|
||||||
if (user.appVersion < 62) {
|
if (userService.currentUser.appVersion < 62) {
|
||||||
Log.error(
|
Log.error(
|
||||||
'DID NOT authenticate the user, as he still has the old version!',
|
'DID NOT authenticate the user, as he still has the old version!',
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final authenticate = Handshake_Authenticate()
|
final authenticate = Handshake_Authenticate()
|
||||||
..userId = Int64(userId)
|
..userId = Int64(userService.currentUser.userId)
|
||||||
..appVersion = (await PackageInfo.fromPlatform()).version
|
..appVersion = (await PackageInfo.fromPlatform()).version
|
||||||
..deviceId = Int64(user.deviceId)
|
..deviceId = Int64(userService.currentUser.deviceId)
|
||||||
..inBackground = AppState.isInBackgroundTask
|
..inBackground = AppState.isInBackgroundTask
|
||||||
..authToken = base64Decode(apiAuthToken);
|
..authToken = base64Decode(apiAuthToken);
|
||||||
|
|
||||||
|
|
@ -474,7 +473,7 @@ class ApiService {
|
||||||
final userData = await getUser();
|
final userData = await getUser();
|
||||||
if (userData == null) return;
|
if (userData == null) return;
|
||||||
|
|
||||||
if (await tryAuthenticateWithToken(userData.userId)) {
|
if (await tryAuthenticateWithToken()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -522,7 +521,7 @@ class ApiService {
|
||||||
value: apiAuthTokenB64,
|
value: apiAuthTokenB64,
|
||||||
);
|
);
|
||||||
|
|
||||||
await tryAuthenticateWithToken(userData.userId);
|
await tryAuthenticateWithToken();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -797,11 +796,10 @@ class ApiService {
|
||||||
});
|
});
|
||||||
return ballance;
|
return ballance;
|
||||||
}
|
}
|
||||||
final user = await getUser();
|
if (userService.currentUser.lastPlanBallance != null && useCache) {
|
||||||
if (user != null && user.lastPlanBallance != null && useCache) {
|
|
||||||
try {
|
try {
|
||||||
return Response_PlanBallance.fromJson(
|
return Response_PlanBallance.fromJson(
|
||||||
user.lastPlanBallance!,
|
userService.currentUser.lastPlanBallance!,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error('from json: $e');
|
Log.error('from json: $e');
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,11 @@ Future<void> handleBackupData(
|
||||||
final originalDatabase = File(
|
final originalDatabase = File(
|
||||||
join(AppEnvironment.supportDir, 'twonly.sqlite'),
|
join(AppEnvironment.supportDir, 'twonly.sqlite'),
|
||||||
);
|
);
|
||||||
await originalDatabase.writeAsBytes(backupContent.twonlyDatabase);
|
|
||||||
|
// in case there was only a secure storage error, do not replace the original database
|
||||||
|
if (!originalDatabase.existsSync()) {
|
||||||
|
await originalDatabase.writeAsBytes(backupContent.twonlyDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
const storage = SecureStorage.instance;
|
const storage = SecureStorage.instance;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,18 +79,22 @@ Future<void> updateUser(
|
||||||
void Function(UserData userData) updateUser,
|
void Function(UserData userData) updateUser,
|
||||||
) async {
|
) async {
|
||||||
await updateProtection.protect(() async {
|
await updateProtection.protect(() async {
|
||||||
final user = await getUser();
|
try {
|
||||||
if (user == null) return;
|
final user = await getUser();
|
||||||
if (user.defaultShowTime == 999999) {
|
if (user == null) return;
|
||||||
// This was the old version for infinity -> change it to null
|
if (user.defaultShowTime == 999999) {
|
||||||
user.defaultShowTime = null;
|
// This was the old version for infinity -> change it to null
|
||||||
|
user.defaultShowTime = null;
|
||||||
|
}
|
||||||
|
updateUser(user);
|
||||||
|
await const FlutterSecureStorage().write(
|
||||||
|
key: SecureStorageKeys.userData,
|
||||||
|
value: jsonEncode(user),
|
||||||
|
);
|
||||||
|
userService.currentUser = user;
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Could not update the user: $e');
|
||||||
}
|
}
|
||||||
updateUser(user);
|
|
||||||
await const FlutterSecureStorage().write(
|
|
||||||
key: SecureStorageKeys.userData,
|
|
||||||
value: jsonEncode(user),
|
|
||||||
);
|
|
||||||
userService.currentUser = user;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
userService.triggerUserUpdate();
|
userService.triggerUserUpdate();
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import 'package:gal/gal.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:local_auth/local_auth.dart';
|
import 'package:local_auth/local_auth.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
|
||||||
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
|
|
@ -19,7 +18,6 @@ import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
extension ShortCutsExtension on BuildContext {
|
extension ShortCutsExtension on BuildContext {
|
||||||
AppLocalizations get lang => AppLocalizations.of(this)!;
|
AppLocalizations get lang => AppLocalizations.of(this)!;
|
||||||
TwonlyDB get db => Provider.of<TwonlyDB>(this);
|
|
||||||
ColorScheme get color => Theme.of(this).colorScheme;
|
ColorScheme get color => Theme.of(this).colorScheme;
|
||||||
Future<dynamic> navPush(Widget route) async {
|
Future<dynamic> navPush(Widget route) async {
|
||||||
return Navigator.push(
|
return Navigator.push(
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
class FeedbackIconButtonComp extends StatefulWidget {
|
class FeedbackIconButtonComp extends StatefulWidget {
|
||||||
|
|
@ -24,10 +24,9 @@ class _FeedbackIconButtonCompState extends State<FeedbackIconButtonComp> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
final user = await getUser();
|
if (!mounted) return;
|
||||||
if (user == null || !mounted) return;
|
|
||||||
setState(() {
|
setState(() {
|
||||||
showFeedbackShortcut = user.showFeedbackShortcut;
|
showFeedbackShortcut = userService.currentUser.showFeedbackShortcut;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
68
lib/src/visual/views/critical_error.view.dart
Normal file
68
lib/src/visual/views/critical_error.view.dart
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:restart_app/restart_app.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/visual/views/onboarding/recover.view.dart';
|
||||||
|
import 'package:twonly/src/visual/views/settings/help/contact_us.view.dart';
|
||||||
|
|
||||||
|
class CriticalErrorView extends StatelessWidget {
|
||||||
|
const CriticalErrorView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const FaIcon(
|
||||||
|
FontAwesomeIcons.triangleExclamation,
|
||||||
|
size: 80,
|
||||||
|
color: Colors.redAccent,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
'Critical Error',
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'Please try restarting twonly. If the error persists, please contact our support and upload your debug log so we can troubleshoot the issue.\n\nYou can restore your account using the button below. If you have forgotten your password, you will need to reinstall twonly and then register with a new account.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: () async {
|
||||||
|
await Restart.restartApp(
|
||||||
|
notificationTitle: 'App restarted',
|
||||||
|
notificationBody: 'Click here to open the app again',
|
||||||
|
forceKill: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
label: const Text('Try Again'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: () async {
|
||||||
|
await context.navPush(const BackupRecoveryView());
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.backup_rounded),
|
||||||
|
label: const Text('Recovery from backup'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => context.navPush(const ContactUsView()),
|
||||||
|
child: const Text('Contact Support'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -70,13 +70,6 @@ class HomeViewState extends State<HomeView> {
|
||||||
unawaited(_mainCameraController.selectCamera(0, true));
|
unawaited(_mainCameraController.selectCamera(0, true));
|
||||||
unawaited(_initAsync());
|
unawaited(_initAsync());
|
||||||
|
|
||||||
handleIntentUrl(
|
|
||||||
context,
|
|
||||||
Uri.parse(
|
|
||||||
'https://me.twonly.eu/qr/#EAAauAEIgLDN0Nm7oKh0EghoYWhoaGhoaBohBRZQ8w_zpm1v7SRTdc8GEOMAxuf1caGDlBa-v0ZiTw9qIiEF05juEs1c3yw0STiSwQR7lowDX5hBaxN4YFR0HhkopGIoudTO5wIyQFQRtU1aO7P7O5s2ekB1ppAost3iQQizwhFObjOLgHQnpwcnwEONXZzSADYqCeEoNcvyE45w0v21z1Imhozk3Q44oI0GQhA9U_chIJwwZ7J9fpeXODZF',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Subscribe to all events (initial link and further)
|
// Subscribe to all events (initial link and further)
|
||||||
_deepLinkSub = AppLinks().uriLinkStream.listen((uri) async {
|
_deepLinkSub = AppLinks().uriLinkStream.listen((uri) async {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:restart_app/restart_app.dart';
|
import 'package:restart_app/restart_app.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
|
||||||
import 'package:twonly/src/model/json/userdata.model.dart';
|
import 'package:twonly/src/model/json/userdata.model.dart';
|
||||||
import 'package:twonly/src/services/backup/restore.backup.dart';
|
import 'package:twonly/src/services/backup/restore.backup.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||||
import 'package:twonly/src/visual/decorations/input_text.decoration.dart';
|
import 'package:twonly/src/visual/decorations/input_text.decoration.dart';
|
||||||
|
import 'package:twonly/src/visual/views/settings/backup/backup_server.view.dart';
|
||||||
|
|
||||||
class BackupRecoveryView extends StatefulWidget {
|
class BackupRecoveryView extends StatefulWidget {
|
||||||
const BackupRecoveryView({super.key});
|
const BackupRecoveryView({super.key});
|
||||||
|
|
@ -140,9 +139,11 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
|
||||||
Center(
|
Center(
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
backupServer = await context.push(
|
backupServer =
|
||||||
Routes.settingsBackupServer,
|
await context.navPush(
|
||||||
);
|
const BackupServerView(),
|
||||||
|
)
|
||||||
|
as BackupServer?;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: Text(context.lang.backupExpertSettings),
|
child: Text(context.lang.backupExpertSettings),
|
||||||
|
|
|
||||||
|
|
@ -264,15 +264,8 @@ $debugLogToken
|
||||||
final fullMessage = await _getFeedbackText();
|
final fullMessage = await _getFeedbackText();
|
||||||
if (!context.mounted || fullMessage == null) return;
|
if (!context.mounted || fullMessage == null) return;
|
||||||
|
|
||||||
final feedbackSend = await Navigator.push(
|
final feedbackSend = await context.navPush(
|
||||||
context,
|
SubmitMessage(fullMessage: fullMessage),
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return SubmitMessage(
|
|
||||||
fullMessage: fullMessage,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (feedbackSend == true && context.mounted) {
|
if (feedbackSend == true && context.mounted) {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import 'package:twonly/src/constants/secure_storage.keys.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart';
|
||||||
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
||||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/secure_storage.dart';
|
import 'package:twonly/src/utils/secure_storage.dart';
|
||||||
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||||
|
|
@ -56,21 +55,18 @@ class _NotificationViewState extends State<NotificationView> {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (run) {
|
if (run) {
|
||||||
final user = await getUser();
|
final pushData = await encryptPushNotification(
|
||||||
if (user != null) {
|
userService.currentUser.userId,
|
||||||
final pushData = await encryptPushNotification(
|
PushNotification(
|
||||||
user.userId,
|
messageId: uuid.v4(),
|
||||||
PushNotification(
|
kind: PushKind.TEST_NOTIFICATION,
|
||||||
messageId: uuid.v4(),
|
),
|
||||||
kind: PushKind.TEST_NOTIFICATION,
|
);
|
||||||
),
|
await apiService.sendTextMessage(
|
||||||
);
|
userService.currentUser.userId,
|
||||||
await apiService.sendTextMessage(
|
Uint8List(0),
|
||||||
user.userId,
|
pushData,
|
||||||
Uint8List(0),
|
);
|
||||||
pushData,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_troubleshootingDidRun = true;
|
_troubleshootingDidRun = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue