rename global variables into app state

This commit is contained in:
otsmr 2026-04-21 02:40:12 +02:00
parent 715774bd7f
commit e945e30991
13 changed files with 82 additions and 75 deletions

View file

@ -32,7 +32,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
globalIsAppInBackground = false; AppState.isAppInBackground = false;
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
unawaited(initAsync()); unawaited(initAsync());
@ -54,13 +54,13 @@ class _AppState extends State<App> with WidgetsBindingObserver {
super.didChangeAppLifecycleState(state); super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) { if (state == AppLifecycleState.resumed) {
if (wasPaused) { if (wasPaused) {
globalIsAppInBackground = false; AppState.isAppInBackground = false;
twonlyDB.markUpdated(); twonlyDB.markUpdated();
unawaited(apiService.connect()); unawaited(apiService.connect());
} }
} else if (state == AppLifecycleState.paused) { } else if (state == AppLifecycleState.paused) {
wasPaused = true; wasPaused = true;
globalIsAppInBackground = true; AppState.isAppInBackground = true;
} }
} }
@ -77,7 +77,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
builder: (context, child) { builder: (context, child) {
return MaterialApp.router( return MaterialApp.router(
routerConfig: routerProvider, routerConfig: routerProvider,
scaffoldMessengerKey: globalRootScaffoldMessengerKey, scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey,
localizationsDelegates: const [ localizationsDelegates: const [
AppLocalizations.delegate, AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,

View file

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -17,6 +19,18 @@ class AppEnvironment {
} }
} }
class AppState {
static bool isAppInBackground = true;
static bool isInBackgroundTask = false;
static bool allowErrorTrackingViaSentry = false;
static bool gotMessageFromServer = false;
}
class AppGlobalKeys {
static final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();
}
late ApiService apiService; late ApiService apiService;
// uses for background notification // uses for background notification
@ -26,20 +40,4 @@ late TwonlyDB twonlyDB;
// which will update this global variable. The variable is set in the main.dart and after the user has registered in the register.view.dart // which will update this global variable. The variable is set in the main.dart and after the user has registered in the register.view.dart
late UserData gUser; late UserData gUser;
// The following global function can be called from anywhere to update final userDataUpdateController = StreamController<void>.broadcast();
// the UI when something changed. The callbacks will be set by
// App widget.
// This callback called by the apiProvider
void Function() globalCallbackAppIsOutdated = () {};
void Function() globalCallbackNewDeviceRegistered = () {};
Map<String, VoidCallback> globalUserDataChangedCallBack = {};
bool globalIsAppInBackground = true;
bool globalIsInBackgroundTask = false;
bool globalAllowErrorTrackingViaSentry = false;
bool globalGotMessageFromServer = false;
final GlobalKey<ScaffoldMessengerState> globalRootScaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();

View file

@ -63,7 +63,7 @@ void main() async {
gUser = user; gUser = user;
if (user.allowErrorTrackingViaSentry) { if (user.allowErrorTrackingViaSentry) {
globalAllowErrorTrackingViaSentry = true; AppState.allowErrorTrackingViaSentry = true;
await SentryFlutter.init( await SentryFlutter.init(
(options) => options (options) => options
..dsn = ..dsn =

View file

@ -64,6 +64,12 @@ class ApiService {
final _connectionStateController = StreamController<bool>.broadcast(); final _connectionStateController = StreamController<bool>.broadcast();
Stream<bool> get onConnectionStateUpdated => _connectionStateController.stream; Stream<bool> get onConnectionStateUpdated => _connectionStateController.stream;
final _appOutdatedController = StreamController<void>.broadcast();
Stream<void> get onAppOutdated => _appOutdatedController.stream;
final _newDeviceRegisteredController = StreamController<void>.broadcast();
Stream<void> get onNewDeviceRegistered => _newDeviceRegisteredController.stream;
bool appIsOutdated = false; bool appIsOutdated = false;
bool isAuthenticated = false; bool isAuthenticated = false;
@ -97,12 +103,12 @@ class ApiService {
await initFCMAfterAuthenticated(); await initFCMAfterAuthenticated();
_connectionStateController.add(true); _connectionStateController.add(true);
if (globalIsInBackgroundTask) { if (AppState.isInBackgroundTask) {
await retransmitRawBytes(); await retransmitRawBytes();
await retransmitAllMessages(); await retransmitAllMessages();
await reuploadMediaFiles(); await reuploadMediaFiles();
await tryDownloadAllMediaFiles(); await tryDownloadAllMediaFiles();
} else if (!globalIsAppInBackground) { } else if (!AppState.isAppInBackground) {
unawaited(retransmitRawBytes()); unawaited(retransmitRawBytes());
unawaited(retransmitAllMessages()); unawaited(retransmitAllMessages());
unawaited(tryDownloadAllMediaFiles()); unawaited(tryDownloadAllMediaFiles());
@ -140,7 +146,7 @@ class ApiService {
} }
Future<void> startReconnectionTimer() async { Future<void> startReconnectionTimer() async {
if (globalIsInBackgroundTask) return; if (AppState.isInBackgroundTask) return;
if (reconnectionTimer?.isActive ?? false) { if (reconnectionTimer?.isActive ?? false) {
return; return;
} }
@ -148,7 +154,7 @@ class ApiService {
reconnectionTimer = Timer(Duration(seconds: _reconnectionDelay), () async { reconnectionTimer = Timer(Duration(seconds: _reconnectionDelay), () async {
reconnectionTimer = null; reconnectionTimer = null;
// only try to reconnect in case the app is in the foreground // only try to reconnect in case the app is in the foreground
if (!globalIsAppInBackground) { if (!AppState.isAppInBackground) {
await connect(); await connect();
} }
}); });
@ -353,14 +359,14 @@ class ApiService {
Log.warn('Got error from server: ${res.error}'); Log.warn('Got error from server: ${res.error}');
} }
if (res.error == ErrorCode.AppVersionOutdated) { if (res.error == ErrorCode.AppVersionOutdated) {
globalCallbackAppIsOutdated(); _appOutdatedController.add(null);
Log.warn('App Version is OUTDATED.'); Log.warn('App Version is OUTDATED.');
appIsOutdated = true; appIsOutdated = true;
await close(() {}); await close(() {});
return Result.error(ErrorCode.InternalError); return Result.error(ErrorCode.InternalError);
} }
if (res.error == ErrorCode.NewDeviceRegistered) { if (res.error == ErrorCode.NewDeviceRegistered) {
globalCallbackNewDeviceRegistered(); _newDeviceRegisteredController.add(null);
Log.warn( Log.warn(
'Device is disabled, as a newer device restore twonly Backup.', 'Device is disabled, as a newer device restore twonly Backup.',
); );
@ -416,7 +422,7 @@ class ApiService {
..userId = Int64(userId) ..userId = Int64(userId)
..appVersion = (await PackageInfo.fromPlatform()).version ..appVersion = (await PackageInfo.fromPlatform()).version
..deviceId = Int64(user.deviceId) ..deviceId = Int64(user.deviceId)
..inBackground = globalIsInBackgroundTask ..inBackground = AppState.isInBackgroundTask
..authToken = base64Decode(apiAuthToken); ..authToken = base64Decode(apiAuthToken);
final handshake = Handshake()..authenticate = authenticate; final handshake = Handshake()..authenticate = authenticate;
@ -427,7 +433,7 @@ class ApiService {
if (result.isSuccess) { if (result.isSuccess) {
Log.info('websocket is authenticated'); Log.info('websocket is authenticated');
isAuthenticated = true; isAuthenticated = true;
if (globalIsInBackgroundTask) { if (AppState.isInBackgroundTask) {
await onAuthenticated(); await onAuthenticated();
} else { } else {
unawaited(onAuthenticated()); unawaited(onAuthenticated());

View file

@ -593,7 +593,7 @@ Future<void> _uploadUploadRequest(MediaFileService media) async {
final connectivityResult = await Connectivity().checkConnectivity(); final connectivityResult = await Connectivity().checkConnectivity();
if (globalIsInBackgroundTask || if (AppState.isInBackgroundTask ||
!connectivityResult.contains(ConnectivityResult.mobile) && !connectivityResult.contains(ConnectivityResult.mobile) &&
!connectivityResult.contains(ConnectivityResult.wifi)) { !connectivityResult.contains(ConnectivityResult.wifi)) {
// no internet, directly put it into the background... // no internet, directly put it into the background...

View file

@ -66,7 +66,7 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
..response = response; ..response = response;
await apiService.sendResponse(ClientToServer()..v0 = v0); await apiService.sendResponse(ClientToServer()..v0 = v0);
globalGotMessageFromServer = true; AppState.gotMessageFromServer = true;
}); });
} }

View file

@ -73,7 +73,7 @@ Future<bool> initBackgroundExecution() async {
twonlyDB = TwonlyDB(); twonlyDB = TwonlyDB();
apiService = ApiService(); apiService = ApiService();
globalIsInBackgroundTask = true; AppState.isInBackgroundTask = true;
_isInitialized = true; _isInitialized = true;
return true; return true;
@ -124,7 +124,7 @@ Future<void> handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async {
return; return;
} }
while (!globalGotMessageFromServer) { while (!AppState.gotMessageFromServer) {
if (stopwatch.elapsed.inSeconds >= 15) { if (stopwatch.elapsed.inSeconds >= 15) {
Log.info('No new message from the server after 15 seconds.'); Log.info('No new message from the server after 15 seconds.');
break; break;
@ -132,7 +132,7 @@ Future<void> handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async {
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
} }
if (globalGotMessageFromServer) { if (AppState.gotMessageFromServer) {
Log.info('Received a server message from the server.'); Log.info('Received a server message from the server.');
} }

View file

@ -139,7 +139,7 @@ Future<void> handleRemoteMessage(RemoteMessage message) async {
if (!Platform.isAndroid) { if (!Platform.isAndroid) {
Log.error('Got message in Dart while on iOS'); Log.error('Got message in Dart while on iOS');
} }
if (message.notification != null && globalIsAppInBackground) { if (message.notification != null && AppState.isAppInBackground) {
Log.error( Log.error(
'Got notification but app is in background, so the SDK already have shown the message.', 'Got notification but app is in background, so the SDK already have shown the message.',
); );

View file

@ -41,7 +41,7 @@ class Log {
StackTrace? stackTrace, StackTrace? stackTrace,
]) { ]) {
final message = filterLogMessage('$messageInput'); final message = filterLogMessage('$messageInput');
if (globalAllowErrorTrackingViaSentry) { if (AppState.allowErrorTrackingViaSentry) {
try { try {
throw Exception(message); throw Exception(message);
} catch (exception, stackTrace) { } catch (exception, stackTrace) {

View file

@ -72,13 +72,8 @@ Future<UserData?> updateUserdata(
gUser = updated; gUser = updated;
return updated; return updated;
}); });
try { userDataUpdateController.add(null);
for (final callBack in globalUserDataChangedCallBack.values) {
callBack();
}
} catch (e) {
Log.error(e);
}
return userData; return userData;
} }

View file

@ -379,10 +379,10 @@ class MainCameraController {
} }
await HapticFeedback.heavyImpact(); await HapticFeedback.heavyImpact();
if (verificationOk) { if (verificationOk) {
globalRootScaffoldMessengerKey.currentState?.showSnackBar( AppGlobalKeys.scaffoldMessengerKey.currentState?.showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(
globalRootScaffoldMessengerKey.currentContext?.lang AppGlobalKeys.scaffoldMessengerKey.currentContext?.lang
.verifiedPublicKey( .verifiedPublicKey(
getContactDisplayName(contact), getContactDisplayName(contact),
) ?? ) ??

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -17,28 +18,36 @@ class _AppOutdatedState extends State<AppOutdated> {
bool appIsOutdated = false; bool appIsOutdated = false;
bool newDeviceRegistered = false; bool newDeviceRegistered = false;
late StreamSubscription<void> _subOutdated;
late StreamSubscription<void> _subNewDevice;
@override @override
void dispose() { void dispose() {
globalCallbackAppIsOutdated = () {}; _subOutdated.cancel();
globalCallbackNewDeviceRegistered = () {}; _subNewDevice.cancel();
super.dispose(); super.dispose();
} }
@override @override
void initState() { void initState() {
globalCallbackAppIsOutdated = () async { super.initState();
_subOutdated = apiService.onAppOutdated.listen((_) async {
if (mounted) {
await context.read<CustomChangeProvider>().updateConnectionState(false); await context.read<CustomChangeProvider>().updateConnectionState(false);
setState(() { setState(() {
appIsOutdated = true; appIsOutdated = true;
}); });
}; }
globalCallbackNewDeviceRegistered = () async { });
_subNewDevice = apiService.onNewDeviceRegistered.listen((_) async {
if (mounted) {
await context.read<CustomChangeProvider>().updateConnectionState(false); await context.read<CustomChangeProvider>().updateConnectionState(false);
setState(() { setState(() {
newDeviceRegistered = true; newDeviceRegistered = true;
}); });
}; }
super.initState(); });
} }
@override @override

View file

@ -28,12 +28,12 @@ class AvatarIcon extends StatefulWidget {
class _AvatarIconState extends State<AvatarIcon> { class _AvatarIconState extends State<AvatarIcon> {
List<Contact> _avatarContacts = []; List<Contact> _avatarContacts = [];
String? _globalUserDataCallBackId;
String? _avatarSvg; String? _avatarSvg;
StreamSubscription<List<Contact>>? groupStream; StreamSubscription<List<Contact>>? groupStream;
StreamSubscription<List<Contact>>? contactsStream; StreamSubscription<List<Contact>>? contactsStream;
StreamSubscription<Contact?>? contactStream; StreamSubscription<Contact?>? contactStream;
StreamSubscription<void>? _userDataSub;
@override @override
void initState() { void initState() {
@ -46,9 +46,7 @@ class _AvatarIconState extends State<AvatarIcon> {
groupStream?.cancel(); groupStream?.cancel();
contactStream?.cancel(); contactStream?.cancel();
contactsStream?.cancel(); contactsStream?.cancel();
if (_globalUserDataCallBackId != null) { _userDataSub?.cancel();
globalUserDataChangedCallBack.remove(_globalUserDataCallBackId);
}
super.dispose(); super.dispose();
} }
@ -95,8 +93,8 @@ class _AvatarIconState extends State<AvatarIcon> {
setState(() {}); setState(() {});
}); });
} else if (widget.myAvatar) { } else if (widget.myAvatar) {
_globalUserDataCallBackId = 'avatar_${getRandomString(10)}'; _userDataSub = userDataUpdateController.stream.listen((_) {
globalUserDataChangedCallBack[_globalUserDataCallBackId!] = () { if (mounted) {
setState(() { setState(() {
if (gUser.avatarSvg != null) { if (gUser.avatarSvg != null) {
_avatarSvg = gUser.avatarSvg; _avatarSvg = gUser.avatarSvg;
@ -104,7 +102,8 @@ class _AvatarIconState extends State<AvatarIcon> {
_avatarContacts = []; _avatarContacts = [];
} }
}); });
}; }
});
if (gUser.avatarSvg != null) { if (gUser.avatarSvg != null) {
_avatarSvg = gUser.avatarSvg; _avatarSvg = gUser.avatarSvg;
} }