From 3d35615136d82e38e1b320745570a901312f97b5 Mon Sep 17 00:00:00 2001 From: otsmr Date: Tue, 21 Apr 2026 03:34:52 +0200 Subject: [PATCH] refactor global user variable --- lib/app.dart | 6 +- lib/globals.dart | 11 +- lib/main.dart | 12 +- lib/src/database/daos/contacts.dao.dart | 2 +- lib/src/database/daos/groups.dao.dart | 4 +- lib/src/providers/purchases.provider.dart | 3 +- lib/src/services/api.service.dart | 14 +- .../api/client2client/groups.c2c.dart | 2 +- .../api/client2client/user_discovery.c2c.dart | 8 +- .../api/mediafiles/download.service.dart | 2 +- .../api/mediafiles/upload.service.dart | 2 +- lib/src/services/api/messages.dart | 16 +- lib/src/services/api/server_messages.dart | 4 +- .../callback_dispatcher.background.dart | 4 +- lib/src/services/backup/common.backup.dart | 48 +-- lib/src/services/backup/create.backup.dart | 37 +-- lib/src/services/backup/restore.backup.dart | 3 +- lib/src/services/flame.service.dart | 5 +- lib/src/services/group.services.dart | 18 +- lib/src/services/intent/links.intent.dart | 4 +- .../mediafiles/mediafile.service.dart | 2 +- .../notifications/fcm.notifications.dart | 11 +- lib/src/services/signal/identity.signal.dart | 21 +- lib/src/services/subscription.service.dart | 6 - lib/src/services/user_discovery.service.dart | 14 +- lib/src/utils/avatars.dart | 6 +- lib/src/utils/qr.dart | 4 +- lib/src/utils/storage.dart | 23 +- .../camera_preview_controller_view.dart | 8 +- .../main_camera_controller.dart | 4 +- .../share_image_contact_selection.view.dart | 2 +- .../views/camera/share_image_editor.view.dart | 3 +- lib/src/views/chats/add_new_user.view.dart | 2 +- lib/src/views/chats/chat_list.view.dart | 20 +- lib/src/views/chats/chat_messages.view.dart | 4 +- .../entries/chat_contacts.entry.dart | 4 +- .../message_input.dart | 2 +- .../reaction_buttons.component.dart | 4 +- .../components/avatar_icon.component.dart | 14 +- lib/src/views/components/flame.dart | 2 +- .../components/max_flame_list_title.dart | 8 +- lib/src/views/contact/contact.view.dart | 8 +- lib/src/views/groups/group.view.dart | 2 +- lib/src/views/home.view.dart | 2 +- .../memories/memories_photo_slider.view.dart | 2 +- lib/src/views/onboarding/register.view.dart | 2 +- lib/src/views/public_profile.view.dart | 6 +- lib/src/views/settings/appearance.view.dart | 97 +++--- .../views/settings/backup/backup.view.dart | 303 +++++++++--------- .../settings/backup/backup_server.view.dart | 10 +- .../settings/chat/chat_reactions.view.dart | 6 +- .../views/settings/data_and_storage.view.dart | 172 +++++----- .../settings/developer/developer.view.dart | 166 +++++----- .../views/settings/help/changelog.view.dart | 68 ++-- lib/src/views/settings/help/help.view.dart | 222 ++++++------- lib/src/views/settings/privacy.view.dart | 12 +- .../settings/privacy/user_discovery.view.dart | 11 +- .../user_discovery_disabled.component.dart | 5 +- .../user_discovery_enabled.component.dart | 11 +- .../user_discovery_settings.view.dart | 21 +- .../settings/profile/modify_avatar.view.dart | 12 +- .../views/settings/profile/profile.view.dart | 206 ++++++------ .../views/settings/settings_main.view.dart | 8 +- .../subscription/subscription.view.dart | 2 +- .../subscription.view.dart | 5 +- .../user_study_data_collection.dart | 12 +- .../user_study_questionnaire.view.dart | 3 +- .../user_study/user_study_welcome.view.dart | 7 +- 68 files changed, 864 insertions(+), 886 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index eb9a749f..4e4bb61b 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -131,9 +131,9 @@ class _AppMainWidgetState extends State { if (_isUserCreated) { if (_isTwonlyLocked) { // do not change in case twonly was already unlocked at some point - _isTwonlyLocked = gUser.screenLockEnabled; + _isTwonlyLocked = AppSession.currentUser.screenLockEnabled; } - if (gUser.appVersion < 62) { + if (AppSession.currentUser.appVersion < 62) { _showDatabaseMigration = true; } } @@ -176,7 +176,7 @@ class _AppMainWidgetState extends State { _isTwonlyLocked = false; }), ); - } else if (gUser.twonlySafeBackup == null && !_skipBackup) { + } else if (AppSession.currentUser.twonlySafeBackup == null && !_skipBackup) { child = SetupBackupView( callBack: () { _skipBackup = true; diff --git a/lib/globals.dart b/lib/globals.dart index fc2035e9..c1a3c02b 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -38,6 +38,13 @@ late TwonlyDB twonlyDB; // Cached UserData in the memory. Every time the user data is changed the `updateUserdata` function is called, // 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; +class AppSession { + static late UserData currentUser; -final userDataUpdateController = StreamController.broadcast(); + static final _userDataUpdateController = StreamController.broadcast(); + static Stream get onUserUpdated => _userDataUpdateController.stream; + + static void triggerUserUpdate() { + _userDataUpdateController.add(null); + } +} diff --git a/lib/main.dart b/lib/main.dart index 80e9addd..b48acf75 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -60,7 +60,7 @@ void main() async { } if (user != null) { - gUser = user; + AppSession.currentUser = user; if (user.allowErrorTrackingViaSentry) { AppState.allowErrorTrackingViaSentry = true; @@ -91,20 +91,18 @@ void main() async { twonlyDB = TwonlyDB(); if (user != null) { - if (gUser.appVersion < 90) { + if (AppSession.currentUser.appVersion < 90) { // BUG: Requested media files for reupload where not reuploaded because the wrong state... await twonlyDB.mediaFilesDao.updateAllRetransmissionUploadingState(); - await updateUserdata((u) { + await updateUser((u) { u.appVersion = 90; - return u; }); } - if (gUser.appVersion < 91) { + if (AppSession.currentUser.appVersion < 91) { // BUG: Requested media files for reupload where not reuploaded because the wrong state... await makeMigrationToVersion91(); - await updateUserdata((u) { + await updateUser((u) { u.appVersion = 91; - return u; }); } } diff --git a/lib/src/database/daos/contacts.dao.dart b/lib/src/database/daos/contacts.dao.dart index 2a37990e..9b34b3ef 100644 --- a/lib/src/database/daos/contacts.dao.dart +++ b/lib/src/database/daos/contacts.dao.dart @@ -140,7 +140,7 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { t.userDiscoveryVersion.isNotNull() & t.userDiscoveryExcluded.equals(false) & t.mediaSendCounter.isBiggerOrEqualValue( - gUser.minimumRequiredImagesExchanged, + AppSession.currentUser.minimumRequiredImagesExchanged, ), )) .watch(); diff --git a/lib/src/database/daos/groups.dao.dart b/lib/src/database/daos/groups.dao.dart index 7f408d2d..a66ccfcd 100644 --- a/lib/src/database/daos/groups.dao.dart +++ b/lib/src/database/daos/groups.dao.dart @@ -113,7 +113,7 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { int contactId, GroupsCompanion group, ) async { - final groupIdDirectChat = getUUIDforDirectChat(contactId, gUser.userId); + final groupIdDirectChat = getUUIDforDirectChat(contactId, AppSession.currentUser.userId); final insertGroup = group.copyWith( groupId: Value(groupIdDirectChat), isDirectChat: const Value(true), @@ -209,7 +209,7 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { } Stream watchDirectChat(int contactId) { - final groupId = getUUIDforDirectChat(contactId, gUser.userId); + final groupId = getUUIDforDirectChat(contactId, AppSession.currentUser.userId); return (select( groups, )..where((t) => t.groupId.equals(groupId))).watchSingleOrNull(); diff --git a/lib/src/providers/purchases.provider.dart b/lib/src/providers/purchases.provider.dart index c71685c1..fb2f7c86 100644 --- a/lib/src/providers/purchases.provider.dart +++ b/lib/src/providers/purchases.provider.dart @@ -174,9 +174,8 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin { // an ok authenticated which is processed in the apiProvider... if (res.isSuccess) { if (Platform.isAndroid) { - await updateUserdata((u) { + await updateUser((u) { u.subscriptionPlanIdStore = purchaseDetails.productID; - return u; }); } } diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index b217a254..d3c245d9 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -62,13 +62,15 @@ class ApiService { Stream get onPlanUpdated => _planUpdateController.stream; final _connectionStateController = StreamController.broadcast(); - Stream get onConnectionStateUpdated => _connectionStateController.stream; + Stream get onConnectionStateUpdated => + _connectionStateController.stream; final _appOutdatedController = StreamController.broadcast(); Stream get onAppOutdated => _appOutdatedController.stream; final _newDeviceRegisteredController = StreamController.broadcast(); - Stream get onNewDeviceRegistered => _newDeviceRegisteredController.stream; + Stream get onNewDeviceRegistered => + _newDeviceRegisteredController.stream; bool appIsOutdated = false; bool isAuthenticated = false; @@ -124,7 +126,7 @@ class ApiService { unawaited(UserDiscoveryService.checkForNewAnnouncedUsers()); - if (gUser.userStudyParticipantsToken != null) { + if (AppSession.currentUser.userStudyParticipantsToken != null) { // In case the user participates in the user study, call the handler after authenticated, to be sure there is a internet connection unawaited(handleUserStudyUpload()); } @@ -341,9 +343,8 @@ class ApiService { final ok = res.value as server.Response_Ok; if (ok.hasAuthenticated()) { final authenticated = ok.authenticated; - await updateUserdata((user) { + await updateUser((user) { user.subscriptionPlan = authenticated.plan; - return user; }); _planUpdateController.add(planFromString(authenticated.plan)); @@ -782,9 +783,8 @@ class ApiService { Future loadPlanBalance({bool useCache = true}) async { final ballance = await getPlanBallance(); if (ballance != null) { - await updateUserdata((u) { + await updateUser((u) { u.lastPlanBallance = ballance.writeToJson(); - return u; }); return ballance; } diff --git a/lib/src/services/api/client2client/groups.c2c.dart b/lib/src/services/api/client2client/groups.c2c.dart index 8c88bac6..1193b4cf 100644 --- a/lib/src/services/api/client2client/groups.c2c.dart +++ b/lib/src/services/api/client2client/groups.c2c.dart @@ -161,7 +161,7 @@ Future handleGroupUpdate( case GroupActionType.demoteToMember: int? affectedContactId = update.affectedContactId.toInt(); - if (affectedContactId == gUser.userId) { + if (affectedContactId == AppSession.currentUser.userId) { affectedContactId = null; if (actionType == GroupActionType.removedMember) { // Oh no, I just got removed from the group... diff --git a/lib/src/services/api/client2client/user_discovery.c2c.dart b/lib/src/services/api/client2client/user_discovery.c2c.dart index e61fd465..d265b825 100644 --- a/lib/src/services/api/client2client/user_discovery.c2c.dart +++ b/lib/src/services/api/client2client/user_discovery.c2c.dart @@ -34,17 +34,17 @@ Future handleUserDiscoveryRequest( ) async { Log.info('Got a user discovery request'); - if (!gUser.isUserDiscoveryEnabled) { + if (!AppSession.currentUser.isUserDiscoveryEnabled) { Log.warn('Got a user discovery request while it is disabled'); return; } final contact = await twonlyDB.contactsDao.getContactById(fromUserId); if (contact == null) return; - if (contact.mediaSendCounter < gUser.minimumRequiredImagesExchanged || + if (contact.mediaSendCounter < AppSession.currentUser.minimumRequiredImagesExchanged || contact.userDiscoveryExcluded) { Log.warn( - 'Got a request to update user discovery, but mediaSendCounter (${contact.mediaSendCounter}) < ${gUser.minimumRequiredImagesExchanged} or user is excluded ${contact.userDiscoveryExcluded}', + 'Got a request to update user discovery, but mediaSendCounter (${contact.mediaSendCounter}) < ${AppSession.currentUser.minimumRequiredImagesExchanged} or user is excluded ${contact.userDiscoveryExcluded}', ); return; } @@ -72,7 +72,7 @@ Future handleUserDiscoveryUpdate( int fromUserId, EncryptedContent_UserDiscoveryUpdate update, ) async { - if (!gUser.isUserDiscoveryEnabled) { + if (!AppSession.currentUser.isUserDiscoveryEnabled) { Log.warn('Got a user discovery update while it is disabled'); return; } diff --git a/lib/src/services/api/mediafiles/download.service.dart b/lib/src/services/api/mediafiles/download.service.dart index 28a2a670..31e57eeb 100644 --- a/lib/src/services/api/mediafiles/download.service.dart +++ b/lib/src/services/api/mediafiles/download.service.dart @@ -96,7 +96,7 @@ Future isAllowedToDownload(MediaType type) async { } final connectivityResult = await Connectivity().checkConnectivity(); - final options = gUser.autoDownloadOptions ?? defaultAutoDownloadOptions; + final options = AppSession.currentUser.autoDownloadOptions ?? defaultAutoDownloadOptions; if (connectivityResult.contains(ConnectivityResult.mobile)) { if (type == MediaType.video) { diff --git a/lib/src/services/api/mediafiles/upload.service.dart b/lib/src/services/api/mediafiles/upload.service.dart index b45eb2a1..4629d26a 100644 --- a/lib/src/services/api/mediafiles/upload.service.dart +++ b/lib/src/services/api/mediafiles/upload.service.dart @@ -357,7 +357,7 @@ Future startBackgroundMediaUpload(MediaFileService mediaService) async { // if the user has enabled auto storing and the file // was send with unlimited counter not in twonly-Mode then store the file - if (gUser.autoStoreAllSendUnlimitedMediaFiles && + if (AppSession.currentUser.autoStoreAllSendUnlimitedMediaFiles && !mediaService.mediaFile.requiresAuthentication && !mediaService.storedPath.existsSync() && mediaService.mediaFile.displayLimitInMilliseconds == null) { diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.dart index 52129f42..cad90fc5 100644 --- a/lib/src/services/api/messages.dart +++ b/lib/src/services/api/messages.dart @@ -345,12 +345,12 @@ Future<(Uint8List, Uint8List?)?> sendCipherText( return null; } } - encryptedContent.senderProfileCounter = Int64(gUser.avatarCounter); + encryptedContent.senderProfileCounter = Int64(AppSession.currentUser.avatarCounter); - if (gUser.isUserDiscoveryEnabled && messageId != null) { + if (AppSession.currentUser.isUserDiscoveryEnabled && messageId != null) { final contact = await twonlyDB.contactsDao.getContactById(contactId); if (contact != null && - contact.mediaSendCounter >= gUser.minimumRequiredImagesExchanged && + contact.mediaSendCounter >= AppSession.currentUser.minimumRequiredImagesExchanged && !contact.userDiscoveryExcluded) { final version = await UserDiscoveryService.getCurrentVersion(); if (version != null) { @@ -406,7 +406,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText( } Future sendTypingIndication(String groupId, bool isTyping) async { - if (!gUser.typingIndicators) return; + if (!AppSession.currentUser.typingIndicators) return; await sendCipherTextToGroup( groupId, pb.EncryptedContent( @@ -462,15 +462,15 @@ Future notifyContactAboutOpeningMessage( Future sendContactMyProfileData(int contactId) async { List? avatarSvgCompressed; - if (gUser.avatarSvg != null) { - avatarSvgCompressed = gzip.encode(utf8.encode(gUser.avatarSvg!)); + if (AppSession.currentUser.avatarSvg != null) { + avatarSvgCompressed = gzip.encode(utf8.encode(AppSession.currentUser.avatarSvg!)); } final encryptedContent = pb.EncryptedContent( contactUpdate: pb.EncryptedContent_ContactUpdate( type: pb.EncryptedContent_ContactUpdate_Type.UPDATE, avatarSvgCompressed: avatarSvgCompressed, - displayName: gUser.displayName, - username: gUser.username, + displayName: AppSession.currentUser.displayName, + username: AppSession.currentUser.username, ), ); await sendCipherText(contactId, encryptedContent); diff --git a/lib/src/services/api/server_messages.dart b/lib/src/services/api/server_messages.dart index 2a1113c3..4b176870 100644 --- a/lib/src/services/api/server_messages.dart +++ b/lib/src/services/api/server_messages.dart @@ -263,7 +263,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( await twonlyDB.receiptsDao.markMessagesForRetry(fromUserId); final senderProfileCounter = await checkForProfileUpdate(fromUserId, content); - if (gUser.isUserDiscoveryEnabled && content.hasSenderUserDiscoveryVersion()) { + if (AppSession.currentUser.isUserDiscoveryEnabled && content.hasSenderUserDiscoveryVersion()) { await checkForUserDiscoveryChanges( fromUserId, content.senderUserDiscoveryVersion, @@ -351,7 +351,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( /// Verify that the user is (still) in that group... if (!await twonlyDB.groupsDao.isContactInGroup(fromUserId, content.groupId)) { - if (getUUIDforDirectChat(gUser.userId, fromUserId) == content.groupId) { + if (getUUIDforDirectChat(AppSession.currentUser.userId, fromUserId) == content.groupId) { final contact = await twonlyDB.contactsDao .getContactByUserId(fromUserId) .getSingleOrNull(); diff --git a/lib/src/services/background/callback_dispatcher.background.dart b/lib/src/services/background/callback_dispatcher.background.dart index bfa87bce..b271cbde 100644 --- a/lib/src/services/background/callback_dispatcher.background.dart +++ b/lib/src/services/background/callback_dispatcher.background.dart @@ -57,7 +57,7 @@ Future initBackgroundExecution() async { // stay alive for multiple hours between task executions final user = await getUser(); if (user == null) return false; - gUser = user; + AppSession.currentUser = user; return true; } @@ -69,7 +69,7 @@ Future initBackgroundExecution() async { final user = await getUser(); if (user == null) return false; - gUser = user; + AppSession.currentUser = user; twonlyDB = TwonlyDB(); apiService = ApiService(); diff --git a/lib/src/services/backup/common.backup.dart b/lib/src/services/backup/common.backup.dart index 9617954b..4fc9080b 100644 --- a/lib/src/services/backup/common.backup.dart +++ b/lib/src/services/backup/common.backup.dart @@ -13,34 +13,35 @@ import 'package:twonly/src/utils/storage.dart'; Future enableTwonlySafe(String password) async { final (backupId, encryptionKey) = await getMasterKey( password, - gUser.username, + AppSession.currentUser.username, ); - await updateUserdata((user) { + await updateUser((user) { user.twonlySafeBackup = TwonlySafeBackup( encryptionKey: encryptionKey, backupId: backupId, ); - return user; }); unawaited(performTwonlySafeBackup(force: true)); } Future removeTwonlySafeFromServer() async { - final serverUrl = await getTwonlySafeBackupUrl(); - if (serverUrl != null) { - try { - final response = await http.delete( - Uri.parse(serverUrl), - headers: { - 'Content-Type': 'application/json', // Set the content type if needed - // Add any other headers if required - }, - ); - Log.info('Download deleted with: ${response.statusCode}'); - } catch (e) { - Log.error('Could not connect upload the backup.'); - } + final serverUrl = getTwonlySafeBackupUrl(); + if (serverUrl == null) { + Log.error('Could not remove twonly safe as serverUrl is null'); + return; + } + try { + final response = await http.delete( + Uri.parse(serverUrl), + headers: { + 'Content-Type': 'application/json', // Set the content type if needed + // Add any other headers if required + }, + ); + Log.info('Download deleted with: ${response.statusCode}'); + } catch (e) { + Log.error('Could not connect upload the backup.'); } } @@ -63,19 +64,18 @@ Future<(Uint8List, Uint8List)> getMasterKey( return (key.sublist(0, 32), key.sublist(32, 64)); } -Future getTwonlySafeBackupUrl() async { - final user = await getUser(); - if (user == null || user.twonlySafeBackup == null) return null; +String? getTwonlySafeBackupUrl() { + if (AppSession.currentUser.twonlySafeBackup == null) return null; return getTwonlySafeBackupUrlFromServer( - user.twonlySafeBackup!.backupId, - user.backupServer, + AppSession.currentUser.twonlySafeBackup!.backupId, + AppSession.currentUser.backupServer, ); } -Future getTwonlySafeBackupUrlFromServer( +String? getTwonlySafeBackupUrlFromServer( List backupId, BackupServer? backupServer, -) async { +) { var backupServerUrl = 'https://safe.twonly.eu/'; if (backupServer != null) { diff --git a/lib/src/services/backup/create.backup.dart b/lib/src/services/backup/create.backup.dart index a03c7e6f..6a5e6421 100644 --- a/lib/src/services/backup/create.backup.dart +++ b/lib/src/services/backup/create.backup.dart @@ -19,20 +19,20 @@ import 'package:twonly/src/services/backup/common.backup.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/settings/backup/backup.view.dart'; Future performTwonlySafeBackup({bool force = false}) async { - if (gUser.twonlySafeBackup == null) { + if (AppSession.currentUser.twonlySafeBackup == null) { return; } - if (gUser.twonlySafeBackup!.backupUploadState == + if (AppSession.currentUser.twonlySafeBackup!.backupUploadState == LastBackupUploadState.pending) { Log.warn('Backup upload is already pending.'); return; } - final lastUpdateTime = gUser.twonlySafeBackup!.lastBackupDone; + final lastUpdateTime = + AppSession.currentUser.twonlySafeBackup!.lastBackupDone; if (!force && lastUpdateTime != null) { if (lastUpdateTime.isAfter(clock.now().subtract(const Duration(days: 1)))) { return; @@ -120,8 +120,8 @@ Future performTwonlySafeBackup({bool force = false}) async { final backupHash = uint8ListToHex((await Sha256().hash(backupBytes)).bytes); - if (gUser.twonlySafeBackup!.lastBackupDone == null || - gUser.twonlySafeBackup!.lastBackupDone!.isAfter( + if (AppSession.currentUser.twonlySafeBackup!.lastBackupDone == null || + AppSession.currentUser.twonlySafeBackup!.lastBackupDone!.isAfter( clock.now().subtract(const Duration(days: 90)), )) { force = true; @@ -149,7 +149,9 @@ Future performTwonlySafeBackup({bool force = false}) async { final secretBox = await chacha20.encrypt( backupBytes, - secretKey: SecretKey(gUser.twonlySafeBackup!.encryptionKey), + secretKey: SecretKey( + AppSession.currentUser.twonlySafeBackup!.encryptionKey, + ), nonce: nonce, ); @@ -171,12 +173,12 @@ Future performTwonlySafeBackup({bool force = false}) async { 'Create twonly Backup with a size of ${encryptedBackupBytes.length} bytes.', ); - if (gUser.backupServer != null) { - if (encryptedBackupBytes.length > gUser.backupServer!.maxBackupBytes) { + if (AppSession.currentUser.backupServer != null) { + if (encryptedBackupBytes.length > + AppSession.currentUser.backupServer!.maxBackupBytes) { Log.error('Backup is to big for the alternative backup server.'); - await updateUserdata((user) { + await updateUser((user) { user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed; - return user; }); return; } @@ -186,7 +188,7 @@ Future performTwonlySafeBackup({bool force = false}) async { taskId: 'backup', file: encryptedBackupBytesFile, httpRequestMethod: 'PUT', - url: (await getTwonlySafeBackupUrl())!, + url: getTwonlySafeBackupUrl()!, post: 'binary', retries: 2, headers: { @@ -195,13 +197,11 @@ Future performTwonlySafeBackup({bool force = false}) async { ); if (await FileDownloader().enqueue(task)) { Log.info('Starting upload from twonly Backup.'); - await updateUserdata((user) { + await updateUser((user) { user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.pending; user.twonlySafeBackup!.lastBackupDone = clock.now(); user.twonlySafeBackup!.lastBackupSize = encryptedBackupBytes.length; - return user; }); - gUpdateBackupView(); } else { Log.error('Error starting UploadTask for twonly Backup.'); } @@ -210,26 +210,23 @@ Future performTwonlySafeBackup({bool force = false}) async { Future handleBackupStatusUpdate(TaskStatusUpdate update) async { if (update.status == TaskStatus.failed || update.status == TaskStatus.canceled) { - await updateUserdata((user) { + await updateUser((user) { if (user.twonlySafeBackup != null) { user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed; } - return user; }); } else if (update.status == TaskStatus.complete) { Log.info( 'twonly Backup uploaded with status code ${update.responseStatusCode}', ); - await updateUserdata((user) { + await updateUser((user) { if (user.twonlySafeBackup != null) { user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.success; } - return user; }); } else { Log.info('Backup is in state: ${update.status}'); return; } - gUpdateBackupView(); } diff --git a/lib/src/services/backup/restore.backup.dart b/lib/src/services/backup/restore.backup.dart index 099bb000..2ec13760 100644 --- a/lib/src/services/backup/restore.backup.dart +++ b/lib/src/services/backup/restore.backup.dart @@ -110,8 +110,7 @@ Future handleBackupData( key: SecureStorageKeys.userData, value: secureStorage[SecureStorageKeys.userData] as String, ); - await updateUserdata((u) { + await updateUser((u) { u.deviceId += 1; - return u; }); } diff --git a/lib/src/services/flame.service.dart b/lib/src/services/flame.service.dart index a7172ce1..dcba381b 100644 --- a/lib/src/services/flame.service.dart +++ b/lib/src/services/flame.service.dart @@ -17,10 +17,9 @@ Future syncFlameCounters({String? forceForGroup}) async { (x) => x.totalMediaCounter == maxMessageCounter, ); - if (gUser.myBestFriendGroupId != bestFriend.groupId) { - await updateUserdata((user) { + if (AppSession.currentUser.myBestFriendGroupId != bestFriend.groupId) { + await updateUser((user) { user.myBestFriendGroupId = bestFriend.groupId; - return user; }); } diff --git a/lib/src/services/group.services.dart b/lib/src/services/group.services.dart index 7a64777a..4ed4d399 100644 --- a/lib/src/services/group.services.dart +++ b/lib/src/services/group.services.dart @@ -42,8 +42,8 @@ Future createNewGroup(String groupName, List members) async { final memberIds = members.map((x) => Int64(x.userId)).toList(); final groupState = EncryptedGroupState( - memberIds: [Int64(gUser.userId)] + memberIds, - adminIds: [Int64(gUser.userId)], + memberIds: [Int64(AppSession.currentUser.userId)] + memberIds, + adminIds: [Int64(AppSession.currentUser.userId)], groupName: groupName, deleteMessagesAfterMilliseconds: Int64( defaultDeleteMessagesAfterMilliseconds, @@ -283,9 +283,9 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { final myPubKey = keyPair.getPublicKey().serialize().toList(); if (listEquals(appendedPubKey, myPubKey)) { - adminIds.remove(Int64(gUser.userId)); + adminIds.remove(Int64(AppSession.currentUser.userId)); memberIds.remove( - Int64(gUser.userId), + Int64(AppSession.currentUser.userId), ); // -> Will remove the user later... } else { Log.info('A non admin left the group!!!'); @@ -303,7 +303,7 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { } } - if (!memberIds.contains(Int64(gUser.userId))) { + if (!memberIds.contains(Int64(AppSession.currentUser.userId))) { // OH no, I am no longer a member of this group... // Return from the group... await twonlyDB.groupsDao.updateGroup( @@ -316,7 +316,7 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { } final isGroupAdmin = - adminIds.firstWhereOrNull((t) => t.toInt() == gUser.userId) != null; + adminIds.firstWhereOrNull((t) => t.toInt() == AppSession.currentUser.userId) != null; if (!listEquals(memberIds, encryptedGroupState.memberIds)) { if (isGroupAdmin) { @@ -368,7 +368,7 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { // First find and insert NEW members for (final memberId in memberIds) { - if (memberId == Int64(gUser.userId)) { + if (memberId == Int64(AppSession.currentUser.userId)) { continue; } if (currentGroupMembers.any((t) => t.contactId == memberId.toInt())) { @@ -838,7 +838,7 @@ Future removeMemberFromGroup( groupId: Value(group.groupId), type: const Value(GroupActionType.removedMember), affectedContactId: Value( - removeContactId == gUser.userId ? null : removeContactId, + removeContactId == AppSession.currentUser.userId ? null : removeContactId, ), ), ); @@ -945,7 +945,7 @@ Future leaveAsNonAdminFromGroup(Group group) async { EncryptedContent( groupUpdate: EncryptedContent_GroupUpdate( groupActionType: groupActionType.name, - affectedContactId: Int64(gUser.userId), + affectedContactId: Int64(AppSession.currentUser.userId), ), ), ); diff --git a/lib/src/services/intent/links.intent.dart b/lib/src/services/intent/links.intent.dart index 03d70db1..a71504eb 100644 --- a/lib/src/services/intent/links.intent.dart +++ b/lib/src/services/intent/links.intent.dart @@ -32,7 +32,7 @@ Future handleIntentUrl(BuildContext context, Uri uri) async { if (!context.mounted) return false; - if (username == gUser.username) { + if (username == AppSession.currentUser.username) { await context.push(Routes.settingsPublicProfile); return true; } @@ -115,7 +115,7 @@ Future handleIntentMediaFile( final newMediaService = await initializeMediaUpload( type, - gUser.defaultShowTime, + AppSession.currentUser.defaultShowTime, ); if (newMediaService == null) { Log.error('Could not create new media file for intent shared file'); diff --git a/lib/src/services/mediafiles/mediafile.service.dart b/lib/src/services/mediafiles/mediafile.service.dart index 1e5424ca..92d1b1f9 100644 --- a/lib/src/services/mediafiles/mediafile.service.dart +++ b/lib/src/services/mediafiles/mediafile.service.dart @@ -237,7 +237,7 @@ class MediaFileService { } if (tempPath.existsSync()) { await tempPath.copy(storedPath.path); - if (gUser.storeMediaFilesInGallery) { + if (AppSession.currentUser.storeMediaFilesInGallery) { if (mediaFile.type == MediaType.video) { await saveVideoToGallery(storedPath.path); } else { diff --git a/lib/src/services/notifications/fcm.notifications.dart b/lib/src/services/notifications/fcm.notifications.dart index 2ae14392..0d5d9098 100644 --- a/lib/src/services/notifications/fcm.notifications.dart +++ b/lib/src/services/notifications/fcm.notifications.dart @@ -48,9 +48,8 @@ Future checkForTokenUpdates() async { if (storedToken == null || fcmToken != storedToken) { Log.info('Got new FCM TOKEN.'); await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken); - await updateUserdata((u) { + await updateUser((u) { u.updateFCMToken = true; - return u; }); } @@ -61,9 +60,8 @@ Future checkForTokenUpdates() async { key: SecureStorageKeys.googleFcm, value: fcmToken, ); - await updateUserdata((u) { + await updateUser((u) { u.updateFCMToken = true; - return u; }); }) .onError((err) { @@ -75,16 +73,15 @@ Future checkForTokenUpdates() async { } Future initFCMAfterAuthenticated({bool force = false}) async { - if (gUser.updateFCMToken || force) { + if (AppSession.currentUser.updateFCMToken || force) { const storage = FlutterSecureStorage(); final storedToken = await storage.read(key: SecureStorageKeys.googleFcm); if (storedToken != null) { final res = await apiService.updateFCMToken(storedToken); if (res.isSuccess) { Log.info('Uploaded new FCM token!'); - await updateUserdata((u) { + await updateUser((u) { u.updateFCMToken = false; - return u; }); } else { Log.error('Could not update FCM token!'); diff --git a/lib/src/services/signal/identity.signal.dart b/lib/src/services/signal/identity.signal.dart index a9692dac..3c41e054 100644 --- a/lib/src/services/signal/identity.signal.dart +++ b/lib/src/services/signal/identity.signal.dart @@ -20,11 +20,12 @@ Future getSignalIdentityKeyPair() async { // This function runs after the clients authenticated with the server. // It then checks if it should update a new session key Future signalHandleNewServerConnection() async { - if (gUser.signalLastSignedPreKeyUpdated != null) { + if (AppSession.currentUser.signalLastSignedPreKeyUpdated != null) { final fortyEightHoursAgo = clock.now().subtract(const Duration(hours: 48)); - final isYoungerThan48Hours = (gUser.signalLastSignedPreKeyUpdated!).isAfter( - fortyEightHoursAgo, - ); + final isYoungerThan48Hours = + (AppSession.currentUser.signalLastSignedPreKeyUpdated!).isAfter( + fortyEightHoursAgo, + ); if (isYoungerThan48Hours) { // The key does live for 48 hours then it expires and a new key is generated. return; @@ -35,9 +36,8 @@ Future signalHandleNewServerConnection() async { Log.error('could not generate a new signed pre key!'); return; } - await updateUserdata((user) { + await updateUser((user) { user.signalLastSignedPreKeyUpdated = clock.now(); - return user; }); final res = await apiService.updateSignedPreKey( signedPreKey.id, @@ -46,9 +46,8 @@ Future signalHandleNewServerConnection() async { ); if (res.isError) { Log.error('could not update the signed pre key: ${res.error}'); - await updateUserdata((user) { + await updateUser((user) { user.signalLastSignedPreKeyUpdated = null; - return user; }); } else { Log.info('updated signed pre key'); @@ -60,10 +59,9 @@ Future> signalGetPreKeys() async { if (user == null) return []; final start = user.currentPreKeyIndexStart; - await updateUserdata((user) { + await updateUser((user) { user.currentPreKeyIndexStart = (user.currentPreKeyIndexStart + 200) % maxValue; - return user; }); final preKeys = generatePreKeys(start, 200); final signalStore = await getSignalStore(); @@ -138,9 +136,8 @@ Future _getNewSignalSignedPreKey() async { } final signedPreKeyId = user.currentSignedPreKeyIndexStart; - await updateUserdata((user) { + await updateUser((user) { user.currentSignedPreKeyIndexStart += 1; - return user; }); final signedPreKey = generateSignedPreKey( diff --git a/lib/src/services/subscription.service.dart b/lib/src/services/subscription.service.dart index 27b50cc3..cd115e43 100644 --- a/lib/src/services/subscription.service.dart +++ b/lib/src/services/subscription.service.dart @@ -1,7 +1,5 @@ // ignore_for_file: constant_identifier_names -import 'package:twonly/globals.dart'; - enum SubscriptionPlan { Free, Tester, @@ -41,7 +39,3 @@ SubscriptionPlan planFromString(String value) { } return SubscriptionPlan.Free; } - -SubscriptionPlan getCurrentPlan() { - return planFromString(gUser.subscriptionPlan); -} diff --git a/lib/src/services/user_discovery.service.dart b/lib/src/services/user_discovery.service.dart index a944db0d..e69a405c 100644 --- a/lib/src/services/user_discovery.service.dart +++ b/lib/src/services/user_discovery.service.dart @@ -53,15 +53,14 @@ class UserDiscoveryService { try { await FlutterUserDiscovery.initializeOrUpdate( threshold: threshold, - userId: gUser.userId, + userId: AppSession.currentUser.userId, publicKey: await getUserPublicKey(), ); - await updateUserdata((u) { - u + await updateUser( + (u) => u ..isUserDiscoveryEnabled = true - ..minimumRequiredImagesExchanged = minimumRequiredImagesExchanged; - return u; - }); + ..minimumRequiredImagesExchanged = minimumRequiredImagesExchanged, + ); } catch (e) { Log.error(e); } @@ -142,9 +141,8 @@ class UserDiscoveryService { } static Future disable() async { - await updateUserdata((u) { + await updateUser((u) { u.isUserDiscoveryEnabled = false; - return u; }); } } diff --git a/lib/src/utils/avatars.dart b/lib/src/utils/avatars.dart index a2648694..f31b611c 100644 --- a/lib/src/utils/avatars.dart +++ b/lib/src/utils/avatars.dart @@ -47,13 +47,13 @@ File avatarPNGFile(int contactId) { } Future getUserAvatar() async { - if (gUser.avatarSvg == null) { + if (AppSession.currentUser.avatarSvg == null) { final data = await rootBundle.load('assets/images/default_avatar.png'); return data.buffer.asUint8List(); } final pictureInfo = await vg.loadPicture( - SvgStringLoader(gUser.avatarSvg!), + SvgStringLoader(AppSession.currentUser.avatarSvg!), null, ); @@ -62,7 +62,7 @@ Future getUserAvatar() async { final byteData = await image.toByteData(format: ui.ImageByteFormat.png); final pngBytes = byteData!.buffer.asUint8List(); - final file = avatarPNGFile(gUser.userId)..writeAsBytesSync(pngBytes); + final file = avatarPNGFile(AppSession.currentUser.userId)..writeAsBytesSync(pngBytes); pictureInfo.picture.dispose(); return file.readAsBytesSync(); diff --git a/lib/src/utils/qr.dart b/lib/src/utils/qr.dart index ceb6c401..ad315b73 100644 --- a/lib/src/utils/qr.dart +++ b/lib/src/utils/qr.dart @@ -17,8 +17,8 @@ Future getProfileQrCodeData() async { final signedPreKey = (await signalStore.loadSignedPreKeys())[0]; final publicProfile = PublicProfile( - userId: Int64(gUser.userId), - username: gUser.username, + userId: Int64(AppSession.currentUser.userId), + username: AppSession.currentUser.username, publicIdentityKey: (await signalStore.getIdentityKeyPair()) .getPublicKey() .serialize(), diff --git a/lib/src/utils/storage.dart b/lib/src/utils/storage.dart index 60f3c15d..f7e35e75 100644 --- a/lib/src/utils/storage.dart +++ b/lib/src/utils/storage.dart @@ -16,7 +16,7 @@ Future isUserCreated() async { if (user == null) { return false; } - gUser = user; + AppSession.currentUser = user; return true; } @@ -43,9 +43,8 @@ Future updateUsersPlan( ) async { context.read().plan = plan; - await updateUserdata((user) { + await updateUser((user) { user.subscriptionPlan = plan.name; - return user; }); if (!context.mounted) return; @@ -54,27 +53,25 @@ Future updateUsersPlan( Mutex updateProtection = Mutex(); -Future updateUserdata( - UserData Function(UserData userData) updateUser, +Future updateUser( + void Function(UserData userData) updateUser, ) async { - final userData = await updateProtection.protect(() async { + await updateProtection.protect(() async { final user = await getUser(); - if (user == null) return null; + if (user == null) return; if (user.defaultShowTime == 999999) { // This was the old version for infinity -> change it to null user.defaultShowTime = null; } - final updated = updateUser(user); + updateUser(user); await const FlutterSecureStorage().write( key: SecureStorageKeys.userData, - value: jsonEncode(updated), + value: jsonEncode(user), ); - gUser = updated; - return updated; + AppSession.currentUser = user; }); - userDataUpdateController.add(null); - return userData; + AppSession.triggerUserUpdate(); } Future deleteLocalUserData() async { diff --git a/lib/src/views/camera/camera_preview_components/camera_preview_controller_view.dart b/lib/src/views/camera/camera_preview_components/camera_preview_controller_view.dart index 76d7ef7d..94708969 100644 --- a/lib/src/views/camera/camera_preview_components/camera_preview_controller_view.dart +++ b/lib/src/views/camera/camera_preview_components/camera_preview_controller_view.dart @@ -208,10 +208,10 @@ class _CameraPreviewViewState extends State { Future initAsync() async { _hasAudioPermission = await Permission.microphone.isGranted; - if (!_hasAudioPermission && !gUser.requestedAudioPermission) { - await updateUserdata((u) { + if (!_hasAudioPermission && + !AppSession.currentUser.requestedAudioPermission) { + await updateUser((u) { u.requestedAudioPermission = true; - return u; }); await requestMicrophonePermission(); } @@ -321,7 +321,7 @@ class _CameraPreviewViewState extends State { ((videoFilePath != null) ? MediaType.video : MediaType.image); final mediaFileService = await initializeMediaUpload( type, - gUser.defaultShowTime, + AppSession.currentUser.defaultShowTime, isDraftMedia: true, ); if (!mounted) return true; 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 de80dba4..df4cf309 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 @@ -135,7 +135,7 @@ class MainCameraController { await cameraController?.initialize(); await cameraController?.startImageStream(_processCameraImage); await cameraController?.setZoomLevel(selectedCameraDetails.scaleFactor); - if (gUser.videoStabilizationEnabled && !kDebugMode) { + if (AppSession.currentUser.videoStabilizationEnabled && !kDebugMode) { await cameraController?.setVideoStabilizationMode( VideoStabilizationMode.level1, ); @@ -395,7 +395,7 @@ class MainCameraController { } } } else { - if (profile.username != gUser.username) { + if (profile.username != AppSession.currentUser.username) { if (scannedNewProfiles[profile.userId.toInt()] == null) { await HapticFeedback.heavyImpact(); scannedNewProfiles[profile.userId.toInt()] = ScannedNewProfile( diff --git a/lib/src/views/camera/share_image_contact_selection.view.dart b/lib/src/views/camera/share_image_contact_selection.view.dart index b4505f09..78e2ca73 100644 --- a/lib/src/views/camera/share_image_contact_selection.view.dart +++ b/lib/src/views/camera/share_image_contact_selection.view.dart @@ -254,7 +254,7 @@ class _ShareImageView extends State { children: [ if (widget.mediaFileService.mediaFile.type == MediaType.image && _screenshotImage?.image != null && - gUser.showShowImagePreviewWhenSending) + AppSession.currentUser.showShowImagePreviewWhenSending) SizedBox( height: 100, width: 100 * 9 / 16, diff --git a/lib/src/views/camera/share_image_editor.view.dart b/lib/src/views/camera/share_image_editor.view.dart index 6f9e96ec..f7ce1c9a 100644 --- a/lib/src/views/camera/share_image_editor.view.dart +++ b/lib/src/views/camera/share_image_editor.view.dart @@ -158,9 +158,8 @@ class _ShareImageEditorView extends State { if (!mounted) return; setState(() {}); if (storeAsDefault) { - await updateUserdata((user) { + await updateUser((user) { user.defaultShowTime = maxShowTime; - return user; }); } } diff --git a/lib/src/views/chats/add_new_user.view.dart b/lib/src/views/chats/add_new_user.view.dart index 4ce086f8..50a378bf 100644 --- a/lib/src/views/chats/add_new_user.view.dart +++ b/lib/src/views/chats/add_new_user.view.dart @@ -92,7 +92,7 @@ class _SearchUsernameView extends State { } Future _requestNewUserByUsername(String username) async { - if (gUser.username == username) return; + if (AppSession.currentUser.username == username) return; setState(() { _isLoading = true; diff --git a/lib/src/views/chats/chat_list.view.dart b/lib/src/views/chats/chat_list.view.dart index dda5cf72..e6b248a2 100644 --- a/lib/src/views/chats/chat_list.view.dart +++ b/lib/src/views/chats/chat_list.view.dart @@ -27,6 +27,7 @@ class ChatListView extends StatefulWidget { } class _ChatListViewState extends State { + StreamSubscription? _userSub; late StreamSubscription> _contactsSub; List _groupsNotPinned = []; List _groupsPinned = []; @@ -43,6 +44,9 @@ class _ChatListViewState extends State { @override void initState() { initAsync(); + _userSub = AppSession.onUserUpdated.listen((_) { + if (mounted) setState(() {}); + }); super.initState(); } @@ -85,11 +89,11 @@ class _ChatListViewState extends State { Sha256().hash, changeLog.codeUnits, )).bytes; - if (!gUser.hideChangeLog && - gUser.lastChangeLogHash.toString() != changeLogHash.toString()) { - await updateUserdata((u) { + if (!AppSession.currentUser.hideChangeLog && + AppSession.currentUser.lastChangeLogHash.toString() != + changeLogHash.toString()) { + await updateUser((u) { u.lastChangeLogHash = changeLogHash; - return u; }); if (!mounted) return; // only show changelog to people who already have contacts @@ -109,6 +113,7 @@ class _ChatListViewState extends State { _contactsSub.cancel(); _countContactRequestStream.cancel(); _countAnnouncedStream.cancel(); + _userSub?.cancel(); super.dispose(); } @@ -122,9 +127,7 @@ class _ChatListViewState extends State { ConnectionStatusBadge( child: GestureDetector( onTap: () async { - await context.push(Routes.settingsProfile); - if (!mounted) return; - setState(() {}); // gUser has updated + context.push(Routes.settingsProfile); }, child: AvatarIcon( myAvatar: true, @@ -199,8 +202,7 @@ class _ChatListViewState extends State { IconButton( onPressed: () async { - await context.push(Routes.settings); - if (mounted) setState(() {}); // gUser may has changed... + context.push(Routes.settings); }, icon: const FaIcon(FontAwesomeIcons.gear, size: 19), ), diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index 8b30c0bd..463a1622 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -122,7 +122,7 @@ class _ChatMessagesViewState extends State { _receiverDeletedAccount = groupContacts.first.accountDeleted; } - if (gUser.typingIndicators) { + if (AppSession.currentUser.typingIndicators) { unawaited(sendTypingIndication(widget.groupId, false)); _nextTypingIndicator = Timer.periodic(const Duration(seconds: 4), ( _, @@ -287,7 +287,7 @@ class _ChatMessagesViewState extends State { itemScrollController: itemScrollController, itemBuilder: (context, i) { if (i == 0) { - return gUser.typingIndicators + return AppSession.currentUser.typingIndicators ? TypingIndicator(group: group) : Container(); } diff --git a/lib/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart b/lib/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart index fa346998..2243cea9 100644 --- a/lib/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart +++ b/lib/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart @@ -102,7 +102,7 @@ class _ContactRowState extends State<_ContactRow> { bool _isLoading = false; Future _onContactClick(bool isAdded) async { - if (widget.contact.userId.toInt() == gUser.userId) { + if (widget.contact.userId.toInt() == AppSession.currentUser.userId) { await context.push(Routes.settingsProfile); return; } @@ -162,7 +162,7 @@ class _ContactRowState extends State<_ContactRow> { final contactInDb = snapshot.data; final isAdded = contactInDb != null || - widget.contact.userId.toInt() == gUser.userId; + widget.contact.userId.toInt() == AppSession.currentUser.userId; return GestureDetector( onTap: _isLoading ? null : () => _onContactClick(isAdded), diff --git a/lib/src/views/chats/chat_messages_components/message_input.dart b/lib/src/views/chats/chat_messages_components/message_input.dart index 8b19a12a..5ad205af 100644 --- a/lib/src/views/chats/chat_messages_components/message_input.dart +++ b/lib/src/views/chats/chat_messages_components/message_input.dart @@ -72,7 +72,7 @@ class _MessageInputState extends State { _textFieldController.text = widget.group.draftMessage!; } widget.textFieldFocus.addListener(_handleTextFocusChange); - if (gUser.typingIndicators) { + if (AppSession.currentUser.typingIndicators) { _nextTypingIndicator = Timer.periodic(const Duration(seconds: 1), ( _, ) async { diff --git a/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart index 342fbf10..74bb167e 100644 --- a/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart +++ b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart @@ -50,8 +50,8 @@ class _ReactionButtonsState extends State { } Future initAsync() async { - if (gUser.preSelectedEmojies != null) { - selectedEmojis = gUser.preSelectedEmojies!; + if (AppSession.currentUser.preSelectedEmojies != null) { + selectedEmojis = AppSession.currentUser.preSelectedEmojies!; } setState(() {}); } diff --git a/lib/src/views/components/avatar_icon.component.dart b/lib/src/views/components/avatar_icon.component.dart index 3ece858e..122f2e9d 100644 --- a/lib/src/views/components/avatar_icon.component.dart +++ b/lib/src/views/components/avatar_icon.component.dart @@ -33,7 +33,7 @@ class _AvatarIconState extends State { StreamSubscription>? groupStream; StreamSubscription>? contactsStream; StreamSubscription? contactStream; - StreamSubscription? _userDataSub; + StreamSubscription? _userSub; @override void initState() { @@ -46,7 +46,7 @@ class _AvatarIconState extends State { groupStream?.cancel(); contactStream?.cancel(); contactsStream?.cancel(); - _userDataSub?.cancel(); + _userSub?.cancel(); super.dispose(); } @@ -93,19 +93,19 @@ class _AvatarIconState extends State { setState(() {}); }); } else if (widget.myAvatar) { - _userDataSub = userDataUpdateController.stream.listen((_) { + _userSub = AppSession.onUserUpdated.listen((_) { if (mounted) { setState(() { - if (gUser.avatarSvg != null) { - _avatarSvg = gUser.avatarSvg; + if (AppSession.currentUser.avatarSvg != null) { + _avatarSvg = AppSession.currentUser.avatarSvg; } else { _avatarContacts = []; } }); } }); - if (gUser.avatarSvg != null) { - _avatarSvg = gUser.avatarSvg; + if (AppSession.currentUser.avatarSvg != null) { + _avatarSvg = AppSession.currentUser.avatarSvg; } } else if (widget.contactId != null) { contactStream = twonlyDB.contactsDao diff --git a/lib/src/views/components/flame.dart b/lib/src/views/components/flame.dart index 07dae00c..1ea1ff17 100644 --- a/lib/src/views/components/flame.dart +++ b/lib/src/views/components/flame.dart @@ -48,7 +48,7 @@ class _FlameCounterWidgetState extends State { } if (groupId != null && group != null) { isBestFriend = - gUser.myBestFriendGroupId == groupId && group.alsoBestFriend; + AppSession.currentUser.myBestFriendGroupId == groupId && group.alsoBestFriend; final stream = twonlyDB.groupsDao.watchFlameCounter(groupId); flameCounterSub = stream.listen((counter) { if (mounted) { diff --git a/lib/src/views/components/max_flame_list_title.dart b/lib/src/views/components/max_flame_list_title.dart index a214b462..af2c41b4 100644 --- a/lib/src/views/components/max_flame_list_title.dart +++ b/lib/src/views/components/max_flame_list_title.dart @@ -38,7 +38,10 @@ class _MaxFlameListTitleState extends State { @override void initState() { - _groupId = getUUIDforDirectChat(widget.contactId, gUser.userId); + _groupId = getUUIDforDirectChat( + widget.contactId, + AppSession.currentUser.userId, + ); final stream = twonlyDB.groupsDao.watchGroup(_groupId); _groupSub = stream.listen((update) { if (mounted) setState(() => _group = update); @@ -53,7 +56,8 @@ class _MaxFlameListTitleState extends State { } Future _restoreFlames() async { - if (!isUserAllowed(getCurrentPlan(), PremiumFeatures.RestoreFlames) && + final currentPlan = planFromString(AppSession.currentUser.subscriptionPlan); + if (!isUserAllowed(currentPlan, PremiumFeatures.RestoreFlames) && kReleaseMode) { await context.push(Routes.settingsSubscription); return; diff --git a/lib/src/views/contact/contact.view.dart b/lib/src/views/contact/contact.view.dart index b438b604..882eb3a8 100644 --- a/lib/src/views/contact/contact.view.dart +++ b/lib/src/views/contact/contact.view.dart @@ -204,7 +204,7 @@ class _ContactViewState extends State { }, ), SelectChatDeletionTimeListTitle( - groupId: getUUIDforDirectChat(widget.userId, gUser.userId), + groupId: getUUIDforDirectChat(widget.userId, AppSession.currentUser.userId), ), const Divider(), MaxFlameListTitle( @@ -222,17 +222,17 @@ class _ContactViewState extends State { setState(() {}); }, ), - if (gUser.isUserDiscoveryEnabled) + if (AppSession.currentUser.isUserDiscoveryEnabled) BetterListTile( icon: FontAwesomeIcons.usersViewfinder, text: context.lang.userDiscoverySettingsTitle, subtitle: !contact.userDiscoveryExcluded && contact.mediaSendCounter < - gUser.minimumRequiredImagesExchanged + AppSession.currentUser.minimumRequiredImagesExchanged ? Text( context.lang.contactUserDiscoveryImagesLeft( - gUser.minimumRequiredImagesExchanged - + AppSession.currentUser.minimumRequiredImagesExchanged - contact.mediaSendCounter, getContactDisplayName(contact), ), diff --git a/lib/src/views/groups/group.view.dart b/lib/src/views/groups/group.view.dart index abee84b1..884df911 100644 --- a/lib/src/views/groups/group.view.dart +++ b/lib/src/views/groups/group.view.dart @@ -142,7 +142,7 @@ class _GroupViewState extends State { success = await removeMemberFromGroup( _group!, keyPair.getPublicKey().serialize(), - gUser.userId, + AppSession.currentUser.userId, ); } else { success = await leaveAsNonAdminFromGroup(_group!); diff --git a/lib/src/views/home.view.dart b/lib/src/views/home.view.dart index 8d5b889c..3638cfec 100644 --- a/lib/src/views/home.view.dart +++ b/lib/src/views/home.view.dart @@ -135,7 +135,7 @@ class HomeViewState extends State { _mainCameraController.setSharedLinkForPreview, ); WidgetsBinding.instance.addPostFrameCallback((_) { - if (widget.initialPage == 1 && !gUser.startWithCameraOpen || + if (widget.initialPage == 1 && !AppSession.currentUser.startWithCameraOpen || widget.initialPage == 0) { globalUpdateOfHomeViewPageIndex(0); } diff --git a/lib/src/views/memories/memories_photo_slider.view.dart b/lib/src/views/memories/memories_photo_slider.view.dart index a44dbecd..85ebf4fb 100644 --- a/lib/src/views/memories/memories_photo_slider.view.dart +++ b/lib/src/views/memories/memories_photo_slider.view.dart @@ -98,7 +98,7 @@ class _MemoriesPhotoSliderViewState extends State { final newMediaService = await initializeMediaUpload( orgMediaService.mediaFile.type, - gUser.defaultShowTime, + AppSession.currentUser.defaultShowTime, ); if (newMediaService == null) { Log.error('Could not create new mediaFIle'); diff --git a/lib/src/views/onboarding/register.view.dart b/lib/src/views/onboarding/register.view.dart index 86f6dcd8..c0f9b831 100644 --- a/lib/src/views/onboarding/register.view.dart +++ b/lib/src/views/onboarding/register.view.dart @@ -142,7 +142,7 @@ class _RegisterViewState extends State { value: jsonEncode(userData), ); - gUser = userData; + AppSession.currentUser = userData; await apiService.authenticate(); widget.callbackOnSuccess(); diff --git a/lib/src/views/public_profile.view.dart b/lib/src/views/public_profile.view.dart index a2bb355d..091b672e 100644 --- a/lib/src/views/public_profile.view.dart +++ b/lib/src/views/public_profile.view.dart @@ -107,7 +107,7 @@ class _PublicProfileViewState extends State { ), const SizedBox(height: 20), Text( - gUser.username, + AppSession.currentUser.username, style: const TextStyle(fontSize: 24), ), const SizedBox(height: 20), @@ -126,11 +126,11 @@ class _PublicProfileViewState extends State { text: context.lang.shareYourProfile, subtitle: (_publicKey == null) ? null - : Text('https://me.twonly.eu/${gUser.username}'), + : Text('https://me.twonly.eu/${AppSession.currentUser.username}'), onTap: () { final params = ShareParams( text: - 'https://me.twonly.eu/${gUser.username}#${base64Url.encode(_publicKey!)}', + 'https://me.twonly.eu/${AppSession.currentUser.username}#${base64Url.encode(_publicKey!)}', ); SharePlus.instance.share(params); }, diff --git a/lib/src/views/settings/appearance.view.dart b/lib/src/views/settings/appearance.view.dart index a23be18b..e396755e 100644 --- a/lib/src/views/settings/appearance.view.dart +++ b/lib/src/views/settings/appearance.view.dart @@ -73,32 +73,20 @@ class _AppearanceViewState extends State { } Future toggleShowFeedbackIcon() async { - await updateUserdata((u) { + await updateUser((u) { u.showFeedbackShortcut = !u.showFeedbackShortcut; - return u; - }); - setState(() { - // gUser }); } Future toggleStartWithCameraOpen() async { - await updateUserdata((u) { + await updateUser((u) { u.startWithCameraOpen = !u.startWithCameraOpen; - return u; - }); - setState(() { - // gUser }); } Future toggleShowImagePreviewWhenSending() async { - await updateUserdata((u) { + await updateUser((u) { u.showShowImagePreviewWhenSending = !u.showShowImagePreviewWhenSending; - return u; - }); - setState(() { - // gUser }); } @@ -109,43 +97,48 @@ class _AppearanceViewState extends State { appBar: AppBar( title: Text(context.lang.settingsAppearance), ), - body: ListView( - children: [ - ListTile( - title: Text(context.lang.settingsAppearanceTheme), - subtitle: Text( - selectedTheme.name, - style: const TextStyle(color: Colors.grey), - ), - onTap: () async { - await _showSelectThemeMode(context); - }, - ), - ListTile( - title: Text(context.lang.contactUsShortcut), - onTap: toggleShowFeedbackIcon, - trailing: Switch( - value: !gUser.showFeedbackShortcut, - onChanged: (a) => toggleShowFeedbackIcon(), - ), - ), - ListTile( - title: Text(context.lang.startWithCameraOpen), - onTap: toggleStartWithCameraOpen, - trailing: Switch( - value: gUser.startWithCameraOpen, - onChanged: (a) => toggleStartWithCameraOpen(), - ), - ), - ListTile( - title: Text(context.lang.showImagePreviewWhenSending), - onTap: toggleShowImagePreviewWhenSending, - trailing: Switch( - value: gUser.showShowImagePreviewWhenSending, - onChanged: (a) => toggleShowImagePreviewWhenSending(), - ), - ), - ], + body: StreamBuilder( + stream: AppSession.onUserUpdated, + builder: (context, snapshot) { + return ListView( + children: [ + ListTile( + title: Text(context.lang.settingsAppearanceTheme), + subtitle: Text( + selectedTheme.name, + style: const TextStyle(color: Colors.grey), + ), + onTap: () async { + await _showSelectThemeMode(context); + }, + ), + ListTile( + title: Text(context.lang.contactUsShortcut), + onTap: toggleShowFeedbackIcon, + trailing: Switch( + value: !AppSession.currentUser.showFeedbackShortcut, + onChanged: (a) => toggleShowFeedbackIcon(), + ), + ), + ListTile( + title: Text(context.lang.startWithCameraOpen), + onTap: toggleStartWithCameraOpen, + trailing: Switch( + value: AppSession.currentUser.startWithCameraOpen, + onChanged: (a) => toggleStartWithCameraOpen(), + ), + ), + ListTile( + title: Text(context.lang.showImagePreviewWhenSending), + onTap: toggleShowImagePreviewWhenSending, + trailing: Switch( + value: AppSession.currentUser.showShowImagePreviewWhenSending, + onChanged: (a) => toggleShowImagePreviewWhenSending(), + ), + ), + ], + ); + }, ), ); } diff --git a/lib/src/views/settings/backup/backup.view.dart b/lib/src/views/settings/backup/backup.view.dart index 95e4f314..5f55ed45 100644 --- a/lib/src/views/settings/backup/backup.view.dart +++ b/lib/src/views/settings/backup/backup.view.dart @@ -8,8 +8,6 @@ import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/services/backup/create.backup.dart'; import 'package:twonly/src/utils/misc.dart'; -void Function() gUpdateBackupView = () {}; - class BackupView extends StatefulWidget { const BackupView({super.key}); @@ -34,18 +32,9 @@ class _BackupViewState extends State { void initState() { super.initState(); unawaited(initAsync()); - gUpdateBackupView = initAsync; } - @override - void dispose() { - gUpdateBackupView = () {}; - super.dispose(); - } - - Future initAsync() async { - setState(() {}); - } + Future initAsync() async {} String backupStatus(LastBackupUploadState status) { switch (status) { @@ -62,156 +51,170 @@ class _BackupViewState extends State { Future changeTwonlySafePassword() async { await context.push(Routes.settingsBackupSetup, extra: true); - setState(() { - // gUser was updated - }); } @override Widget build(BuildContext context) { - final backupServer = gUser.backupServer ?? defaultBackupServer; - return Scaffold( - appBar: AppBar( - title: Text(context.lang.settingsBackup), - ), - body: PageView( - controller: pageController, - onPageChanged: (index) { - setState(() { - activePageIdx = index; - }); - }, - children: [ - BackupOption( - title: 'twonly Backup', - description: context.lang.backupTwonlySafeDesc, - bottomButton: FilledButton( - onPressed: changeTwonlySafePassword, - child: Text(context.lang.backupChangePassword), - ), - child: (gUser.twonlySafeBackup == null) - ? null - : Column( - children: [ - Table( - defaultVerticalAlignment: - TableCellVerticalAlignment.middle, + return StreamBuilder( + stream: AppSession.onUserUpdated, + builder: (context, _) { + final backupServer = + AppSession.currentUser.backupServer ?? defaultBackupServer; + return Scaffold( + appBar: AppBar( + title: Text(context.lang.settingsBackup), + ), + body: PageView( + controller: pageController, + onPageChanged: (index) { + setState(() { + activePageIdx = index; + }); + }, + children: [ + BackupOption( + title: 'twonly Backup', + description: context.lang.backupTwonlySafeDesc, + bottomButton: FilledButton( + onPressed: changeTwonlySafePassword, + child: Text(context.lang.backupChangePassword), + ), + child: (AppSession.currentUser.twonlySafeBackup == null) + ? null + : Column( children: [ - ...[ - ( - context.lang.backupServer, - (backupServer.serverUrl.contains('@')) - ? backupServer.serverUrl.split('@')[1] - : backupServer.serverUrl.replaceAll( - 'https://', - '', - ), - ), - ( - context.lang.backupMaxBackupSize, - formatBytes(backupServer.maxBackupBytes), - ), - ( - context.lang.backupStorageRetention, - '${backupServer.retentionDays} Days', - ), - ( - context.lang.backupLastBackupDate, - formatDateTime( - context, - gUser.twonlySafeBackup!.lastBackupDone, - ), - ), - ( - context.lang.backupLastBackupSize, - formatBytes( - gUser.twonlySafeBackup!.lastBackupSize, - ), - ), - ( - context.lang.backupLastBackupResult, - backupStatus( - gUser.twonlySafeBackup!.backupUploadState, - ), - ), - ].map((pair) { - return TableRow( - children: [ - TableCell( - // padding: EdgeInsets.all(4), - child: Text(pair.$1), + Table( + defaultVerticalAlignment: + TableCellVerticalAlignment.middle, + children: [ + ...[ + ( + context.lang.backupServer, + (backupServer.serverUrl.contains('@')) + ? backupServer.serverUrl.split('@')[1] + : backupServer.serverUrl.replaceAll( + 'https://', + '', + ), ), - TableCell( - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 4, - ), - child: Text( - pair.$2, - textAlign: TextAlign.right, - ), + ( + context.lang.backupMaxBackupSize, + formatBytes(backupServer.maxBackupBytes), + ), + ( + context.lang.backupStorageRetention, + '${backupServer.retentionDays} Days', + ), + ( + context.lang.backupLastBackupDate, + formatDateTime( + context, + AppSession + .currentUser + .twonlySafeBackup! + .lastBackupDone, ), ), - ], - ); - }), + ( + context.lang.backupLastBackupSize, + formatBytes( + AppSession + .currentUser + .twonlySafeBackup! + .lastBackupSize, + ), + ), + ( + context.lang.backupLastBackupResult, + backupStatus( + AppSession + .currentUser + .twonlySafeBackup! + .backupUploadState, + ), + ), + ].map((pair) { + return TableRow( + children: [ + TableCell( + // padding: EdgeInsets.all(4), + child: Text(pair.$1), + ), + TableCell( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 4, + ), + child: Text( + pair.$2, + textAlign: TextAlign.right, + ), + ), + ), + ], + ); + }), + ], + ), + const SizedBox(height: 10), + OutlinedButton( + onPressed: isLoading + ? null + : () async { + setState(() { + isLoading = true; + }); + await performTwonlySafeBackup(force: true); + setState(() { + isLoading = false; + }); + }, + child: Text(context.lang.backupTwonlySaveNow), + ), ], ), - const SizedBox(height: 10), - OutlinedButton( - onPressed: isLoading - ? null - : () async { - setState(() { - isLoading = true; - }); - await performTwonlySafeBackup(force: true); - setState(() { - isLoading = false; - }); - }, - child: Text(context.lang.backupTwonlySaveNow), - ), - ], - ), + ), + BackupOption( + title: '${context.lang.backupData} (Coming Soon)', + description: context.lang.backupDataDesc, + ), + ], ), - BackupOption( - title: '${context.lang.backupData} (Coming Soon)', - description: context.lang.backupDataDesc, + bottomNavigationBar: BottomNavigationBar( + showSelectedLabels: true, + showUnselectedLabels: true, + unselectedIconTheme: IconThemeData( + color: Theme.of( + context, + ).colorScheme.inverseSurface.withAlpha(150), + ), + selectedIconTheme: IconThemeData( + color: Theme.of(context).colorScheme.inverseSurface, + ), + items: [ + const BottomNavigationBarItem( + icon: FaIcon(FontAwesomeIcons.vault, size: 17), + label: 'twonly Backup', + ), + BottomNavigationBarItem( + icon: const FaIcon(Icons.archive_outlined, size: 17), + label: context.lang.backupData, + ), + ], + onTap: (index) async { + activePageIdx = index; + await pageController.animateToPage( + index, + duration: const Duration(milliseconds: 100), + curve: Curves.bounceIn, + ); + if (mounted) setState(() {}); + }, + currentIndex: activePageIdx, + // ), ), - ], - ), - bottomNavigationBar: BottomNavigationBar( - showSelectedLabels: true, - showUnselectedLabels: true, - unselectedIconTheme: IconThemeData( - color: Theme.of(context).colorScheme.inverseSurface.withAlpha(150), - ), - selectedIconTheme: IconThemeData( - color: Theme.of(context).colorScheme.inverseSurface, - ), - items: [ - const BottomNavigationBarItem( - icon: FaIcon(FontAwesomeIcons.vault, size: 17), - label: 'twonly Backup', - ), - BottomNavigationBarItem( - icon: const FaIcon(Icons.archive_outlined, size: 17), - label: context.lang.backupData, - ), - ], - onTap: (index) async { - activePageIdx = index; - await pageController.animateToPage( - index, - duration: const Duration(milliseconds: 100), - curve: Curves.bounceIn, - ); - if (mounted) setState(() {}); - }, - currentIndex: activePageIdx, - // ), - ), + ); + }, ); } } diff --git a/lib/src/views/settings/backup/backup_server.view.dart b/lib/src/views/settings/backup/backup_server.view.dart index de780810..433a1214 100644 --- a/lib/src/views/settings/backup/backup_server.view.dart +++ b/lib/src/views/settings/backup/backup_server.view.dart @@ -31,8 +31,8 @@ class _BackupServerViewState extends State { } Future initAsync() async { - if (gUser.backupServer != null) { - final uri = Uri.parse(gUser.backupServer!.serverUrl); + if (AppSession.currentUser.backupServer != null) { + final uri = Uri.parse(AppSession.currentUser.backupServer!.serverUrl); // remove user auth data final serverUrl = Uri( scheme: uri.scheme, @@ -79,9 +79,8 @@ class _BackupServerViewState extends State { retentionDays: data['retentionDays']! as int, maxBackupBytes: data['maxBackupBytes']! as int, ); - await updateUserdata((user) { + await updateUser((user) { user.backupServer = backupServer; - return user; }); if (mounted) Navigator.pop(context, backupServer); } else { @@ -166,9 +165,8 @@ class _BackupServerViewState extends State { Center( child: OutlinedButton( onPressed: () async { - await updateUserdata((user) { + await updateUser((user) { user.backupServer = null; - return user; }); if (context.mounted) Navigator.pop(context); }, diff --git a/lib/src/views/settings/chat/chat_reactions.view.dart b/lib/src/views/settings/chat/chat_reactions.view.dart index 6adae922..a58778e9 100644 --- a/lib/src/views/settings/chat/chat_reactions.view.dart +++ b/lib/src/views/settings/chat/chat_reactions.view.dart @@ -38,9 +38,8 @@ class _ChatReactionSelectionView extends State { } else { if (selectedEmojis.length < 12) { selectedEmojis.add(emoji); - await updateUserdata((user) { + await updateUser((user) { user.preSelectedEmojies = selectedEmojis; - return user; }); } else { ScaffoldMessenger.of(context).showSnackBar( @@ -99,9 +98,8 @@ class _ChatReactionSelectionView extends State { 6, ); setState(() {}); - await updateUserdata((user) { + await updateUser((user) { user.preSelectedEmojies = selectedEmojis; - return user; }); }, child: const Icon(Icons.settings_backup_restore_rounded), diff --git a/lib/src/views/settings/data_and_storage.view.dart b/lib/src/views/settings/data_and_storage.view.dart index 7043785a..96b590b1 100644 --- a/lib/src/views/settings/data_and_storage.view.dart +++ b/lib/src/views/settings/data_and_storage.view.dart @@ -27,110 +27,121 @@ class _DataAndStorageViewState extends State { builder: (context) { return AutoDownloadOptionsDialog( autoDownloadOptions: - gUser.autoDownloadOptions ?? defaultAutoDownloadOptions, + AppSession.currentUser.autoDownloadOptions ?? + defaultAutoDownloadOptions, connectionMode: connectionMode, - onUpdate: () async { - setState(() {}); - }, + onUpdate: () {}, ); }, ); } Future toggleStoreInGallery() async { - await updateUserdata((u) { + await updateUser((u) { u.storeMediaFilesInGallery = !u.storeMediaFilesInGallery; - return u; }); - setState(() {}); } Future toggleAutoStoreMediaFiles() async { - await updateUserdata((u) { + await updateUser((u) { u.autoStoreAllSendUnlimitedMediaFiles = !u.autoStoreAllSendUnlimitedMediaFiles; - return u; }); - setState(() {}); } @override Widget build(BuildContext context) { - final autoDownloadOptions = - gUser.autoDownloadOptions ?? defaultAutoDownloadOptions; return Scaffold( appBar: AppBar( title: Text(context.lang.settingsStorageData), ), - body: ListView( - children: [ - ListTile( - title: Text(context.lang.settingsStorageDataStoreInGTitle), - subtitle: Text(context.lang.settingsStorageDataStoreInGSubtitle), - onTap: toggleStoreInGallery, - trailing: Switch( - value: gUser.storeMediaFilesInGallery, - onChanged: (a) => toggleStoreInGallery(), - ), - ), - ListTile( - title: Text(context.lang.autoStoreAllSendUnlimitedMediaFiles), - subtitle: Text( - context.lang.autoStoreAllSendUnlimitedMediaFilesSubtitle, - style: const TextStyle(fontSize: 9), - ), - onTap: toggleAutoStoreMediaFiles, - trailing: Switch( - value: gUser.autoStoreAllSendUnlimitedMediaFiles, - onChanged: (a) => toggleAutoStoreMediaFiles(), - ), - ), - if (Platform.isAndroid) - ListTile( - title: Text( - context.lang.exportMemories, + body: StreamBuilder( + stream: AppSession.onUserUpdated, + builder: (context, _) { + final autoDownloadOptions = + AppSession.currentUser.autoDownloadOptions ?? + defaultAutoDownloadOptions; + return ListView( + children: [ + ListTile( + title: Text(context.lang.settingsStorageDataStoreInGTitle), + subtitle: Text( + context.lang.settingsStorageDataStoreInGSubtitle, + ), + onTap: toggleStoreInGallery, + trailing: Switch( + value: AppSession.currentUser.storeMediaFilesInGallery, + onChanged: (a) => toggleStoreInGallery(), + ), ), - onTap: () => context.push(Routes.settingsStorageExport), - ), - if (Platform.isAndroid) - ListTile( - title: Text( - context.lang.importMemories, + ListTile( + title: Text(context.lang.autoStoreAllSendUnlimitedMediaFiles), + subtitle: Text( + context.lang.autoStoreAllSendUnlimitedMediaFilesSubtitle, + style: const TextStyle(fontSize: 9), + ), + onTap: toggleAutoStoreMediaFiles, + trailing: Switch( + value: AppSession + .currentUser + .autoStoreAllSendUnlimitedMediaFiles, + onChanged: (a) => toggleAutoStoreMediaFiles(), + ), ), - onTap: () => context.push(Routes.settingsStorageImport), - ), - const Divider(), - ListTile( - title: Text( - context.lang.settingsStorageDataMediaAutoDownload, - style: const TextStyle(fontSize: 13), - ), - ), - ListTile( - title: Text(context.lang.settingsStorageDataAutoDownMobile), - subtitle: Text( - autoDownloadOptions[ConnectivityResult.mobile.name]! - .where((e) => e != 'audio') - .join(', '), - style: const TextStyle(color: Colors.grey), - ), - onTap: () async { - await showAutoDownloadOptions(context, ConnectivityResult.mobile); - }, - ), - ListTile( - title: Text(context.lang.settingsStorageDataAutoDownWifi), - subtitle: Text( - autoDownloadOptions[ConnectivityResult.wifi.name]! - .where((e) => e != 'audio') - .join(', '), - style: const TextStyle(color: Colors.grey), - ), - onTap: () async { - await showAutoDownloadOptions(context, ConnectivityResult.wifi); - }, - ), - ], + if (Platform.isAndroid) + ListTile( + title: Text( + context.lang.exportMemories, + ), + onTap: () => context.push(Routes.settingsStorageExport), + ), + if (Platform.isAndroid) + ListTile( + title: Text( + context.lang.importMemories, + ), + onTap: () => context.push(Routes.settingsStorageImport), + ), + const Divider(), + ListTile( + title: Text( + context.lang.settingsStorageDataMediaAutoDownload, + style: const TextStyle(fontSize: 13), + ), + ), + ListTile( + title: Text(context.lang.settingsStorageDataAutoDownMobile), + subtitle: Text( + autoDownloadOptions[ConnectivityResult.mobile.name]! + .where((e) => e != 'audio') + .join(', '), + style: const TextStyle(color: Colors.grey), + ), + onTap: () async { + await showAutoDownloadOptions( + context, + ConnectivityResult.mobile, + ); + }, + ), + ListTile( + title: Text(context.lang.settingsStorageDataAutoDownWifi), + subtitle: Text( + autoDownloadOptions[ConnectivityResult.wifi.name]! + .where((e) => e != 'audio') + .join(', '), + style: const TextStyle(color: Colors.grey), + ), + onTap: () async { + await showAutoDownloadOptions( + context, + ConnectivityResult.wifi, + ); + }, + ), + ], + ); + }, ), ); } @@ -215,9 +226,8 @@ class _AutoDownloadOptionsDialogState extends State { // Call the onUpdate callback to notify the parent widget - await updateUserdata((u) { + await updateUser((u) { u.autoDownloadOptions = autoDownloadOptions; - return u; }); widget.onUpdate(); diff --git a/lib/src/views/settings/developer/developer.view.dart b/lib/src/views/settings/developer/developer.view.dart index a9d3ca57..00486e14 100644 --- a/lib/src/views/settings/developer/developer.view.dart +++ b/lib/src/views/settings/developer/developer.view.dart @@ -26,19 +26,13 @@ class _DeveloperSettingsViewState extends State { } Future toggleDeveloperSettings() async { - await updateUserdata((u) { - u.isDeveloper = !u.isDeveloper; - return u; - }); - setState(() {}); + await updateUser((u) => u.isDeveloper = !u.isDeveloper); } Future toggleVideoStabilization() async { - await updateUserdata((u) { - u.videoStabilizationEnabled = !u.videoStabilizationEnabled; - return u; - }); - setState(() {}); + await updateUser( + (u) => u.videoStabilizationEnabled = !u.videoStabilizationEnabled, + ); } @override @@ -47,80 +41,86 @@ class _DeveloperSettingsViewState extends State { appBar: AppBar( title: const Text('Developer Settings'), ), - body: ListView( - children: [ - ListTile( - title: const Text('Show Developer Settings'), - onTap: toggleDeveloperSettings, - trailing: Switch( - value: gUser.isDeveloper, - onChanged: (a) => toggleDeveloperSettings(), - ), - ), - ListTile( - title: const Text('Show Retransmission Database'), - onTap: () => - context.push(Routes.settingsDeveloperRetransmissionDatabase), - ), - ListTile( - title: const Text('Toggle Video Stabilization'), - onTap: toggleVideoStabilization, - trailing: Switch( - value: gUser.videoStabilizationEnabled, - onChanged: (a) => toggleVideoStabilization(), - ), - ), - ListTile( - title: const Text('Delete all (!) app data'), - onTap: () async { - final ok = await showAlertDialog( - context, - 'Sure?', - 'If you do not have a backup, you have to register with a new account.', - ); - if (ok) { - await deleteLocalUserData(); - await Restart.restartApp( - notificationTitle: 'Account successfully deleted', - notificationBody: 'Click here to open the app again', - forceKill: true, - ); - } - }, - ), - ListTile( - title: const Text('Reduce flames'), - onTap: () => context.push(Routes.settingsDeveloperReduceFlames), - ), - if (!kReleaseMode) - ListTile( - title: const Text('Make it possible to reset flames'), - onTap: () async { - final chats = await twonlyDB.groupsDao.getAllDirectChats(); - - for (final chat in chats) { - await twonlyDB.groupsDao.updateGroup( - chat.groupId, - GroupsCompanion( - flameCounter: const Value(0), - maxFlameCounter: const Value(365), - lastFlameCounterChange: Value(clock.now()), - maxFlameCounterFrom: Value( - clock.now().subtract(const Duration(days: 1)), - ), - ), + body: StreamBuilder( + stream: AppSession.onUserUpdated, + builder: (context, _) { + return ListView( + children: [ + ListTile( + title: const Text('Show Developer Settings'), + onTap: toggleDeveloperSettings, + trailing: Switch( + value: AppSession.currentUser.isDeveloper, + onChanged: (_) => toggleDeveloperSettings(), + ), + ), + ListTile( + title: const Text('Show Retransmission Database'), + onTap: () => context.push( + Routes.settingsDeveloperRetransmissionDatabase, + ), + ), + ListTile( + title: const Text('Toggle Video Stabilization'), + onTap: toggleVideoStabilization, + trailing: Switch( + value: AppSession.currentUser.videoStabilizationEnabled, + onChanged: (a) => toggleVideoStabilization(), + ), + ), + ListTile( + title: const Text('Delete all (!) app data'), + onTap: () async { + final ok = await showAlertDialog( + context, + 'Sure?', + 'If you do not have a backup, you have to register with a new account.', ); - } - await HapticFeedback.heavyImpact(); - }, - ), - if (!kReleaseMode) - ListTile( - title: const Text('Automated Testing'), - onTap: () => - context.push(Routes.settingsDeveloperAutomatedTesting), - ), - ], + if (ok) { + await deleteLocalUserData(); + await Restart.restartApp( + notificationTitle: 'Account successfully deleted', + notificationBody: 'Click here to open the app again', + forceKill: true, + ); + } + }, + ), + ListTile( + title: const Text('Reduce flames'), + onTap: () => context.push(Routes.settingsDeveloperReduceFlames), + ), + if (!kReleaseMode) + ListTile( + title: const Text('Make it possible to reset flames'), + onTap: () async { + final chats = await twonlyDB.groupsDao.getAllDirectChats(); + + for (final chat in chats) { + await twonlyDB.groupsDao.updateGroup( + chat.groupId, + GroupsCompanion( + flameCounter: const Value(0), + maxFlameCounter: const Value(365), + lastFlameCounterChange: Value(clock.now()), + maxFlameCounterFrom: Value( + clock.now().subtract(const Duration(days: 1)), + ), + ), + ); + } + await HapticFeedback.heavyImpact(); + }, + ), + if (!kReleaseMode) + ListTile( + title: const Text('Automated Testing'), + onTap: () => + context.push(Routes.settingsDeveloperAutomatedTesting), + ), + ], + ); + }, ), ); } diff --git a/lib/src/views/settings/help/changelog.view.dart b/lib/src/views/settings/help/changelog.view.dart index bd2402f7..57bb0d17 100644 --- a/lib/src/views/settings/help/changelog.view.dart +++ b/lib/src/views/settings/help/changelog.view.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; @@ -73,7 +74,6 @@ class ChangeLogView extends StatefulWidget { class _ChangeLogViewState extends State { String changeLog = ''; - bool hideChangeLog = false; @override void initState() { @@ -87,49 +87,41 @@ class _ChangeLogViewState extends State { Future initAsync() async { changeLog = await rootBundle.loadString('CHANGELOG.md'); - final user = await getUser(); - if (user != null) { - hideChangeLog = user.hideChangeLog; - } - setState(() {}); - } - - Future _toggleAutoOpen(bool value) async { - await updateUserdata((u) { - u.hideChangeLog = !hideChangeLog; - return u; - }); - setState(() { - hideChangeLog = !value; - }); + if (mounted) setState(() {}); } @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Changelog'), - ), - body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(8), - child: ListView( - children: parseMarkdown(context, changeLog), + return StreamBuilder( + stream: AppSession.onUserUpdated, + builder: (context, _) { + return Scaffold( + appBar: AppBar( + title: const Text('Changelog'), ), - ), - ), - bottomNavigationBar: BottomAppBar( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(context.lang.openChangeLog), - Switch( - value: !hideChangeLog, - onChanged: _toggleAutoOpen, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(8), + child: ListView( + children: parseMarkdown(context, changeLog), + ), ), - ], - ), - ), + ), + bottomNavigationBar: BottomAppBar( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(context.lang.openChangeLog), + Switch( + value: !AppSession.currentUser.hideChangeLog, + onChanged: (_) => + updateUser((u) => u.hideChangeLog = !u.hideChangeLog), + ), + ], + ), + ), + ); + }, ); } } diff --git a/lib/src/views/settings/help/help.view.dart b/lib/src/views/settings/help/help.view.dart index 19b671e2..8bbb177d 100644 --- a/lib/src/views/settings/help/help.view.dart +++ b/lib/src/views/settings/help/help.view.dart @@ -19,11 +19,9 @@ class HelpView extends StatefulWidget { class _HelpViewState extends State { Future toggleAllowErrorTrackingViaSentry() async { - await updateUserdata((u) { - u.allowErrorTrackingViaSentry = !u.allowErrorTrackingViaSentry; - return u; - }); - setState(() {}); + await updateUser( + (u) => u.allowErrorTrackingViaSentry = !u.allowErrorTrackingViaSentry, + ); } @override @@ -32,111 +30,115 @@ class _HelpViewState extends State { appBar: AppBar( title: Text(context.lang.settingsHelp), ), - body: ListView( - children: [ - ListTile( - title: Text(context.lang.settingsHelpFAQ), - onTap: () => context.push(Routes.settingsHelpFaq), - ), - ListTile( - title: Text(context.lang.settingsHelpContactUs), - onTap: () => context.push(Routes.settingsHelpContactUs), - ), - const Divider(), - ListTile( - title: Text(context.lang.allowErrorTracking), - subtitle: Text( - context.lang.allowErrorTrackingSubtitle, - style: const TextStyle(fontSize: 10), - ), - onTap: toggleAllowErrorTrackingViaSentry, - trailing: Switch( - value: gUser.allowErrorTrackingViaSentry, - onChanged: (a) => toggleAllowErrorTrackingViaSentry(), - ), - ), - ListTile( - title: Text(context.lang.settingsHelpDiagnostics), - onTap: () => context.push(Routes.settingsHelpDiagnostics), - ), - const Divider(), - if (gUser.userStudyParticipantsToken == null || kDebugMode) - ListTile( - title: const Text('Teilnahme an Nutzerstudie'), - onTap: () => context.push(Routes.settingsHelpUserStudy), - ), - FutureBuilder( - future: PackageInfo.fromPlatform(), - builder: (context, snap) { - if (snap.hasData) { - return ListTile( - title: Text(context.lang.settingsHelpVersion), - subtitle: Text(snap.data!.version), - ); - } else { - return Container(); - } - }, - ), - ListTile( - title: Text(context.lang.settingsHelpLicenses), - onTap: () => showLicensePage(context: context), - ), - ListTile( - title: Text(context.lang.settingsHelpCredits), - onTap: () => context.push(Routes.settingsHelpCredits), - ), - ListTile( - title: const Text('Changelog'), - onTap: () => context.push(Routes.settingsHelpChangelog), - ), - ListTile( - title: const Text('Open Source'), - onTap: () => launchUrl( - Uri.parse('https://github.com/twonlyapp/twonly-app'), - ), - trailing: const FaIcon( - FontAwesomeIcons.arrowUpRightFromSquare, - size: 15, - ), - ), - ListTile( - title: Text(context.lang.settingsHelpImprint), - onTap: () => launchUrl(Uri.parse('https://twonly.eu/de/legal/')), - trailing: const FaIcon( - FontAwesomeIcons.arrowUpRightFromSquare, - size: 15, - ), - ), - ListTile( - title: Text(context.lang.settingsHelpTerms), - onTap: () => - launchUrl(Uri.parse('https://twonly.eu/de/legal/agb.html')), - trailing: const FaIcon( - FontAwesomeIcons.arrowUpRightFromSquare, - size: 15, - ), - ), - ListTile( - onLongPress: () async { - final okay = await showAlertDialog( - context, - 'Developer Settings', - 'Do you want to enable the developer settings?', - ); - if (okay) { - await updateUserdata((u) { - u.isDeveloper = true; - return u; - }); - } - }, - title: const Text( - 'Copyright twonly', - style: TextStyle(color: Colors.grey, fontSize: 13), - ), - ), - ], + body: StreamBuilder( + stream: AppSession.onUserUpdated, + builder: (context, _) { + return ListView( + children: [ + ListTile( + title: Text(context.lang.settingsHelpFAQ), + onTap: () => context.push(Routes.settingsHelpFaq), + ), + ListTile( + title: Text(context.lang.settingsHelpContactUs), + onTap: () => context.push(Routes.settingsHelpContactUs), + ), + const Divider(), + ListTile( + title: Text(context.lang.allowErrorTracking), + subtitle: Text( + context.lang.allowErrorTrackingSubtitle, + style: const TextStyle(fontSize: 10), + ), + onTap: toggleAllowErrorTrackingViaSentry, + trailing: Switch( + value: AppSession.currentUser.allowErrorTrackingViaSentry, + onChanged: (a) => toggleAllowErrorTrackingViaSentry(), + ), + ), + ListTile( + title: Text(context.lang.settingsHelpDiagnostics), + onTap: () => context.push(Routes.settingsHelpDiagnostics), + ), + const Divider(), + if (AppSession.currentUser.userStudyParticipantsToken == null || + kDebugMode) + ListTile( + title: const Text('Teilnahme an Nutzerstudie'), + onTap: () => context.push(Routes.settingsHelpUserStudy), + ), + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, snap) { + if (snap.hasData) { + return ListTile( + title: Text(context.lang.settingsHelpVersion), + subtitle: Text(snap.data!.version), + ); + } else { + return Container(); + } + }, + ), + ListTile( + title: Text(context.lang.settingsHelpLicenses), + onTap: () => showLicensePage(context: context), + ), + ListTile( + title: Text(context.lang.settingsHelpCredits), + onTap: () => context.push(Routes.settingsHelpCredits), + ), + ListTile( + title: const Text('Changelog'), + onTap: () => context.push(Routes.settingsHelpChangelog), + ), + ListTile( + title: const Text('Open Source'), + onTap: () => launchUrl( + Uri.parse('https://github.com/twonlyapp/twonly-app'), + ), + trailing: const FaIcon( + FontAwesomeIcons.arrowUpRightFromSquare, + size: 15, + ), + ), + ListTile( + title: Text(context.lang.settingsHelpImprint), + onTap: () => + launchUrl(Uri.parse('https://twonly.eu/de/legal/')), + trailing: const FaIcon( + FontAwesomeIcons.arrowUpRightFromSquare, + size: 15, + ), + ), + ListTile( + title: Text(context.lang.settingsHelpTerms), + onTap: () => + launchUrl(Uri.parse('https://twonly.eu/de/legal/agb.html')), + trailing: const FaIcon( + FontAwesomeIcons.arrowUpRightFromSquare, + size: 15, + ), + ), + ListTile( + onLongPress: () async { + final okay = await showAlertDialog( + context, + 'Developer Settings', + 'Do you want to enable the developer settings?', + ); + if (okay) { + await updateUser((u) => u.isDeveloper = true); + } + }, + title: const Text( + 'Copyright twonly', + style: TextStyle(color: Colors.grey, fontSize: 13), + ), + ), + ], + ); + }, ), ); } diff --git a/lib/src/views/settings/privacy.view.dart b/lib/src/views/settings/privacy.view.dart index 89a92209..5bac16c5 100644 --- a/lib/src/views/settings/privacy.view.dart +++ b/lib/src/views/settings/privacy.view.dart @@ -20,22 +20,20 @@ class _PrivacyViewState extends State { Future toggleAuthRequirementOnStartup() async { final isAuth = await authenticateUser( - gUser.screenLockEnabled + AppSession.currentUser.screenLockEnabled ? context.lang.settingsScreenLockAuthMessageDisable : context.lang.settingsScreenLockAuthMessageEnable, ); if (!isAuth) return; - await updateUserdata((u) { + await updateUser((u) { u.screenLockEnabled = !u.screenLockEnabled; - return u; }); setState(() {}); } Future toggleTypingIndicators() async { - await updateUserdata((u) { + await updateUser((u) { u.typingIndicators = !u.typingIndicators; - return u; }); setState(() {}); } @@ -86,7 +84,7 @@ class _PrivacyViewState extends State { subtitle: Text(context.lang.settingsTypingIndicationSubtitle), onTap: toggleTypingIndicators, trailing: Switch( - value: gUser.typingIndicators, + value: AppSession.currentUser.typingIndicators, onChanged: (a) => toggleTypingIndicators(), ), ), @@ -96,7 +94,7 @@ class _PrivacyViewState extends State { subtitle: Text(context.lang.settingsScreenLockSubtitle), onTap: toggleAuthRequirementOnStartup, trailing: Switch( - value: gUser.screenLockEnabled, + value: AppSession.currentUser.screenLockEnabled, onChanged: (a) => toggleAuthRequirementOnStartup(), ), ), diff --git a/lib/src/views/settings/privacy/user_discovery.view.dart b/lib/src/views/settings/privacy/user_discovery.view.dart index 93f9323a..0d910063 100644 --- a/lib/src/views/settings/privacy/user_discovery.view.dart +++ b/lib/src/views/settings/privacy/user_discovery.view.dart @@ -18,9 +18,14 @@ class _UserDiscoverySettingsViewState extends State { appBar: AppBar( title: const Text('Freunde finden'), ), - body: gUser.isUserDiscoveryEnabled - ? UserDiscoveryEnabledComponent(onUpdate: () => setState(() {})) - : UserDiscoveryDisabledComponent(onUpdate: () => setState(() {})), + body: StreamBuilder( + stream: AppSession.onUserUpdated, + builder: (context, _) { + return AppSession.currentUser.isUserDiscoveryEnabled + ? const UserDiscoveryEnabledComponent() + : const UserDiscoveryDisabledComponent(); + }, + ), ); } } diff --git a/lib/src/views/settings/privacy/user_discovery/user_discovery_disabled.component.dart b/lib/src/views/settings/privacy/user_discovery/user_discovery_disabled.component.dart index 36c9ab87..21664ba5 100644 --- a/lib/src/views/settings/privacy/user_discovery/user_discovery_disabled.component.dart +++ b/lib/src/views/settings/privacy/user_discovery/user_discovery_disabled.component.dart @@ -4,9 +4,7 @@ import 'package:twonly/src/themes/light.dart'; import 'package:twonly/src/utils/misc.dart'; class UserDiscoveryDisabledComponent extends StatefulWidget { - const UserDiscoveryDisabledComponent({required this.onUpdate, super.key}); - - final VoidCallback onUpdate; + const UserDiscoveryDisabledComponent({super.key}); @override State createState() => @@ -20,7 +18,6 @@ class _UserDiscoveryDisabledComponentState threshold: 2, minimumRequiredImagesExchanged: 4, ); - widget.onUpdate(); } @override diff --git a/lib/src/views/settings/privacy/user_discovery/user_discovery_enabled.component.dart b/lib/src/views/settings/privacy/user_discovery/user_discovery_enabled.component.dart index 8acc3022..00ad46aa 100644 --- a/lib/src/views/settings/privacy/user_discovery/user_discovery_enabled.component.dart +++ b/lib/src/views/settings/privacy/user_discovery/user_discovery_enabled.component.dart @@ -15,9 +15,7 @@ import 'package:twonly/src/views/components/user_context_menu.component.dart'; import 'package:twonly/src/views/settings/privacy/user_discovery/user_discovery_settings.view.dart'; class UserDiscoveryEnabledComponent extends StatefulWidget { - const UserDiscoveryEnabledComponent({required this.onUpdate, super.key}); - - final VoidCallback onUpdate; + const UserDiscoveryEnabledComponent({super.key}); @override State createState() => @@ -69,9 +67,6 @@ class _UserDiscoveryEnabledComponentState if (ok) { await UserDiscoveryService.disable(); } - - // This will show the DisabledComponent as the gUser has been updated... - widget.onUpdate(); } @override @@ -119,7 +114,7 @@ class _UserDiscoveryEnabledComponentState ), subtitle: (version != null && - (gUser.isDeveloper || !kReleaseMode)) + (AppSession.currentUser.isDeveloper || !kReleaseMode)) ? Text( context.lang.userDiscoveryEnabledVersion( '${version.announcement}.${version.promotion}', @@ -171,7 +166,7 @@ class _UserDiscoveryEnabledComponentState title: Text(context.lang.userDiscoveryActionDisable), onTap: _disableUserDiscovery, ), - if (_version != null && (gUser.isDeveloper || !kReleaseMode)) + if (_version != null && (AppSession.currentUser.isDeveloper || !kReleaseMode)) ListTile( title: Text( context.lang.userDiscoveryEnabledYourVersion( diff --git a/lib/src/views/settings/privacy/user_discovery/user_discovery_settings.view.dart b/lib/src/views/settings/privacy/user_discovery/user_discovery_settings.view.dart index e0cf5c0c..9e306834 100644 --- a/lib/src/views/settings/privacy/user_discovery/user_discovery_settings.view.dart +++ b/lib/src/views/settings/privacy/user_discovery/user_discovery_settings.view.dart @@ -22,26 +22,28 @@ class _UserDiscoverySettingsViewState extends State { @override void initState() { - _minimumRequiredImagesExchanged = gUser.minimumRequiredImagesExchanged; - _userDiscoveryThreshold = gUser.userDiscoveryThreshold; + _minimumRequiredImagesExchanged = + AppSession.currentUser.minimumRequiredImagesExchanged; + _userDiscoveryThreshold = AppSession.currentUser.userDiscoveryThreshold; super.initState(); } Future _saveChanges() async { final requiresNewInitialization = - gUser.userDiscoveryThreshold != _userDiscoveryThreshold; + AppSession.currentUser.userDiscoveryThreshold != + _userDiscoveryThreshold; - await updateUserdata((u) { + await updateUser((u) { u ..minimumRequiredImagesExchanged = _minimumRequiredImagesExchanged ..userDiscoveryThreshold = _userDiscoveryThreshold; - return u; }); if (requiresNewInitialization) { await UserDiscoveryService.initializeOrUpdate( - threshold: gUser.userDiscoveryThreshold, - minimumRequiredImagesExchanged: gUser.minimumRequiredImagesExchanged, + threshold: AppSession.currentUser.userDiscoveryThreshold, + minimumRequiredImagesExchanged: + AppSession.currentUser.minimumRequiredImagesExchanged, ); } if (mounted) Navigator.pop(context); @@ -113,8 +115,9 @@ class _UserDiscoverySettingsViewState extends State { height: 30, ), if (_minimumRequiredImagesExchanged != - gUser.minimumRequiredImagesExchanged || - _userDiscoveryThreshold != gUser.userDiscoveryThreshold) + AppSession.currentUser.minimumRequiredImagesExchanged || + _userDiscoveryThreshold != + AppSession.currentUser.userDiscoveryThreshold) Padding( padding: const EdgeInsets.all(17), child: FilledButton( diff --git a/lib/src/views/settings/profile/modify_avatar.view.dart b/lib/src/views/settings/profile/modify_avatar.view.dart index 4fb0f946..c91ebbfd 100644 --- a/lib/src/views/settings/profile/modify_avatar.view.dart +++ b/lib/src/views/settings/profile/modify_avatar.view.dart @@ -23,13 +23,12 @@ class _ModifyAvatarViewState extends State { } Future updateUserAvatar(String json, String svg) async { - await updateUserdata((user) { - user + await updateUser( + (u) => u ..avatarJson = json ..avatarSvg = svg - ..avatarCounter = user.avatarCounter + 1; - return user; - }); + ..avatarCounter = u.avatarCounter + 1, + ); } AvatarMakerThemeData getAvatarMakerTheme(BuildContext context) { @@ -121,7 +120,8 @@ class _ModifyAvatarViewState extends State { canPop: false, onPopInvokedWithResult: (didPop, result) async { if (didPop) return; - if (_avatarMakerController.getJsonOptionsSync() != gUser.avatarJson) { + if (_avatarMakerController.getJsonOptionsSync() != + AppSession.currentUser.avatarJson) { // there where changes final shouldPop = await _showBackDialog() ?? false; if (context.mounted && shouldPop) { diff --git a/lib/src/views/settings/profile/profile.view.dart b/lib/src/views/settings/profile/profile.view.dart index fa743e29..6b9c05a3 100644 --- a/lib/src/views/settings/profile/profile.view.dart +++ b/lib/src/views/settings/profile/profile.view.dart @@ -47,13 +47,11 @@ class _ProfileViewState extends State { } Future updateUserDisplayName(String displayName) async { - await updateUserdata((user) { - user + await updateUser( + (u) => u ..displayName = displayName - ..avatarCounter = user.avatarCounter + 1; - return user; - }); - if (mounted) setState(() {}); // gUser has updated + ..avatarCounter = u.avatarCounter + 1, + ); } Future _updateUsername(String username) async { @@ -94,13 +92,11 @@ class _ProfileViewState extends State { await removeTwonlySafeFromServer(); unawaited(performTwonlySafeBackup(force: true)); - await updateUserdata((user) { - user + await updateUser( + (u) => u ..username = username - ..avatarCounter = user.avatarCounter + 1; - return user; - }); - setState(() {}); // gUser has updated + ..avatarCounter = u.avatarCounter + 1, + ); } @override @@ -109,99 +105,107 @@ class _ProfileViewState extends State { appBar: AppBar( title: Text(context.lang.settingsProfile), ), - body: ListView( - physics: const BouncingScrollPhysics(), - children: [ - const SizedBox(height: 25), - AvatarMakerAvatar( - backgroundColor: Colors.transparent, - radius: 80, - controller: _avatarMakerController, - ), - const SizedBox(height: 10), - Center( - child: SizedBox( - height: 35, - child: ElevatedButton.icon( - icon: const Icon(Icons.edit), - label: Text(context.lang.settingsProfileCustomizeAvatar), - onPressed: () async { - await context.push(Routes.settingsProfileModifyAvatar); - await _avatarMakerController.performRestore(); - setState(() {}); + body: StreamBuilder( + stream: AppSession.onUserUpdated, + builder: (context, _) { + return ListView( + physics: const BouncingScrollPhysics(), + children: [ + const SizedBox(height: 25), + AvatarMakerAvatar( + backgroundColor: Colors.transparent, + radius: 80, + controller: _avatarMakerController, + ), + const SizedBox(height: 10), + Center( + child: SizedBox( + height: 35, + child: ElevatedButton.icon( + icon: const Icon(Icons.edit), + label: Text(context.lang.settingsProfileCustomizeAvatar), + onPressed: () async { + await context.push(Routes.settingsProfileModifyAvatar); + await _avatarMakerController.performRestore(); + }, + ), + ), + ), + const SizedBox(height: 20), + const Divider(), + BetterListTile( + leading: const Padding( + padding: EdgeInsets.only(right: 5, left: 1), + child: FaIcon( + FontAwesomeIcons.qrcode, + size: 20, + ), + ), + onTap: () => context.push(Routes.settingsPublicProfile), + text: context.lang.profileYourQrCode, + ), + BetterListTile( + leading: const Padding( + padding: EdgeInsets.only(right: 5, left: 1), + child: FaIcon( + FontAwesomeIcons.at, + size: 20, + ), + ), + text: context.lang.registerUsernameDecoration, + subtitle: Text(AppSession.currentUser.username), + onTap: () async { + final username = await showDisplayNameChangeDialog( + context, + AppSession.currentUser.username, + context.lang.registerUsernameDecoration, + context.lang.registerUsernameDecoration, + maxLength: 12, + inputFormatters: [ + LengthLimitingTextInputFormatter(12), + FilteringTextInputFormatter.allow( + RegExp('[a-z0-9A-Z._]'), + ), + ], + ); + if (context.mounted && username != null && username != '') { + await _updateUsername(username); + } }, ), - ), - ), - const SizedBox(height: 20), - const Divider(), - BetterListTile( - leading: const Padding( - padding: EdgeInsets.only(right: 5, left: 1), - child: FaIcon( - FontAwesomeIcons.qrcode, - size: 20, + BetterListTile( + icon: FontAwesomeIcons.userPen, + text: context.lang.settingsProfileEditDisplayName, + subtitle: Text(AppSession.currentUser.displayName), + onTap: () async { + final displayName = await showDisplayNameChangeDialog( + context, + AppSession.currentUser.displayName, + context.lang.settingsProfileEditDisplayName, + context.lang.settingsProfileEditDisplayNameNew, + maxLength: 30, + ); + if (context.mounted && + displayName != null && + displayName != '') { + await updateUserDisplayName(displayName); + } + }, ), - ), - onTap: () => context.push(Routes.settingsPublicProfile), - text: context.lang.profileYourQrCode, - ), - BetterListTile( - leading: const Padding( - padding: EdgeInsets.only(right: 5, left: 1), - child: FaIcon( - FontAwesomeIcons.at, - size: 20, + BetterListTile( + text: context.lang.yourTwonlyScore, + icon: FontAwesomeIcons.trophy, + trailing: Text( + twonlyScore.toString(), + style: TextStyle( + color: context.color.primary, + fontSize: 18, + ), + ), ), - ), - text: context.lang.registerUsernameDecoration, - subtitle: Text(gUser.username), - onTap: () async { - final username = await showDisplayNameChangeDialog( - context, - gUser.username, - context.lang.registerUsernameDecoration, - context.lang.registerUsernameDecoration, - maxLength: 12, - inputFormatters: [ - LengthLimitingTextInputFormatter(12), - FilteringTextInputFormatter.allow(RegExp('[a-z0-9A-Z._]')), - ], - ); - if (context.mounted && username != null && username != '') { - await _updateUsername(username); - } - }, - ), - BetterListTile( - icon: FontAwesomeIcons.userPen, - text: context.lang.settingsProfileEditDisplayName, - subtitle: Text(gUser.displayName), - onTap: () async { - final displayName = await showDisplayNameChangeDialog( - context, - gUser.displayName, - context.lang.settingsProfileEditDisplayName, - context.lang.settingsProfileEditDisplayNameNew, - maxLength: 30, - ); - if (context.mounted && displayName != null && displayName != '') { - await updateUserDisplayName(displayName); - } - }, - ), - BetterListTile( - text: context.lang.yourTwonlyScore, - icon: FontAwesomeIcons.trophy, - trailing: Text( - twonlyScore.toString(), - style: TextStyle( - color: context.color.primary, - fontSize: 18, - ), - ), - ), - ], + ], + ); + }, ), ); } diff --git a/lib/src/views/settings/settings_main.view.dart b/lib/src/views/settings/settings_main.view.dart index 47a11a4f..b16c1e64 100644 --- a/lib/src/views/settings/settings_main.view.dart +++ b/lib/src/views/settings/settings_main.view.dart @@ -47,12 +47,12 @@ class _SettingsMainViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - substringBy(gUser.displayName, 27), + substringBy(AppSession.currentUser.displayName, 27), style: const TextStyle(fontSize: 20), textAlign: TextAlign.left, ), Text( - gUser.username, + AppSession.currentUser.username, style: const TextStyle( fontSize: 14, ), @@ -124,11 +124,11 @@ class _SettingsMainViewState extends State { onTap: () async { await context.push(Routes.settingsHelp); setState(() { - // gUser could have been changed + // AppSession.currentUser could have been changed }); }, ), - if (gUser.isDeveloper) + if (AppSession.currentUser.isDeveloper) BetterListTile( icon: FontAwesomeIcons.code, text: 'Developer Settings', diff --git a/lib/src/views/settings/subscription/subscription.view.dart b/lib/src/views/settings/subscription/subscription.view.dart index 12839923..da53fc85 100644 --- a/lib/src/views/settings/subscription/subscription.view.dart +++ b/lib/src/views/settings/subscription/subscription.view.dart @@ -290,7 +290,7 @@ class _PlanCardState extends State { var url = 'https://apps.apple.com/account/subscriptions'; if (Platform.isAndroid) { url = - 'https://play.google.com/store/account/subscriptions?sku=${gUser.subscriptionPlanIdStore}&package=eu.twonly'; + 'https://play.google.com/store/account/subscriptions?sku=${AppSession.currentUser.subscriptionPlanIdStore}&package=eu.twonly'; } await launchUrl( Uri.parse(url), diff --git a/lib/src/views/settings/subscription_custom/subscription.view.dart b/lib/src/views/settings/subscription_custom/subscription.view.dart index 4867d27c..062b22a0 100644 --- a/lib/src/views/settings/subscription_custom/subscription.view.dart +++ b/lib/src/views/settings/subscription_custom/subscription.view.dart @@ -39,10 +39,7 @@ String localePrizing(BuildContext context, int cents) { Future loadPlanBalance({bool useCache = true}) async { final ballance = await apiService.getPlanBallance(); if (ballance != null) { - await updateUserdata((u) { - u.lastPlanBallance = ballance.writeToJson(); - return u; - }); + await updateUser((u) => u.lastPlanBallance = ballance.writeToJson()); return ballance; } final user = await getUser(); diff --git a/lib/src/views/user_study/user_study_data_collection.dart b/lib/src/views/user_study/user_study_data_collection.dart index 1b576ee8..d5020d82 100644 --- a/lib/src/views/user_study/user_study_data_collection.dart +++ b/lib/src/views/user_study/user_study_data_collection.dart @@ -15,7 +15,7 @@ const surveyUrlBase = 'https://survey.twonly.org/upload.php'; Future handleUserStudyUpload() async { try { - final token = gUser.userStudyParticipantsToken; + final token = AppSession.currentUser.userStudyParticipantsToken; if (token == null) return; // in case the survey was taken offline try again @@ -35,8 +35,8 @@ Future handleUserStudyUpload() async { await KeyValueStore.delete(userStudySurveyKey); } - if (gUser.lastUserStudyDataUpload != null && - isToday(gUser.lastUserStudyDataUpload!)) { + if (AppSession.currentUser.lastUserStudyDataUpload != null && + isToday(AppSession.currentUser.lastUserStudyDataUpload!)) { // Only send updates once a day. // This enables to see if improvements to actually work. return; @@ -56,18 +56,16 @@ Future handleUserStudyUpload() async { headers: {'Content-Type': 'application/json'}, ); if (response.statusCode == 200) { - await updateUserdata((u) { + await updateUser((u) { u.lastUserStudyDataUpload = DateTime.now(); - return u; }); } if (response.statusCode == 404) { // Token is unknown to the server... - await updateUserdata((u) { + await updateUser((u) { u ..lastUserStudyDataUpload = null ..userStudyParticipantsToken = null; - return u; }); } } catch (e) { diff --git a/lib/src/views/user_study/user_study_questionnaire.view.dart b/lib/src/views/user_study/user_study_questionnaire.view.dart index 96cbc9dd..493331de 100644 --- a/lib/src/views/user_study/user_study_questionnaire.view.dart +++ b/lib/src/views/user_study/user_study_questionnaire.view.dart @@ -50,12 +50,11 @@ class _UserStudyQuestionnaireViewState Future _submitData() async { await KeyValueStore.put(userStudySurveyKey, _responses); - await updateUserdata((u) { + await updateUser((u) { // generate a random participants id to identify data send later while keeping the user anonym u ..userStudyParticipantsToken = getRandomString(25) ..askedForUserStudyPermission = true; - return u; }); await handleUserStudyUpload(); diff --git a/lib/src/views/user_study/user_study_welcome.view.dart b/lib/src/views/user_study/user_study_welcome.view.dart index 4598d013..9de6d907 100644 --- a/lib/src/views/user_study/user_study_welcome.view.dart +++ b/lib/src/views/user_study/user_study_welcome.view.dart @@ -86,10 +86,9 @@ class _UserStudyWelcomeViewState extends State { Center( child: GestureDetector( onTap: () async { - await updateUserdata((u) { - u.askedForUserStudyPermission = true; - return u; - }); + await updateUser( + (u) => u.askedForUserStudyPermission = true, + ); if (context.mounted) context.pop(); }, child: const Text(