twonly-app/lib/app.dart

237 lines
6.5 KiB
Dart
Raw Normal View History

2025-07-14 22:38:23 +00:00
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
2025-01-25 12:17:10 +00:00
import 'package:provider/provider.dart';
2025-02-09 17:02:52 +00:00
import 'package:twonly/globals.dart';
2025-04-21 19:32:18 +00:00
import 'package:twonly/src/localization/generated/app_localizations.dart';
2025-05-30 15:24:02 +00:00
import 'package:twonly/src/providers/connection.provider.dart';
2025-12-20 01:14:01 +00:00
import 'package:twonly/src/providers/purchases.provider.dart';
2026-02-09 22:45:34 +00:00
import 'package:twonly/src/providers/routing.provider.dart';
2025-05-30 15:24:02 +00:00
import 'package:twonly/src/providers/settings.provider.dart';
2025-11-06 17:40:22 +00:00
import 'package:twonly/src/services/subscription.service.dart';
2026-02-09 22:45:34 +00:00
import 'package:twonly/src/themes/dark.dart';
import 'package:twonly/src/themes/light.dart';
2025-11-06 19:50:22 +00:00
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/pow.dart';
2025-01-24 14:25:44 +00:00
import 'package:twonly/src/utils/storage.dart';
2025-07-14 22:38:23 +00:00
import 'package:twonly/src/views/components/app_outdated.dart';
2025-05-30 15:24:02 +00:00
import 'package:twonly/src/views/home.view.dart';
2025-07-14 22:38:23 +00:00
import 'package:twonly/src/views/onboarding/onboarding.view.dart';
2025-06-19 08:22:16 +00:00
import 'package:twonly/src/views/onboarding/register.view.dart';
2026-02-09 22:45:34 +00:00
import 'package:twonly/src/views/settings/backup/setup_backup.view.dart';
2026-04-10 14:34:16 +00:00
import 'package:twonly/src/views/unlock_twonly.view.dart';
2024-12-31 08:50:53 +00:00
2025-04-21 19:32:18 +00:00
class App extends StatefulWidget {
const App({super.key});
2024-12-31 08:50:53 +00:00
@override
2025-04-21 19:32:18 +00:00
State<App> createState() => _AppState();
2024-12-31 08:50:53 +00:00
}
2025-04-21 19:32:18 +00:00
class _AppState extends State<App> with WidgetsBindingObserver {
2025-02-09 20:01:01 +00:00
bool wasPaused = false;
2025-01-20 23:30:57 +00:00
@override
void initState() {
2025-01-20 23:30:57 +00:00
super.initState();
2025-02-09 20:01:01 +00:00
globalIsAppInBackground = false;
2025-02-08 22:07:36 +00:00
WidgetsBinding.instance.addObserver(this);
2025-01-25 12:17:10 +00:00
globalCallbackConnectionState = ({required isConnected}) async {
2026-04-10 14:34:16 +00:00
await context.read<CustomChangeProvider>().updateConnectionState(
isConnected,
);
2025-10-14 12:16:55 +00:00
await setUserPlan();
2025-01-27 00:43:52 +00:00
};
globalCallbackUpdatePlan = (plan) {
2025-12-20 01:14:01 +00:00
context.read<PurchasesProvider>().updatePlan(plan);
2025-07-18 12:22:34 +00:00
};
unawaited(initAsync());
2025-02-09 20:01:01 +00:00
}
2025-07-14 22:38:23 +00:00
Future<void> setUserPlan() async {
2025-05-07 21:39:39 +00:00
final user = await getUser();
2025-05-30 16:04:04 +00:00
if (user != null && mounted) {
if (mounted) {
2025-12-20 01:14:01 +00:00
context.read<PurchasesProvider>().updatePlan(
2026-04-10 14:34:16 +00:00
planFromString(user.subscriptionPlan),
);
2025-05-30 16:04:04 +00:00
}
2025-05-07 21:39:39 +00:00
}
2025-02-09 17:02:52 +00:00
}
2025-07-14 22:38:23 +00:00
Future<void> initAsync() async {
await setUserPlan();
2025-12-21 02:33:20 +00:00
await apiService.connect();
2025-07-14 22:38:23 +00:00
await apiService.listenToNetworkChanges();
2025-05-08 19:37:14 +00:00
}
2025-02-08 22:07:36 +00:00
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
2025-02-09 20:01:01 +00:00
if (wasPaused) {
globalIsAppInBackground = false;
2025-05-30 15:24:02 +00:00
twonlyDB.markUpdated();
2025-12-21 02:33:20 +00:00
unawaited(apiService.connect());
2025-02-09 20:01:01 +00:00
}
2025-02-09 17:02:52 +00:00
} else if (state == AppLifecycleState.paused) {
2025-02-09 20:01:01 +00:00
wasPaused = true;
globalIsAppInBackground = true;
2025-02-08 22:07:36 +00:00
}
}
2025-01-27 19:30:41 +00:00
@override
void dispose() {
2025-02-08 22:07:36 +00:00
WidgetsBinding.instance.removeObserver(this);
globalCallbackConnectionState = ({required isConnected}) {};
globalCallbackUpdatePlan = (planId) {};
2025-01-27 19:30:41 +00:00
super.dispose();
}
2024-12-31 08:50:53 +00:00
@override
Widget build(BuildContext context) {
return ListenableBuilder(
2025-02-02 22:19:43 +00:00
listenable: context.watch<SettingsChangeProvider>(),
builder: (context, child) {
2026-02-09 22:45:34 +00:00
return MaterialApp.router(
routerConfig: routerProvider,
2025-12-26 21:08:39 +00:00
scaffoldMessengerKey: globalRootScaffoldMessengerKey,
2025-01-19 22:03:37 +00:00
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
2026-02-09 22:45:34 +00:00
debugShowCheckedModeBanner: false,
2024-12-31 08:50:53 +00:00
supportedLocales: const [
2025-01-19 22:03:37 +00:00
Locale('en', ''),
Locale('de', ''),
2024-12-31 08:50:53 +00:00
],
2026-02-09 22:45:34 +00:00
title: 'twonly',
theme: lightTheme,
darkTheme: darkTheme,
2025-02-02 22:19:43 +00:00
themeMode: context.watch<SettingsChangeProvider>().themeMode,
2024-12-31 08:50:53 +00:00
);
},
);
}
}
2025-02-09 20:01:01 +00:00
2025-04-21 19:32:18 +00:00
class AppMainWidget extends StatefulWidget {
2025-07-14 22:38:23 +00:00
const AppMainWidget({
required this.initialPage,
2025-07-15 11:38:38 +00:00
super.key,
2025-07-14 22:38:23 +00:00
});
2025-02-09 20:01:01 +00:00
final int initialPage;
@override
2025-04-21 19:32:18 +00:00
State<AppMainWidget> createState() => _AppMainWidgetState();
2025-02-09 20:01:01 +00:00
}
2025-04-21 19:32:18 +00:00
class _AppMainWidgetState extends State<AppMainWidget> {
2025-10-25 10:00:40 +00:00
bool _isUserCreated = false;
bool _showDatabaseMigration = false;
bool _showOnboarding = true;
bool _isLoaded = false;
2025-12-15 18:12:25 +00:00
bool _skipBackup = false;
2026-04-10 14:34:16 +00:00
bool _isTwonlyLocked = true;
2025-12-26 08:07:41 +00:00
int _initialPage = 0;
2025-10-25 10:00:40 +00:00
2025-11-08 11:26:17 +00:00
(Future<int>?, bool) _proofOfWork = (null, false);
2025-11-06 19:50:22 +00:00
2025-10-25 10:00:40 +00:00
@override
void initState() {
2025-12-26 08:07:41 +00:00
_initialPage = widget.initialPage;
2025-10-25 10:00:40 +00:00
initAsync();
super.initState();
}
Future<void> initAsync() async {
_isUserCreated = await isUserCreated();
2025-10-25 23:14:46 +00:00
if (_isUserCreated) {
2026-04-10 14:34:16 +00:00
if (_isTwonlyLocked) {
// do not change in case twonly was already unlocked at some point
_isTwonlyLocked = gUser.screenLockEnabled;
}
2025-10-25 23:14:46 +00:00
if (gUser.appVersion < 62) {
_showDatabaseMigration = true;
}
2025-12-26 08:07:41 +00:00
if (!gUser.startWithCameraOpen) {
_initialPage = 0;
}
2025-10-25 23:14:46 +00:00
}
2025-11-06 19:50:22 +00:00
if (!_isUserCreated && !_showDatabaseMigration) {
// This means the user is in the onboarding screen, so start with the Proof of Work.
2025-11-08 11:26:17 +00:00
final (proof, disabled) = await apiService.getProofOfWork();
2025-11-06 19:50:22 +00:00
if (proof != null) {
Log.info('Starting with proof of work calculation.');
// Starting with the proof of work.
2026-04-10 14:34:16 +00:00
_proofOfWork = (
calculatePoW(proof.prefix, proof.difficulty.toInt()),
false,
);
2025-11-08 11:26:17 +00:00
} else {
_proofOfWork = (null, disabled);
2025-11-06 19:50:22 +00:00
}
}
2025-10-25 10:00:40 +00:00
setState(() {
_isLoaded = true;
});
}
2025-02-09 20:01:01 +00:00
@override
Widget build(BuildContext context) {
2025-10-25 10:00:40 +00:00
if (!_isLoaded) {
return Center(child: Container());
}
late Widget child;
if (_showDatabaseMigration) {
2026-03-01 03:18:43 +00:00
child = const Center(child: Text('Please reinstall twonly.'));
2025-10-25 10:00:40 +00:00
} else if (_isUserCreated) {
2026-04-10 14:34:16 +00:00
if (_isTwonlyLocked) {
child = UnlockTwonlyView(
callbackOnSuccess: () => setState(() {
_isTwonlyLocked = false;
}),
);
} else if (gUser.twonlySafeBackup == null && !_skipBackup) {
2026-02-09 22:45:34 +00:00
child = SetupBackupView(
2025-10-29 10:13:13 +00:00
callBack: () {
2025-12-15 18:12:25 +00:00
_skipBackup = true;
2025-10-29 10:13:13 +00:00
setState(() {});
},
);
} else {
child = HomeView(
2025-12-26 08:07:41 +00:00
initialPage: _initialPage,
2025-10-29 10:13:13 +00:00
);
}
2025-10-25 10:00:40 +00:00
} else if (_showOnboarding) {
child = OnboardingView(
callbackOnSuccess: () => setState(() {
_showOnboarding = false;
}),
);
} else {
child = RegisterView(
callbackOnSuccess: initAsync,
2025-11-06 19:50:22 +00:00
proofOfWork: _proofOfWork,
2025-10-25 10:00:40 +00:00
);
}
2025-02-09 20:01:01 +00:00
return Stack(
children: [
2025-10-25 10:00:40 +00:00
child,
2025-07-15 11:38:38 +00:00
const AppOutdated(),
2025-02-09 20:01:01 +00:00
],
);
}
}