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
void initState() {
super.initState();
globalIsAppInBackground = false;
AppState.isAppInBackground = false;
WidgetsBinding.instance.addObserver(this);
unawaited(initAsync());
@ -54,13 +54,13 @@ class _AppState extends State<App> 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<App> with WidgetsBindingObserver {
builder: (context, child) {
return MaterialApp.router(
routerConfig: routerProvider,
scaffoldMessengerKey: globalRootScaffoldMessengerKey,
scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,

View file

@ -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<ScaffoldMessengerState> scaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();
}
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<String, VoidCallback> globalUserDataChangedCallBack = {};
bool globalIsAppInBackground = true;
bool globalIsInBackgroundTask = false;
bool globalAllowErrorTrackingViaSentry = false;
bool globalGotMessageFromServer = false;
final GlobalKey<ScaffoldMessengerState> globalRootScaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();
final userDataUpdateController = StreamController<void>.broadcast();

View file

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

View file

@ -64,6 +64,12 @@ class ApiService {
final _connectionStateController = StreamController<bool>.broadcast();
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 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<void> 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());

View file

@ -593,9 +593,9 @@ Future<void> _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);

View file

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

View file

@ -73,7 +73,7 @@ Future<bool> initBackgroundExecution() async {
twonlyDB = TwonlyDB();
apiService = ApiService();
globalIsInBackgroundTask = true;
AppState.isInBackgroundTask = true;
_isInitialized = true;
return true;
@ -124,7 +124,7 @@ Future<void> 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<void> 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.');
}

View file

@ -139,7 +139,7 @@ Future<void> 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.',
);

View file

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

View file

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

View file

@ -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),
) ??

View file

@ -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<AppOutdated> {
bool appIsOutdated = false;
bool newDeviceRegistered = false;
late StreamSubscription<void> _subOutdated;
late StreamSubscription<void> _subNewDevice;
@override
void dispose() {
globalCallbackAppIsOutdated = () {};
globalCallbackNewDeviceRegistered = () {};
_subOutdated.cancel();
_subNewDevice.cancel();
super.dispose();
}
@override
void initState() {
globalCallbackAppIsOutdated = () async {
await context.read<CustomChangeProvider>().updateConnectionState(false);
setState(() {
appIsOutdated = true;
});
};
globalCallbackNewDeviceRegistered = () async {
await context.read<CustomChangeProvider>().updateConnectionState(false);
setState(() {
newDeviceRegistered = true;
});
};
super.initState();
_subOutdated = apiService.onAppOutdated.listen((_) async {
if (mounted) {
await context.read<CustomChangeProvider>().updateConnectionState(false);
setState(() {
appIsOutdated = true;
});
}
});
_subNewDevice = apiService.onNewDeviceRegistered.listen((_) async {
if (mounted) {
await context.read<CustomChangeProvider>().updateConnectionState(false);
setState(() {
newDeviceRegistered = true;
});
}
});
}
@override

View file

@ -28,12 +28,12 @@ class AvatarIcon extends StatefulWidget {
class _AvatarIconState extends State<AvatarIcon> {
List<Contact> _avatarContacts = [];
String? _globalUserDataCallBackId;
String? _avatarSvg;
StreamSubscription<List<Contact>>? groupStream;
StreamSubscription<List<Contact>>? contactsStream;
StreamSubscription<Contact?>? contactStream;
StreamSubscription<void>? _userDataSub;
@override
void initState() {
@ -46,9 +46,7 @@ class _AvatarIconState extends State<AvatarIcon> {
groupStream?.cancel();
contactStream?.cancel();
contactsStream?.cancel();
if (_globalUserDataCallBackId != null) {
globalUserDataChangedCallBack.remove(_globalUserDataCallBackId);
}
_userDataSub?.cancel();
super.dispose();
}
@ -95,16 +93,17 @@ class _AvatarIconState extends State<AvatarIcon> {
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;
}