From e945e30991a4a792aca732626c1cb9e3b7618091 Mon Sep 17 00:00:00 2001 From: otsmr Date: Tue, 21 Apr 2026 02:40:12 +0200 Subject: [PATCH] rename global variables into app state --- lib/app.dart | 8 ++-- lib/globals.dart | 32 ++++++++-------- lib/main.dart | 2 +- lib/src/services/api.service.dart | 22 +++++++---- .../api/mediafiles/upload.service.dart | 4 +- lib/src/services/api/server_messages.dart | 2 +- .../callback_dispatcher.background.dart | 6 +-- .../notifications/fcm.notifications.dart | 2 +- lib/src/utils/log.dart | 2 +- lib/src/utils/storage.dart | 9 +---- .../main_camera_controller.dart | 4 +- lib/src/views/components/app_outdated.dart | 37 ++++++++++++------- .../components/avatar_icon.component.dart | 27 +++++++------- 13 files changed, 82 insertions(+), 75 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index e6091da9..eb9a749f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -32,7 +32,7 @@ class _AppState extends State with WidgetsBindingObserver { @override void initState() { super.initState(); - globalIsAppInBackground = false; + AppState.isAppInBackground = false; WidgetsBinding.instance.addObserver(this); unawaited(initAsync()); @@ -54,13 +54,13 @@ class _AppState extends State with WidgetsBindingObserver { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.resumed) { if (wasPaused) { - globalIsAppInBackground = false; + AppState.isAppInBackground = false; twonlyDB.markUpdated(); unawaited(apiService.connect()); } } else if (state == AppLifecycleState.paused) { wasPaused = true; - globalIsAppInBackground = true; + AppState.isAppInBackground = true; } } @@ -77,7 +77,7 @@ class _AppState extends State with WidgetsBindingObserver { builder: (context, child) { return MaterialApp.router( routerConfig: routerProvider, - scaffoldMessengerKey: globalRootScaffoldMessengerKey, + scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey, localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, diff --git a/lib/globals.dart b/lib/globals.dart index 68b756a1..fc2035e9 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:camera/camera.dart'; import 'package:flutter/material.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 scaffoldMessengerKey = + GlobalKey(); +} + late ApiService apiService; // 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 late UserData gUser; -// The following global function can be called from anywhere to update -// 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 globalUserDataChangedCallBack = {}; - -bool globalIsAppInBackground = true; -bool globalIsInBackgroundTask = false; -bool globalAllowErrorTrackingViaSentry = false; -bool globalGotMessageFromServer = false; - -final GlobalKey globalRootScaffoldMessengerKey = - GlobalKey(); +final userDataUpdateController = StreamController.broadcast(); diff --git a/lib/main.dart b/lib/main.dart index e09651bf..80e9addd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -63,7 +63,7 @@ void main() async { gUser = user; if (user.allowErrorTrackingViaSentry) { - globalAllowErrorTrackingViaSentry = true; + AppState.allowErrorTrackingViaSentry = true; await SentryFlutter.init( (options) => options ..dsn = diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 7e47f6f2..b217a254 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -64,6 +64,12 @@ class ApiService { final _connectionStateController = StreamController.broadcast(); Stream get onConnectionStateUpdated => _connectionStateController.stream; + final _appOutdatedController = StreamController.broadcast(); + Stream get onAppOutdated => _appOutdatedController.stream; + + final _newDeviceRegisteredController = StreamController.broadcast(); + Stream get onNewDeviceRegistered => _newDeviceRegisteredController.stream; + bool appIsOutdated = false; bool isAuthenticated = false; @@ -97,12 +103,12 @@ class ApiService { await initFCMAfterAuthenticated(); _connectionStateController.add(true); - if (globalIsInBackgroundTask) { + if (AppState.isInBackgroundTask) { await retransmitRawBytes(); await retransmitAllMessages(); await reuploadMediaFiles(); await tryDownloadAllMediaFiles(); - } else if (!globalIsAppInBackground) { + } else if (!AppState.isAppInBackground) { unawaited(retransmitRawBytes()); unawaited(retransmitAllMessages()); unawaited(tryDownloadAllMediaFiles()); @@ -140,7 +146,7 @@ class ApiService { } Future startReconnectionTimer() async { - if (globalIsInBackgroundTask) return; + if (AppState.isInBackgroundTask) return; if (reconnectionTimer?.isActive ?? false) { return; } @@ -148,7 +154,7 @@ class ApiService { reconnectionTimer = Timer(Duration(seconds: _reconnectionDelay), () async { reconnectionTimer = null; // only try to reconnect in case the app is in the foreground - if (!globalIsAppInBackground) { + if (!AppState.isAppInBackground) { await connect(); } }); @@ -353,14 +359,14 @@ class ApiService { Log.warn('Got error from server: ${res.error}'); } if (res.error == ErrorCode.AppVersionOutdated) { - globalCallbackAppIsOutdated(); + _appOutdatedController.add(null); Log.warn('App Version is OUTDATED.'); appIsOutdated = true; await close(() {}); return Result.error(ErrorCode.InternalError); } if (res.error == ErrorCode.NewDeviceRegistered) { - globalCallbackNewDeviceRegistered(); + _newDeviceRegisteredController.add(null); Log.warn( 'Device is disabled, as a newer device restore twonly Backup.', ); @@ -416,7 +422,7 @@ class ApiService { ..userId = Int64(userId) ..appVersion = (await PackageInfo.fromPlatform()).version ..deviceId = Int64(user.deviceId) - ..inBackground = globalIsInBackgroundTask + ..inBackground = AppState.isInBackgroundTask ..authToken = base64Decode(apiAuthToken); final handshake = Handshake()..authenticate = authenticate; @@ -427,7 +433,7 @@ class ApiService { if (result.isSuccess) { Log.info('websocket is authenticated'); isAuthenticated = true; - if (globalIsInBackgroundTask) { + if (AppState.isInBackgroundTask) { await onAuthenticated(); } else { unawaited(onAuthenticated()); diff --git a/lib/src/services/api/mediafiles/upload.service.dart b/lib/src/services/api/mediafiles/upload.service.dart index db485966..b45eb2a1 100644 --- a/lib/src/services/api/mediafiles/upload.service.dart +++ b/lib/src/services/api/mediafiles/upload.service.dart @@ -593,9 +593,9 @@ Future _uploadUploadRequest(MediaFileService media) async { final connectivityResult = await Connectivity().checkConnectivity(); - if (globalIsInBackgroundTask || + if (AppState.isInBackgroundTask || !connectivityResult.contains(ConnectivityResult.mobile) && - !connectivityResult.contains(ConnectivityResult.wifi)) { + !connectivityResult.contains(ConnectivityResult.wifi)) { // no internet, directly put it into the background... await FileDownloader().enqueue(task); await media.setUploadState(UploadState.backgroundUploadTaskStarted); diff --git a/lib/src/services/api/server_messages.dart b/lib/src/services/api/server_messages.dart index afd6b17b..2a1113c3 100644 --- a/lib/src/services/api/server_messages.dart +++ b/lib/src/services/api/server_messages.dart @@ -66,7 +66,7 @@ Future handleServerMessage(server.ServerToClient msg) async { ..response = response; await apiService.sendResponse(ClientToServer()..v0 = v0); - globalGotMessageFromServer = true; + AppState.gotMessageFromServer = true; }); } diff --git a/lib/src/services/background/callback_dispatcher.background.dart b/lib/src/services/background/callback_dispatcher.background.dart index e3828add..bfa87bce 100644 --- a/lib/src/services/background/callback_dispatcher.background.dart +++ b/lib/src/services/background/callback_dispatcher.background.dart @@ -73,7 +73,7 @@ Future initBackgroundExecution() async { twonlyDB = TwonlyDB(); apiService = ApiService(); - globalIsInBackgroundTask = true; + AppState.isInBackgroundTask = true; _isInitialized = true; return true; @@ -124,7 +124,7 @@ Future handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async { return; } - while (!globalGotMessageFromServer) { + while (!AppState.gotMessageFromServer) { if (stopwatch.elapsed.inSeconds >= 15) { Log.info('No new message from the server after 15 seconds.'); break; @@ -132,7 +132,7 @@ Future handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async { await Future.delayed(const Duration(milliseconds: 500)); } - if (globalGotMessageFromServer) { + if (AppState.gotMessageFromServer) { Log.info('Received a server message from the server.'); } diff --git a/lib/src/services/notifications/fcm.notifications.dart b/lib/src/services/notifications/fcm.notifications.dart index 89b70e4d..2ae14392 100644 --- a/lib/src/services/notifications/fcm.notifications.dart +++ b/lib/src/services/notifications/fcm.notifications.dart @@ -139,7 +139,7 @@ Future handleRemoteMessage(RemoteMessage message) async { if (!Platform.isAndroid) { Log.error('Got message in Dart while on iOS'); } - if (message.notification != null && globalIsAppInBackground) { + if (message.notification != null && AppState.isAppInBackground) { Log.error( 'Got notification but app is in background, so the SDK already have shown the message.', ); diff --git a/lib/src/utils/log.dart b/lib/src/utils/log.dart index a8be4bc3..70c92f98 100644 --- a/lib/src/utils/log.dart +++ b/lib/src/utils/log.dart @@ -41,7 +41,7 @@ class Log { StackTrace? stackTrace, ]) { final message = filterLogMessage('$messageInput'); - if (globalAllowErrorTrackingViaSentry) { + if (AppState.allowErrorTrackingViaSentry) { try { throw Exception(message); } catch (exception, stackTrace) { diff --git a/lib/src/utils/storage.dart b/lib/src/utils/storage.dart index 6d89be2c..60f3c15d 100644 --- a/lib/src/utils/storage.dart +++ b/lib/src/utils/storage.dart @@ -72,13 +72,8 @@ Future updateUserdata( gUser = updated; return updated; }); - try { - for (final callBack in globalUserDataChangedCallBack.values) { - callBack(); - } - } catch (e) { - Log.error(e); - } + userDataUpdateController.add(null); + return userData; } diff --git a/lib/src/views/camera/camera_preview_components/main_camera_controller.dart b/lib/src/views/camera/camera_preview_components/main_camera_controller.dart index b4fc0997..de80dba4 100644 --- a/lib/src/views/camera/camera_preview_components/main_camera_controller.dart +++ b/lib/src/views/camera/camera_preview_components/main_camera_controller.dart @@ -379,10 +379,10 @@ class MainCameraController { } await HapticFeedback.heavyImpact(); if (verificationOk) { - globalRootScaffoldMessengerKey.currentState?.showSnackBar( + AppGlobalKeys.scaffoldMessengerKey.currentState?.showSnackBar( SnackBar( content: Text( - globalRootScaffoldMessengerKey.currentContext?.lang + AppGlobalKeys.scaffoldMessengerKey.currentContext?.lang .verifiedPublicKey( getContactDisplayName(contact), ) ?? diff --git a/lib/src/views/components/app_outdated.dart b/lib/src/views/components/app_outdated.dart index ac50807a..efa2aac5 100644 --- a/lib/src/views/components/app_outdated.dart +++ b/lib/src/views/components/app_outdated.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -16,29 +17,37 @@ class AppOutdated extends StatefulWidget { class _AppOutdatedState extends State { bool appIsOutdated = false; bool newDeviceRegistered = false; + + late StreamSubscription _subOutdated; + late StreamSubscription _subNewDevice; @override void dispose() { - globalCallbackAppIsOutdated = () {}; - globalCallbackNewDeviceRegistered = () {}; + _subOutdated.cancel(); + _subNewDevice.cancel(); super.dispose(); } @override void initState() { - globalCallbackAppIsOutdated = () async { - await context.read().updateConnectionState(false); - setState(() { - appIsOutdated = true; - }); - }; - globalCallbackNewDeviceRegistered = () async { - await context.read().updateConnectionState(false); - setState(() { - newDeviceRegistered = true; - }); - }; super.initState(); + _subOutdated = apiService.onAppOutdated.listen((_) async { + if (mounted) { + await context.read().updateConnectionState(false); + setState(() { + appIsOutdated = true; + }); + } + }); + + _subNewDevice = apiService.onNewDeviceRegistered.listen((_) async { + if (mounted) { + await context.read().updateConnectionState(false); + setState(() { + newDeviceRegistered = true; + }); + } + }); } @override diff --git a/lib/src/views/components/avatar_icon.component.dart b/lib/src/views/components/avatar_icon.component.dart index f16e86cc..3ece858e 100644 --- a/lib/src/views/components/avatar_icon.component.dart +++ b/lib/src/views/components/avatar_icon.component.dart @@ -28,12 +28,12 @@ class AvatarIcon extends StatefulWidget { class _AvatarIconState extends State { List _avatarContacts = []; - String? _globalUserDataCallBackId; String? _avatarSvg; StreamSubscription>? groupStream; StreamSubscription>? contactsStream; StreamSubscription? contactStream; + StreamSubscription? _userDataSub; @override void initState() { @@ -46,9 +46,7 @@ class _AvatarIconState extends State { groupStream?.cancel(); contactStream?.cancel(); contactsStream?.cancel(); - if (_globalUserDataCallBackId != null) { - globalUserDataChangedCallBack.remove(_globalUserDataCallBackId); - } + _userDataSub?.cancel(); super.dispose(); } @@ -95,16 +93,17 @@ class _AvatarIconState extends State { setState(() {}); }); } else if (widget.myAvatar) { - _globalUserDataCallBackId = 'avatar_${getRandomString(10)}'; - globalUserDataChangedCallBack[_globalUserDataCallBackId!] = () { - setState(() { - if (gUser.avatarSvg != null) { - _avatarSvg = gUser.avatarSvg; - } else { - _avatarContacts = []; - } - }); - }; + _userDataSub = userDataUpdateController.stream.listen((_) { + if (mounted) { + setState(() { + if (gUser.avatarSvg != null) { + _avatarSvg = gUser.avatarSvg; + } else { + _avatarContacts = []; + } + }); + } + }); if (gUser.avatarSvg != null) { _avatarSvg = gUser.avatarSvg; }