refactor global user variable

This commit is contained in:
otsmr 2026-04-21 03:34:52 +02:00
parent e945e30991
commit 3d35615136
68 changed files with 864 additions and 886 deletions

View file

@ -131,9 +131,9 @@ class _AppMainWidgetState extends State<AppMainWidget> {
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<AppMainWidget> {
_isTwonlyLocked = false;
}),
);
} else if (gUser.twonlySafeBackup == null && !_skipBackup) {
} else if (AppSession.currentUser.twonlySafeBackup == null && !_skipBackup) {
child = SetupBackupView(
callBack: () {
_skipBackup = true;

View file

@ -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<void>.broadcast();
static final _userDataUpdateController = StreamController<void>.broadcast();
static Stream<void> get onUserUpdated => _userDataUpdateController.stream;
static void triggerUserUpdate() {
_userDataUpdateController.add(null);
}
}

View file

@ -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;
});
}
}

View file

@ -140,7 +140,7 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
t.userDiscoveryVersion.isNotNull() &
t.userDiscoveryExcluded.equals(false) &
t.mediaSendCounter.isBiggerOrEqualValue(
gUser.minimumRequiredImagesExchanged,
AppSession.currentUser.minimumRequiredImagesExchanged,
),
))
.watch();

View file

@ -113,7 +113,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> 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<TwonlyDB> with _$GroupsDaoMixin {
}
Stream<Group?> 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();

View file

@ -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;
});
}
}

View file

@ -62,13 +62,15 @@ class ApiService {
Stream<SubscriptionPlan> get onPlanUpdated => _planUpdateController.stream;
final _connectionStateController = StreamController<bool>.broadcast();
Stream<bool> get onConnectionStateUpdated => _connectionStateController.stream;
Stream<bool> get onConnectionStateUpdated =>
_connectionStateController.stream;
final _appOutdatedController = StreamController<void>.broadcast();
Stream<void> get onAppOutdated => _appOutdatedController.stream;
final _newDeviceRegisteredController = StreamController<void>.broadcast();
Stream<void> get onNewDeviceRegistered => _newDeviceRegisteredController.stream;
Stream<void> 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<Response_PlanBallance?> 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;
}

View file

@ -161,7 +161,7 @@ Future<void> 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...

View file

@ -34,17 +34,17 @@ Future<void> 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<void> 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;
}

View file

@ -96,7 +96,7 @@ Future<bool> 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) {

View file

@ -357,7 +357,7 @@ Future<void> 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) {

View file

@ -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<void> 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<void> notifyContactAboutOpeningMessage(
Future<void> sendContactMyProfileData(int contactId) async {
List<int>? 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);

View file

@ -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();

View file

@ -57,7 +57,7 @@ Future<bool> 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<bool> initBackgroundExecution() async {
final user = await getUser();
if (user == null) return false;
gUser = user;
AppSession.currentUser = user;
twonlyDB = TwonlyDB();
apiService = ApiService();

View file

@ -13,22 +13,24 @@ import 'package:twonly/src/utils/storage.dart';
Future<void> 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<void> removeTwonlySafeFromServer() async {
final serverUrl = await getTwonlySafeBackupUrl();
if (serverUrl != null) {
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),
@ -41,7 +43,6 @@ Future<void> removeTwonlySafeFromServer() async {
} catch (e) {
Log.error('Could not connect upload the backup.');
}
}
}
Future<(Uint8List, Uint8List)> getMasterKey(
@ -63,19 +64,18 @@ Future<(Uint8List, Uint8List)> getMasterKey(
return (key.sublist(0, 32), key.sublist(32, 64));
}
Future<String?> 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<String?> getTwonlySafeBackupUrlFromServer(
String? getTwonlySafeBackupUrlFromServer(
List<int> backupId,
BackupServer? backupServer,
) async {
) {
var backupServerUrl = 'https://safe.twonly.eu/';
if (backupServer != null) {

View file

@ -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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> performTwonlySafeBackup({bool force = false}) async {
Future<void> 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();
}

View file

@ -110,8 +110,7 @@ Future<void> handleBackupData(
key: SecureStorageKeys.userData,
value: secureStorage[SecureStorageKeys.userData] as String,
);
await updateUserdata((u) {
await updateUser((u) {
u.deviceId += 1;
return u;
});
}

View file

@ -17,10 +17,9 @@ Future<void> 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;
});
}

View file

@ -42,8 +42,8 @@ Future<bool> createNewGroup(String groupName, List<Contact> 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<bool> 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<bool> leaveAsNonAdminFromGroup(Group group) async {
EncryptedContent(
groupUpdate: EncryptedContent_GroupUpdate(
groupActionType: groupActionType.name,
affectedContactId: Int64(gUser.userId),
affectedContactId: Int64(AppSession.currentUser.userId),
),
),
);

View file

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

View file

@ -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 {

View file

@ -48,9 +48,8 @@ Future<void> 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<void> 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<void> checkForTokenUpdates() async {
}
Future<void> 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!');

View file

@ -20,9 +20,10 @@ Future<IdentityKeyPair?> getSignalIdentityKeyPair() async {
// This function runs after the clients authenticated with the server.
// It then checks if it should update a new session key
Future<void> 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(
final isYoungerThan48Hours =
(AppSession.currentUser.signalLastSignedPreKeyUpdated!).isAfter(
fortyEightHoursAgo,
);
if (isYoungerThan48Hours) {
@ -35,9 +36,8 @@ Future<void> 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<void> 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<List<PreKeyRecord>> 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<SignedPreKeyRecord?> _getNewSignalSignedPreKey() async {
}
final signedPreKeyId = user.currentSignedPreKeyIndexStart;
await updateUserdata((user) {
await updateUser((user) {
user.currentSignedPreKeyIndexStart += 1;
return user;
});
final signedPreKey = generateSignedPreKey(

View file

@ -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);
}

View file

@ -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<void> disable() async {
await updateUserdata((u) {
await updateUser((u) {
u.isUserDiscoveryEnabled = false;
return u;
});
}
}

View file

@ -47,13 +47,13 @@ File avatarPNGFile(int contactId) {
}
Future<Uint8List> 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<Uint8List> 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();

View file

@ -17,8 +17,8 @@ Future<Uint8List> 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(),

View file

@ -16,7 +16,7 @@ Future<bool> isUserCreated() async {
if (user == null) {
return false;
}
gUser = user;
AppSession.currentUser = user;
return true;
}
@ -43,9 +43,8 @@ Future<void> updateUsersPlan(
) async {
context.read<PurchasesProvider>().plan = plan;
await updateUserdata((user) {
await updateUser((user) {
user.subscriptionPlan = plan.name;
return user;
});
if (!context.mounted) return;
@ -54,27 +53,25 @@ Future<void> updateUsersPlan(
Mutex updateProtection = Mutex();
Future<UserData?> updateUserdata(
UserData Function(UserData userData) updateUser,
Future<void> updateUser(
void Function(UserData userData) updateUser,
) async {
final userData = await updateProtection.protect<UserData?>(() 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<bool> deleteLocalUserData() async {

View file

@ -208,10 +208,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
Future<void> 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<CameraPreviewView> {
((videoFilePath != null) ? MediaType.video : MediaType.image);
final mediaFileService = await initializeMediaUpload(
type,
gUser.defaultShowTime,
AppSession.currentUser.defaultShowTime,
isDraftMedia: true,
);
if (!mounted) return true;

View file

@ -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(

View file

@ -254,7 +254,7 @@ class _ShareImageView extends State<ShareImageView> {
children: [
if (widget.mediaFileService.mediaFile.type == MediaType.image &&
_screenshotImage?.image != null &&
gUser.showShowImagePreviewWhenSending)
AppSession.currentUser.showShowImagePreviewWhenSending)
SizedBox(
height: 100,
width: 100 * 9 / 16,

View file

@ -158,9 +158,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
if (!mounted) return;
setState(() {});
if (storeAsDefault) {
await updateUserdata((user) {
await updateUser((user) {
user.defaultShowTime = maxShowTime;
return user;
});
}
}

View file

@ -92,7 +92,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
}
Future<void> _requestNewUserByUsername(String username) async {
if (gUser.username == username) return;
if (AppSession.currentUser.username == username) return;
setState(() {
_isLoading = true;

View file

@ -27,6 +27,7 @@ class ChatListView extends StatefulWidget {
}
class _ChatListViewState extends State<ChatListView> {
StreamSubscription<void>? _userSub;
late StreamSubscription<List<Group>> _contactsSub;
List<Group> _groupsNotPinned = [];
List<Group> _groupsPinned = [];
@ -43,6 +44,9 @@ class _ChatListViewState extends State<ChatListView> {
@override
void initState() {
initAsync();
_userSub = AppSession.onUserUpdated.listen((_) {
if (mounted) setState(() {});
});
super.initState();
}
@ -85,11 +89,11 @@ class _ChatListViewState extends State<ChatListView> {
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<ChatListView> {
_contactsSub.cancel();
_countContactRequestStream.cancel();
_countAnnouncedStream.cancel();
_userSub?.cancel();
super.dispose();
}
@ -122,9 +127,7 @@ class _ChatListViewState extends State<ChatListView> {
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<ChatListView> {
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),
),

View file

@ -122,7 +122,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
_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<ChatMessagesView> {
itemScrollController: itemScrollController,
itemBuilder: (context, i) {
if (i == 0) {
return gUser.typingIndicators
return AppSession.currentUser.typingIndicators
? TypingIndicator(group: group)
: Container();
}

View file

@ -102,7 +102,7 @@ class _ContactRowState extends State<_ContactRow> {
bool _isLoading = false;
Future<void> _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),

View file

@ -72,7 +72,7 @@ class _MessageInputState extends State<MessageInput> {
_textFieldController.text = widget.group.draftMessage!;
}
widget.textFieldFocus.addListener(_handleTextFocusChange);
if (gUser.typingIndicators) {
if (AppSession.currentUser.typingIndicators) {
_nextTypingIndicator = Timer.periodic(const Duration(seconds: 1), (
_,
) async {

View file

@ -50,8 +50,8 @@ class _ReactionButtonsState extends State<ReactionButtons> {
}
Future<void> initAsync() async {
if (gUser.preSelectedEmojies != null) {
selectedEmojis = gUser.preSelectedEmojies!;
if (AppSession.currentUser.preSelectedEmojies != null) {
selectedEmojis = AppSession.currentUser.preSelectedEmojies!;
}
setState(() {});
}

View file

@ -33,7 +33,7 @@ class _AvatarIconState extends State<AvatarIcon> {
StreamSubscription<List<Contact>>? groupStream;
StreamSubscription<List<Contact>>? contactsStream;
StreamSubscription<Contact?>? contactStream;
StreamSubscription<void>? _userDataSub;
StreamSubscription<void>? _userSub;
@override
void initState() {
@ -46,7 +46,7 @@ class _AvatarIconState extends State<AvatarIcon> {
groupStream?.cancel();
contactStream?.cancel();
contactsStream?.cancel();
_userDataSub?.cancel();
_userSub?.cancel();
super.dispose();
}
@ -93,19 +93,19 @@ class _AvatarIconState extends State<AvatarIcon> {
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

View file

@ -48,7 +48,7 @@ class _FlameCounterWidgetState extends State<FlameCounterWidget> {
}
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) {

View file

@ -38,7 +38,10 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
@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<MaxFlameListTitle> {
}
Future<void> _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;

View file

@ -204,7 +204,7 @@ class _ContactViewState extends State<ContactView> {
},
),
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<ContactView> {
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),
),

View file

@ -142,7 +142,7 @@ class _GroupViewState extends State<GroupView> {
success = await removeMemberFromGroup(
_group!,
keyPair.getPublicKey().serialize(),
gUser.userId,
AppSession.currentUser.userId,
);
} else {
success = await leaveAsNonAdminFromGroup(_group!);

View file

@ -135,7 +135,7 @@ class HomeViewState extends State<HomeView> {
_mainCameraController.setSharedLinkForPreview,
);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.initialPage == 1 && !gUser.startWithCameraOpen ||
if (widget.initialPage == 1 && !AppSession.currentUser.startWithCameraOpen ||
widget.initialPage == 0) {
globalUpdateOfHomeViewPageIndex(0);
}

View file

@ -98,7 +98,7 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> {
final newMediaService = await initializeMediaUpload(
orgMediaService.mediaFile.type,
gUser.defaultShowTime,
AppSession.currentUser.defaultShowTime,
);
if (newMediaService == null) {
Log.error('Could not create new mediaFIle');

View file

@ -142,7 +142,7 @@ class _RegisterViewState extends State<RegisterView> {
value: jsonEncode(userData),
);
gUser = userData;
AppSession.currentUser = userData;
await apiService.authenticate();
widget.callbackOnSuccess();

View file

@ -107,7 +107,7 @@ class _PublicProfileViewState extends State<PublicProfileView> {
),
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<PublicProfileView> {
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);
},

View file

@ -73,32 +73,20 @@ class _AppearanceViewState extends State<AppearanceView> {
}
Future<void> toggleShowFeedbackIcon() async {
await updateUserdata((u) {
await updateUser((u) {
u.showFeedbackShortcut = !u.showFeedbackShortcut;
return u;
});
setState(() {
// gUser
});
}
Future<void> toggleStartWithCameraOpen() async {
await updateUserdata((u) {
await updateUser((u) {
u.startWithCameraOpen = !u.startWithCameraOpen;
return u;
});
setState(() {
// gUser
});
}
Future<void> toggleShowImagePreviewWhenSending() async {
await updateUserdata((u) {
await updateUser((u) {
u.showShowImagePreviewWhenSending = !u.showShowImagePreviewWhenSending;
return u;
});
setState(() {
// gUser
});
}
@ -109,7 +97,10 @@ class _AppearanceViewState extends State<AppearanceView> {
appBar: AppBar(
title: Text(context.lang.settingsAppearance),
),
body: ListView(
body: StreamBuilder<void>(
stream: AppSession.onUserUpdated,
builder: (context, snapshot) {
return ListView(
children: [
ListTile(
title: Text(context.lang.settingsAppearanceTheme),
@ -125,7 +116,7 @@ class _AppearanceViewState extends State<AppearanceView> {
title: Text(context.lang.contactUsShortcut),
onTap: toggleShowFeedbackIcon,
trailing: Switch(
value: !gUser.showFeedbackShortcut,
value: !AppSession.currentUser.showFeedbackShortcut,
onChanged: (a) => toggleShowFeedbackIcon(),
),
),
@ -133,7 +124,7 @@ class _AppearanceViewState extends State<AppearanceView> {
title: Text(context.lang.startWithCameraOpen),
onTap: toggleStartWithCameraOpen,
trailing: Switch(
value: gUser.startWithCameraOpen,
value: AppSession.currentUser.startWithCameraOpen,
onChanged: (a) => toggleStartWithCameraOpen(),
),
),
@ -141,11 +132,13 @@ class _AppearanceViewState extends State<AppearanceView> {
title: Text(context.lang.showImagePreviewWhenSending),
onTap: toggleShowImagePreviewWhenSending,
trailing: Switch(
value: gUser.showShowImagePreviewWhenSending,
value: AppSession.currentUser.showShowImagePreviewWhenSending,
onChanged: (a) => toggleShowImagePreviewWhenSending(),
),
),
],
);
},
),
);
}

View file

@ -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<BackupView> {
void initState() {
super.initState();
unawaited(initAsync());
gUpdateBackupView = initAsync;
}
@override
void dispose() {
gUpdateBackupView = () {};
super.dispose();
}
Future<void> initAsync() async {
setState(() {});
}
Future<void> initAsync() async {}
String backupStatus(LastBackupUploadState status) {
switch (status) {
@ -62,14 +51,15 @@ class _BackupViewState extends State<BackupView> {
Future<void> changeTwonlySafePassword() async {
await context.push(Routes.settingsBackupSetup, extra: true);
setState(() {
// gUser was updated
});
}
@override
Widget build(BuildContext context) {
final backupServer = gUser.backupServer ?? defaultBackupServer;
return StreamBuilder<void>(
stream: AppSession.onUserUpdated,
builder: (context, _) {
final backupServer =
AppSession.currentUser.backupServer ?? defaultBackupServer;
return Scaffold(
appBar: AppBar(
title: Text(context.lang.settingsBackup),
@ -89,7 +79,7 @@ class _BackupViewState extends State<BackupView> {
onPressed: changeTwonlySafePassword,
child: Text(context.lang.backupChangePassword),
),
child: (gUser.twonlySafeBackup == null)
child: (AppSession.currentUser.twonlySafeBackup == null)
? null
: Column(
children: [
@ -119,19 +109,28 @@ class _BackupViewState extends State<BackupView> {
context.lang.backupLastBackupDate,
formatDateTime(
context,
gUser.twonlySafeBackup!.lastBackupDone,
AppSession
.currentUser
.twonlySafeBackup!
.lastBackupDone,
),
),
(
context.lang.backupLastBackupSize,
formatBytes(
gUser.twonlySafeBackup!.lastBackupSize,
AppSession
.currentUser
.twonlySafeBackup!
.lastBackupSize,
),
),
(
context.lang.backupLastBackupResult,
backupStatus(
gUser.twonlySafeBackup!.backupUploadState,
AppSession
.currentUser
.twonlySafeBackup!
.backupUploadState,
),
),
].map((pair) {
@ -185,7 +184,9 @@ class _BackupViewState extends State<BackupView> {
showSelectedLabels: true,
showUnselectedLabels: true,
unselectedIconTheme: IconThemeData(
color: Theme.of(context).colorScheme.inverseSurface.withAlpha(150),
color: Theme.of(
context,
).colorScheme.inverseSurface.withAlpha(150),
),
selectedIconTheme: IconThemeData(
color: Theme.of(context).colorScheme.inverseSurface,
@ -213,6 +214,8 @@ class _BackupViewState extends State<BackupView> {
// ),
),
);
},
);
}
}

View file

@ -31,8 +31,8 @@ class _BackupServerViewState extends State<BackupServerView> {
}
Future<void> 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<BackupServerView> {
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<BackupServerView> {
Center(
child: OutlinedButton(
onPressed: () async {
await updateUserdata((user) {
await updateUser((user) {
user.backupServer = null;
return user;
});
if (context.mounted) Navigator.pop(context);
},

View file

@ -38,9 +38,8 @@ class _ChatReactionSelectionView extends State<ChatReactionSelectionView> {
} 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<ChatReactionSelectionView> {
6,
);
setState(() {});
await updateUserdata((user) {
await updateUser((user) {
user.preSelectedEmojies = selectedEmojis;
return user;
});
},
child: const Icon(Icons.settings_backup_restore_rounded),

View file

@ -27,49 +27,50 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
builder: (context) {
return AutoDownloadOptionsDialog(
autoDownloadOptions:
gUser.autoDownloadOptions ?? defaultAutoDownloadOptions,
AppSession.currentUser.autoDownloadOptions ??
defaultAutoDownloadOptions,
connectionMode: connectionMode,
onUpdate: () async {
setState(() {});
},
onUpdate: () {},
);
},
);
}
Future<void> toggleStoreInGallery() async {
await updateUserdata((u) {
await updateUser((u) {
u.storeMediaFilesInGallery = !u.storeMediaFilesInGallery;
return u;
});
setState(() {});
}
Future<void> 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(
body: StreamBuilder<void>(
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),
subtitle: Text(
context.lang.settingsStorageDataStoreInGSubtitle,
),
onTap: toggleStoreInGallery,
trailing: Switch(
value: gUser.storeMediaFilesInGallery,
value: AppSession.currentUser.storeMediaFilesInGallery,
onChanged: (a) => toggleStoreInGallery(),
),
),
@ -81,7 +82,9 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
),
onTap: toggleAutoStoreMediaFiles,
trailing: Switch(
value: gUser.autoStoreAllSendUnlimitedMediaFiles,
value: AppSession
.currentUser
.autoStoreAllSendUnlimitedMediaFiles,
onChanged: (a) => toggleAutoStoreMediaFiles(),
),
),
@ -115,7 +118,10 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
style: const TextStyle(color: Colors.grey),
),
onTap: () async {
await showAutoDownloadOptions(context, ConnectivityResult.mobile);
await showAutoDownloadOptions(
context,
ConnectivityResult.mobile,
);
},
),
ListTile(
@ -127,10 +133,15 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
style: const TextStyle(color: Colors.grey),
),
onTap: () async {
await showAutoDownloadOptions(context, ConnectivityResult.wifi);
await showAutoDownloadOptions(
context,
ConnectivityResult.wifi,
);
},
),
],
);
},
),
);
}
@ -215,9 +226,8 @@ class _AutoDownloadOptionsDialogState extends State<AutoDownloadOptionsDialog> {
// Call the onUpdate callback to notify the parent widget
await updateUserdata((u) {
await updateUser((u) {
u.autoDownloadOptions = autoDownloadOptions;
return u;
});
widget.onUpdate();

View file

@ -26,19 +26,13 @@ class _DeveloperSettingsViewState extends State<DeveloperSettingsView> {
}
Future<void> toggleDeveloperSettings() async {
await updateUserdata((u) {
u.isDeveloper = !u.isDeveloper;
return u;
});
setState(() {});
await updateUser((u) => u.isDeveloper = !u.isDeveloper);
}
Future<void> toggleVideoStabilization() async {
await updateUserdata((u) {
u.videoStabilizationEnabled = !u.videoStabilizationEnabled;
return u;
});
setState(() {});
await updateUser(
(u) => u.videoStabilizationEnabled = !u.videoStabilizationEnabled,
);
}
@override
@ -47,26 +41,30 @@ class _DeveloperSettingsViewState extends State<DeveloperSettingsView> {
appBar: AppBar(
title: const Text('Developer Settings'),
),
body: ListView(
body: StreamBuilder<void>(
stream: AppSession.onUserUpdated,
builder: (context, _) {
return ListView(
children: [
ListTile(
title: const Text('Show Developer Settings'),
onTap: toggleDeveloperSettings,
trailing: Switch(
value: gUser.isDeveloper,
onChanged: (a) => toggleDeveloperSettings(),
value: AppSession.currentUser.isDeveloper,
onChanged: (_) => toggleDeveloperSettings(),
),
),
ListTile(
title: const Text('Show Retransmission Database'),
onTap: () =>
context.push(Routes.settingsDeveloperRetransmissionDatabase),
onTap: () => context.push(
Routes.settingsDeveloperRetransmissionDatabase,
),
),
ListTile(
title: const Text('Toggle Video Stabilization'),
onTap: toggleVideoStabilization,
trailing: Switch(
value: gUser.videoStabilizationEnabled,
value: AppSession.currentUser.videoStabilizationEnabled,
onChanged: (a) => toggleVideoStabilization(),
),
),
@ -121,6 +119,8 @@ class _DeveloperSettingsViewState extends State<DeveloperSettingsView> {
context.push(Routes.settingsDeveloperAutomatedTesting),
),
],
);
},
),
);
}

View file

@ -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<ChangeLogView> {
String changeLog = '';
bool hideChangeLog = false;
@override
void initState() {
@ -87,25 +87,14 @@ class _ChangeLogViewState extends State<ChangeLogView> {
Future<void> initAsync() async {
changeLog = await rootBundle.loadString('CHANGELOG.md');
final user = await getUser();
if (user != null) {
hideChangeLog = user.hideChangeLog;
}
setState(() {});
}
Future<void> _toggleAutoOpen(bool value) async {
await updateUserdata((u) {
u.hideChangeLog = !hideChangeLog;
return u;
});
setState(() {
hideChangeLog = !value;
});
if (mounted) setState(() {});
}
@override
Widget build(BuildContext context) {
return StreamBuilder<void>(
stream: AppSession.onUserUpdated,
builder: (context, _) {
return Scaffold(
appBar: AppBar(
title: const Text('Changelog'),
@ -124,12 +113,15 @@ class _ChangeLogViewState extends State<ChangeLogView> {
children: [
Text(context.lang.openChangeLog),
Switch(
value: !hideChangeLog,
onChanged: _toggleAutoOpen,
value: !AppSession.currentUser.hideChangeLog,
onChanged: (_) =>
updateUser((u) => u.hideChangeLog = !u.hideChangeLog),
),
],
),
),
);
},
);
}
}

View file

@ -19,11 +19,9 @@ class HelpView extends StatefulWidget {
class _HelpViewState extends State<HelpView> {
Future<void> toggleAllowErrorTrackingViaSentry() async {
await updateUserdata((u) {
u.allowErrorTrackingViaSentry = !u.allowErrorTrackingViaSentry;
return u;
});
setState(() {});
await updateUser(
(u) => u.allowErrorTrackingViaSentry = !u.allowErrorTrackingViaSentry,
);
}
@override
@ -32,7 +30,10 @@ class _HelpViewState extends State<HelpView> {
appBar: AppBar(
title: Text(context.lang.settingsHelp),
),
body: ListView(
body: StreamBuilder<void>(
stream: AppSession.onUserUpdated,
builder: (context, _) {
return ListView(
children: [
ListTile(
title: Text(context.lang.settingsHelpFAQ),
@ -51,7 +52,7 @@ class _HelpViewState extends State<HelpView> {
),
onTap: toggleAllowErrorTrackingViaSentry,
trailing: Switch(
value: gUser.allowErrorTrackingViaSentry,
value: AppSession.currentUser.allowErrorTrackingViaSentry,
onChanged: (a) => toggleAllowErrorTrackingViaSentry(),
),
),
@ -60,7 +61,8 @@ class _HelpViewState extends State<HelpView> {
onTap: () => context.push(Routes.settingsHelpDiagnostics),
),
const Divider(),
if (gUser.userStudyParticipantsToken == null || kDebugMode)
if (AppSession.currentUser.userStudyParticipantsToken == null ||
kDebugMode)
ListTile(
title: const Text('Teilnahme an Nutzerstudie'),
onTap: () => context.push(Routes.settingsHelpUserStudy),
@ -102,7 +104,8 @@ class _HelpViewState extends State<HelpView> {
),
ListTile(
title: Text(context.lang.settingsHelpImprint),
onTap: () => launchUrl(Uri.parse('https://twonly.eu/de/legal/')),
onTap: () =>
launchUrl(Uri.parse('https://twonly.eu/de/legal/')),
trailing: const FaIcon(
FontAwesomeIcons.arrowUpRightFromSquare,
size: 15,
@ -125,10 +128,7 @@ class _HelpViewState extends State<HelpView> {
'Do you want to enable the developer settings?',
);
if (okay) {
await updateUserdata((u) {
u.isDeveloper = true;
return u;
});
await updateUser((u) => u.isDeveloper = true);
}
},
title: const Text(
@ -137,6 +137,8 @@ class _HelpViewState extends State<HelpView> {
),
),
],
);
},
),
);
}

View file

@ -20,22 +20,20 @@ class _PrivacyViewState extends State<PrivacyView> {
Future<void> 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<void> toggleTypingIndicators() async {
await updateUserdata((u) {
await updateUser((u) {
u.typingIndicators = !u.typingIndicators;
return u;
});
setState(() {});
}
@ -86,7 +84,7 @@ class _PrivacyViewState extends State<PrivacyView> {
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<PrivacyView> {
subtitle: Text(context.lang.settingsScreenLockSubtitle),
onTap: toggleAuthRequirementOnStartup,
trailing: Switch(
value: gUser.screenLockEnabled,
value: AppSession.currentUser.screenLockEnabled,
onChanged: (a) => toggleAuthRequirementOnStartup(),
),
),

View file

@ -18,9 +18,14 @@ class _UserDiscoverySettingsViewState extends State<UserDiscoverySettingsView> {
appBar: AppBar(
title: const Text('Freunde finden'),
),
body: gUser.isUserDiscoveryEnabled
? UserDiscoveryEnabledComponent(onUpdate: () => setState(() {}))
: UserDiscoveryDisabledComponent(onUpdate: () => setState(() {})),
body: StreamBuilder<void>(
stream: AppSession.onUserUpdated,
builder: (context, _) {
return AppSession.currentUser.isUserDiscoveryEnabled
? const UserDiscoveryEnabledComponent()
: const UserDiscoveryDisabledComponent();
},
),
);
}
}

View file

@ -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<UserDiscoveryDisabledComponent> createState() =>
@ -20,7 +18,6 @@ class _UserDiscoveryDisabledComponentState
threshold: 2,
minimumRequiredImagesExchanged: 4,
);
widget.onUpdate();
}
@override

View file

@ -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<UserDiscoveryEnabledComponent> 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(

View file

@ -22,26 +22,28 @@ class _UserDiscoverySettingsViewState extends State<UserDiscoverySettingsView> {
@override
void initState() {
_minimumRequiredImagesExchanged = gUser.minimumRequiredImagesExchanged;
_userDiscoveryThreshold = gUser.userDiscoveryThreshold;
_minimumRequiredImagesExchanged =
AppSession.currentUser.minimumRequiredImagesExchanged;
_userDiscoveryThreshold = AppSession.currentUser.userDiscoveryThreshold;
super.initState();
}
Future<void> _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<UserDiscoverySettingsView> {
height: 30,
),
if (_minimumRequiredImagesExchanged !=
gUser.minimumRequiredImagesExchanged ||
_userDiscoveryThreshold != gUser.userDiscoveryThreshold)
AppSession.currentUser.minimumRequiredImagesExchanged ||
_userDiscoveryThreshold !=
AppSession.currentUser.userDiscoveryThreshold)
Padding(
padding: const EdgeInsets.all(17),
child: FilledButton(

View file

@ -23,13 +23,12 @@ class _ModifyAvatarViewState extends State<ModifyAvatarView> {
}
Future<void> 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<ModifyAvatarView> {
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) {

View file

@ -47,13 +47,11 @@ class _ProfileViewState extends State<ProfileView> {
}
Future<void> 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<void> _updateUsername(String username) async {
@ -94,13 +92,11 @@ class _ProfileViewState extends State<ProfileView> {
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,7 +105,10 @@ class _ProfileViewState extends State<ProfileView> {
appBar: AppBar(
title: Text(context.lang.settingsProfile),
),
body: ListView(
body: StreamBuilder<void>(
stream: AppSession.onUserUpdated,
builder: (context, _) {
return ListView(
physics: const BouncingScrollPhysics(),
children: <Widget>[
const SizedBox(height: 25),
@ -128,7 +127,6 @@ class _ProfileViewState extends State<ProfileView> {
onPressed: () async {
await context.push(Routes.settingsProfileModifyAvatar);
await _avatarMakerController.performRestore();
setState(() {});
},
),
),
@ -155,17 +153,19 @@ class _ProfileViewState extends State<ProfileView> {
),
),
text: context.lang.registerUsernameDecoration,
subtitle: Text(gUser.username),
subtitle: Text(AppSession.currentUser.username),
onTap: () async {
final username = await showDisplayNameChangeDialog(
context,
gUser.username,
AppSession.currentUser.username,
context.lang.registerUsernameDecoration,
context.lang.registerUsernameDecoration,
maxLength: 12,
inputFormatters: [
LengthLimitingTextInputFormatter(12),
FilteringTextInputFormatter.allow(RegExp('[a-z0-9A-Z._]')),
FilteringTextInputFormatter.allow(
RegExp('[a-z0-9A-Z._]'),
),
],
);
if (context.mounted && username != null && username != '') {
@ -176,16 +176,18 @@ class _ProfileViewState extends State<ProfileView> {
BetterListTile(
icon: FontAwesomeIcons.userPen,
text: context.lang.settingsProfileEditDisplayName,
subtitle: Text(gUser.displayName),
subtitle: Text(AppSession.currentUser.displayName),
onTap: () async {
final displayName = await showDisplayNameChangeDialog(
context,
gUser.displayName,
AppSession.currentUser.displayName,
context.lang.settingsProfileEditDisplayName,
context.lang.settingsProfileEditDisplayNameNew,
maxLength: 30,
);
if (context.mounted && displayName != null && displayName != '') {
if (context.mounted &&
displayName != null &&
displayName != '') {
await updateUserDisplayName(displayName);
}
},
@ -202,6 +204,8 @@ class _ProfileViewState extends State<ProfileView> {
),
),
],
);
},
),
);
}

View file

@ -47,12 +47,12 @@ class _SettingsMainViewState extends State<SettingsMainView> {
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<SettingsMainView> {
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',

View file

@ -290,7 +290,7 @@ class _PlanCardState extends State<PlanCard> {
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),

View file

@ -39,10 +39,7 @@ String localePrizing(BuildContext context, int cents) {
Future<Response_PlanBallance?> 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();

View file

@ -15,7 +15,7 @@ const surveyUrlBase = 'https://survey.twonly.org/upload.php';
Future<void> 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<void> 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<void> 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) {

View file

@ -50,12 +50,11 @@ class _UserStudyQuestionnaireViewState
Future<void> _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();

View file

@ -86,10 +86,9 @@ class _UserStudyWelcomeViewState extends State<UserStudyWelcomeView> {
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(