move user handling to a single user service

This commit is contained in:
otsmr 2026-04-25 11:17:08 +02:00
parent 583368505d
commit 1e72883db0
31 changed files with 126 additions and 166 deletions

View file

@ -11,7 +11,6 @@ import 'package:twonly/src/providers/purchases.provider.dart';
import 'package:twonly/src/providers/routing.provider.dart';
import 'package:twonly/src/providers/settings.provider.dart';
import 'package:twonly/src/services/subscription.service.dart';
import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/pow.dart';
import 'package:twonly/src/visual/components/app_outdated.comp.dart';
@ -45,10 +44,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
Future<void> initAsync() async {
try {
final user = await getUser();
if (user != null && mounted) {
if (userService.isUserCreated && mounted) {
context.read<PurchasesProvider>().updatePlan(
planFromString(user.subscriptionPlan),
planFromString(userService.currentUser.subscriptionPlan),
);
}
} catch (e) {
@ -142,7 +140,6 @@ class AppMainWidget extends StatefulWidget {
}
class _AppMainWidgetState extends State<AppMainWidget> {
bool _isUserCreated = false;
bool _showOnboarding = true;
bool _isLoaded = false;
Object? _storageError;
@ -159,9 +156,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
Future<void> initAsync() async {
try {
_isUserCreated = await isUserCreated();
if (_isUserCreated) {
if (userService.isUserCreated) {
if (_isTwonlyLocked) {
// do not change in case twonly was already unlocked at some point
_isTwonlyLocked = userService.currentUser.screenLockEnabled;
@ -202,7 +197,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
late Widget child;
if (_isUserCreated) {
if (userService.isUserCreated) {
if (_isTwonlyLocked) {
child = UnlockTwonlyView(
callbackOnSuccess: () => setState(() {

View file

@ -107,9 +107,8 @@ void main() async {
);
}
final settingsController = SettingsChangeProvider();
final settingsController = SettingsChangeProvider()..loadSettings();
await settingsController.loadSettings();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
await initFileDownloader();
@ -144,13 +143,13 @@ Future<void> runMigrations() async {
if (userService.currentUser.appVersion < 90) {
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
await twonlyDB.mediaFilesDao.updateAllRetransmissionUploadingState();
await updateUser((u) => u.appVersion = 90);
await UserService.update((u) => u.appVersion = 90);
}
if (userService.currentUser.appVersion < 91) {
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
await makeMigrationToVersion91();
await updateUser((u) => u.appVersion = 91);
await UserService.update((u) => u.appVersion = 91);
}
if (userService.currentUser.appVersion < 109) {
@ -163,6 +162,6 @@ Future<void> runMigrations() async {
);
}
}
await updateUser((u) => u.appVersion = 109);
await UserService.update((u) => u.appVersion = 109);
}
}

View file

@ -42,9 +42,8 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
_planSub = apiService.onPlanUpdated.listen(updatePlan);
_connSub = apiService.onConnectionStateUpdated.listen((_) async {
try {
final user = await getUser();
if (user != null) {
updatePlan(planFromString(user.subscriptionPlan));
if (userService.isUserCreated) {
updatePlan(planFromString(userService.currentUser.subscriptionPlan));
}
} catch (e) {
Log.error(e);
@ -95,8 +94,10 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
notifyListeners();
try {
final user = await getUser();
if (user != null && isPayingUser(planFromString(user.subscriptionPlan))) {
if (userService.isUserCreated &&
isPayingUser(
planFromString(userService.currentUser.subscriptionPlan),
)) {
Log.info('Started IPA timer for verification.');
globalForceIpaCheck = Timer(const Duration(seconds: 5), () async {
Log.info(
@ -185,7 +186,7 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
// an ok authenticated which is processed in the apiProvider...
if (res.isSuccess) {
if (Platform.isAndroid) {
await updateUser((u) {
await UserService.update((u) {
u.subscriptionPlanIdStore = purchaseDetails.productID;
});
}
@ -216,11 +217,10 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
purchaseDetails.error == null) {
globalForceIpaCheck?.cancel();
final user = await getUser();
final currentPlan = userService.currentUser.subscriptionPlan;
if (user != null &&
(user.subscriptionPlan != SubscriptionPlan.Family.name &&
user.subscriptionPlan != SubscriptionPlan.Pro.name)) {
if (currentPlan != SubscriptionPlan.Family.name &&
currentPlan != SubscriptionPlan.Pro.name) {
for (var i = 0; i < 100; i++) {
if (apiService.isAuthenticated) {
Log.info(

View file

@ -1,20 +1,19 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/utils/log.dart';
class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
late ThemeMode _themeMode;
ThemeMode get themeMode => _themeMode;
Future<void> loadSettings() async {
try {
_themeMode = (await getUser())?.themeMode ?? ThemeMode.system;
void loadSettings() {
if (userService.isUserCreated) {
_themeMode = userService.currentUser.themeMode;
notifyListeners();
} catch (e) {
} else {
_themeMode = ThemeMode.system;
Log.error(e);
}
}
@ -27,6 +26,6 @@ class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
notifyListeners();
await updateUser((u) => u.themeMode = newThemeMode);
await UserService.update((u) => u.themeMode = newThemeMode);
}
}

View file

@ -354,7 +354,7 @@ class ApiService {
final ok = res.value as server.Response_Ok;
if (ok.hasAuthenticated()) {
final authenticated = ok.authenticated;
await updateUser((user) {
await UserService.update((user) {
user.subscriptionPlan = authenticated.plan;
});
_planUpdateController.add(planFromString(authenticated.plan));
@ -470,8 +470,7 @@ class ApiService {
return;
}
final userData = await getUser();
if (userData == null) return;
if (userService.isUserCreated) return;
if (await tryAuthenticateWithToken()) {
return;
@ -501,7 +500,7 @@ class ApiService {
final getAuthToken = Handshake_GetAuthToken()
..response = signature
..userId = Int64(userData.userId);
..userId = Int64(userService.currentUser.userId);
final getauthtoken = Handshake()..getAuthToken = getAuthToken;
@ -791,7 +790,7 @@ class ApiService {
Future<Response_PlanBallance?> loadPlanBalance({bool useCache = true}) async {
final ballance = await getPlanBallance();
if (ballance != null) {
await updateUser((u) {
await UserService.update((u) {
u.lastPlanBallance = ballance.writeToJson();
});
return ballance;

View file

@ -17,7 +17,7 @@ Future<void> enableTwonlySafe(String password) async {
userService.currentUser.username,
);
await updateUser((user) {
await UserService.update((user) {
user.twonlySafeBackup = TwonlySafeBackup(
encryptionKey: encryptionKey,
backupId: backupId,

View file

@ -94,7 +94,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
key: SecureStorageKeys.signalSignedPreKey,
);
final userBackup = await getUser();
final userBackup = await UserService.getUser();
if (userBackup == null) return;
// FILTER settings which should not be in the backup
userBackup
@ -183,7 +183,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
if (encryptedBackupBytes.length >
userService.currentUser.backupServer!.maxBackupBytes) {
Log.error('Backup is to big for the alternative backup server.');
await updateUser((user) {
await UserService.update((user) {
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed;
});
return;
@ -203,7 +203,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
);
if (await FileDownloader().enqueue(task)) {
Log.info('Starting upload from twonly Backup.');
await updateUser((user) {
await UserService.update((user) {
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.pending;
user.twonlySafeBackup!.lastBackupDone = clock.now();
user.twonlySafeBackup!.lastBackupSize = encryptedBackupBytes.length;
@ -216,7 +216,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
Future<void> handleBackupStatusUpdate(TaskStatusUpdate update) async {
if (update.status == TaskStatus.failed ||
update.status == TaskStatus.canceled) {
await updateUser((user) {
await UserService.update((user) {
if (user.twonlySafeBackup != null) {
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed;
}
@ -225,7 +225,7 @@ Future<void> handleBackupStatusUpdate(TaskStatusUpdate update) async {
Log.info(
'twonly Backup uploaded with status code ${update.responseStatusCode}',
);
await updateUser((user) {
await UserService.update((user) {
if (user.twonlySafeBackup != null) {
user.twonlySafeBackup!.backupUploadState =
LastBackupUploadState.success;

View file

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

View file

@ -18,7 +18,7 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
);
if (userService.currentUser.myBestFriendGroupId != bestFriend.groupId) {
await updateUser((user) {
await UserService.update((user) {
user.myBestFriendGroupId = bestFriend.groupId;
});
}

View file

@ -49,7 +49,7 @@ Future<void> checkForTokenUpdates() async {
if (storedToken == null || fcmToken != storedToken) {
Log.info('Got new FCM TOKEN.');
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
await updateUser((u) {
await UserService.update((u) {
u.updateFCMToken = true;
});
}
@ -61,7 +61,7 @@ Future<void> checkForTokenUpdates() async {
key: SecureStorageKeys.googleFcm,
value: fcmToken,
);
await updateUser((u) {
await UserService.update((u) {
u.updateFCMToken = true;
});
})
@ -81,7 +81,7 @@ Future<void> initFCMAfterAuthenticated({bool force = false}) async {
final res = await apiService.updateFCMToken(storedToken);
if (res.isSuccess) {
Log.info('Uploaded new FCM token!');
await updateUser((u) {
await UserService.update((u) {
u.updateFCMToken = false;
});
} else {

View file

@ -39,7 +39,7 @@ Future<void> signalHandleNewServerConnection() async {
Log.error('could not generate a new signed pre key!');
return;
}
await updateUser((user) {
await UserService.update((user) {
user.signalLastSignedPreKeyUpdated = clock.now();
});
final res = await apiService.updateSignedPreKey(
@ -49,7 +49,7 @@ Future<void> signalHandleNewServerConnection() async {
);
if (res.isError) {
Log.error('could not update the signed pre key: ${res.error}');
await updateUser((user) {
await UserService.update((user) {
user.signalLastSignedPreKeyUpdated = null;
});
} else {
@ -59,13 +59,9 @@ Future<void> signalHandleNewServerConnection() async {
Future<List<PreKeyRecord>> signalGetPreKeys() async {
return lockingSignalProtocol.protect(() async {
final user = await getUser();
if (user == null) return [];
final start = user.currentPreKeyIndexStart;
await updateUser((user) {
user.currentPreKeyIndexStart =
(user.currentPreKeyIndexStart + 200) % maxValue;
final start = userService.currentUser.currentPreKeyIndexStart;
await UserService.update((u) {
u.currentPreKeyIndexStart = (u.currentPreKeyIndexStart + 200) % maxValue;
});
final preKeys = generatePreKeys(start, 200);
final signalStore = await getSignalStore();
@ -138,14 +134,14 @@ Future<void> createIfNotExistsSignalIdentity() async {
Future<SignedPreKeyRecord?> _getNewSignalSignedPreKey() async {
return lockingSignalProtocol.protect(() async {
var identityKeyPair = await getSignalIdentityKeyPair();
final user = await getUser();
final signalStore = await getSignalStore();
if (identityKeyPair == null || signalStore == null || user == null) {
if (identityKeyPair == null || signalStore == null) {
return null;
}
final signedPreKeyId = user.currentSignedPreKeyIndexStart;
await updateUser((user) {
final signedPreKeyId =
userService.currentUser.currentSignedPreKeyIndexStart;
await UserService.update((user) {
user.currentSignedPreKeyIndexStart += 1;
});

View file

@ -1,20 +1,17 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:mutex/mutex.dart';
import 'package:provider/provider.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/constants/secure_storage.keys.dart';
import 'package:twonly/src/model/json/userdata.model.dart';
import 'package:twonly/src/providers/purchases.provider.dart';
import 'package:twonly/src/services/subscription.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/secure_storage.dart';
class UserService {
late UserData currentUser;
bool isUserCreated = false;
static final Mutex _updateProtection = Mutex();
final _userDataUpdateController = StreamController<void>.broadcast();
Stream<void> get onUserUpdated => _userDataUpdateController.stream;
@ -23,79 +20,53 @@ class UserService {
final user = await getUser();
if (user == null) return false;
userService.currentUser = user;
isUserCreated = true;
return true;
}
static Future<UserData?> getUser() async {
try {
final userDataJson = await SecureStorage.instance.read(
key: SecureStorageKeys.userData,
);
if (userDataJson == null) {
return null;
}
return UserData.fromJson(
jsonDecode(userDataJson) as Map<String, dynamic>,
);
} catch (e) {
Log.error('could not load user: $e');
rethrow; // Rethrow instead of returning null to distinguish error from missing user
}
}
static Future<void> update(
void Function(UserData userData) updateUser,
) async {
await _updateProtection.protect(() async {
try {
final user = await getUser();
if (user == null) return;
if (user.defaultShowTime == 999999) {
// This was the old version for infinity -> change it to null
user.defaultShowTime = null;
}
updateUser(user);
await SecureStorage.instance.write(
key: SecureStorageKeys.userData,
value: jsonEncode(user),
);
userService.currentUser = user;
} catch (e) {
Log.error('Could not update the user: $e');
}
});
userService.triggerUserUpdate();
}
void triggerUserUpdate() {
_userDataUpdateController.add(null);
}
void dispose() {
_userDataUpdateController.close();
}
}
Future<bool> isUserCreated() async {
final user = await getUser();
if (user == null) {
return false;
}
userService.currentUser = user;
return true;
}
Future<UserData?> getUser() async {
try {
final userDataJson = await SecureStorage.instance.read(
key: SecureStorageKeys.userData,
);
if (userDataJson == null) {
return null;
}
return UserData.fromJson(jsonDecode(userDataJson) as Map<String, dynamic>);
} catch (e) {
Log.error('could not load user: $e');
rethrow; // Rethrow instead of returning null to distinguish error from missing user
}
}
Future<void> updateUsersPlan(
BuildContext context,
SubscriptionPlan plan,
) async {
context.read<PurchasesProvider>().plan = plan;
await updateUser((user) {
user.subscriptionPlan = plan.name;
});
if (!context.mounted) return;
context.read<PurchasesProvider>().updatePlan(plan);
}
Mutex updateProtection = Mutex();
Future<void> updateUser(
void Function(UserData userData) updateUser,
) async {
await updateProtection.protect(() async {
try {
final user = await getUser();
if (user == null) return;
if (user.defaultShowTime == 999999) {
// This was the old version for infinity -> change it to null
user.defaultShowTime = null;
}
updateUser(user);
await const FlutterSecureStorage().write(
key: SecureStorageKeys.userData,
value: jsonEncode(user),
);
userService.currentUser = user;
} catch (e) {
Log.error('Could not update the user: $e');
}
});
userService.triggerUserUpdate();
}

View file

@ -56,7 +56,7 @@ class UserDiscoveryService {
userId: userService.currentUser.userId,
publicKey: await getUserPublicKey(),
);
await updateUser(
await UserService.update(
(u) => u
..isUserDiscoveryEnabled = true
..minimumRequiredImagesExchanged = minimumRequiredImagesExchanged,
@ -168,7 +168,7 @@ class UserDiscoveryService {
}
static Future<void> disable() async {
await updateUser((u) {
await UserService.update((u) {
u.isUserDiscoveryEnabled = false;
});
}

View file

@ -100,13 +100,13 @@ Future<void> handleUserStudyUpload() async {
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode == 200) {
await updateUser((u) {
await UserService.update((u) {
u.lastUserStudyDataUpload = DateTime.now();
});
}
if (response.statusCode == 404) {
// Token is unknown to the server...
await updateUser((u) {
await UserService.update((u) {
u
..lastUserStudyDataUpload = null
..userStudyParticipantsToken = null;

View file

@ -209,7 +209,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
if (!_hasAudioPermission &&
!userService.currentUser.requestedAudioPermission) {
await updateUser((u) => u.requestedAudioPermission = true);
await UserService.update((u) => u.requestedAudioPermission = true);
await requestMicrophonePermission();
}
}

View file

@ -160,7 +160,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
if (!mounted) return;
setState(() {});
if (storeAsDefault) {
await updateUser((user) {
await UserService.update((user) {
user.defaultShowTime = maxShowTime;
});
}

View file

@ -93,7 +93,7 @@ class _ChatListViewState extends State<ChatListView> {
if (!userService.currentUser.hideChangeLog &&
userService.currentUser.lastChangeLogHash.toString() !=
changeLogHash.toString()) {
await updateUser((u) {
await UserService.update((u) {
u.lastChangeLogHash = changeLogHash;
});
if (!mounted) return;

View file

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

View file

@ -73,19 +73,19 @@ class _AppearanceViewState extends State<AppearanceView> {
}
Future<void> toggleShowFeedbackIcon() async {
await updateUser((u) {
await UserService.update((u) {
u.showFeedbackShortcut = !u.showFeedbackShortcut;
});
}
Future<void> toggleStartWithCameraOpen() async {
await updateUser((u) {
await UserService.update((u) {
u.startWithCameraOpen = !u.startWithCameraOpen;
});
}
Future<void> toggleShowImagePreviewWhenSending() async {
await updateUser((u) {
await UserService.update((u) {
u.showShowImagePreviewWhenSending = !u.showShowImagePreviewWhenSending;
});
}

View file

@ -80,7 +80,7 @@ class _BackupServerViewState extends State<BackupServerView> {
retentionDays: data['retentionDays']! as int,
maxBackupBytes: data['maxBackupBytes']! as int,
);
await updateUser((user) {
await UserService.update((user) {
user.backupServer = backupServer;
});
if (mounted) Navigator.pop(context, backupServer);
@ -166,7 +166,7 @@ class _BackupServerViewState extends State<BackupServerView> {
Center(
child: OutlinedButton(
onPressed: () async {
await updateUser((user) {
await UserService.update((user) {
user.backupServer = null;
});
if (context.mounted) Navigator.pop(context);

View file

@ -35,7 +35,7 @@ class _ChatReactionSelectionView extends State<ChatReactionSelectionView> {
} else {
if (_selectedEmojis.length < 12) {
_selectedEmojis.add(emoji);
await updateUser((user) {
await UserService.update((user) {
user.preSelectedEmojies = _selectedEmojis;
});
} else {
@ -90,7 +90,7 @@ class _ChatReactionSelectionView extends State<ChatReactionSelectionView> {
padding: const EdgeInsets.only(bottom: 30),
child: FloatingActionButton(
foregroundColor: Colors.white,
onPressed: () => updateUser(
onPressed: () => UserService.update(
(u) => u.preSelectedEmojies = EmojiAnimationComp
.animatedIcons
.keys

View file

@ -38,13 +38,13 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
}
Future<void> toggleStoreInGallery() async {
await updateUser((u) {
await UserService.update((u) {
u.storeMediaFilesInGallery = !u.storeMediaFilesInGallery;
});
}
Future<void> toggleAutoStoreMediaFiles() async {
await updateUser((u) {
await UserService.update((u) {
u.autoStoreAllSendUnlimitedMediaFiles =
!u.autoStoreAllSendUnlimitedMediaFiles;
});
@ -227,7 +227,7 @@ class _AutoDownloadOptionsDialogState extends State<AutoDownloadOptionsDialog> {
// Call the onUpdate callback to notify the parent widget
await updateUser((u) {
await UserService.update((u) {
u.autoDownloadOptions = autoDownloadOptions;
});

View file

@ -30,11 +30,11 @@ class _DeveloperSettingsViewState extends State<DeveloperSettingsView> {
}
Future<void> toggleDeveloperSettings() async {
await updateUser((u) => u.isDeveloper = !u.isDeveloper);
await UserService.update((u) => u.isDeveloper = !u.isDeveloper);
}
Future<void> toggleVideoStabilization() async {
await updateUser(
await UserService.update(
(u) => u.videoStabilizationEnabled = !u.videoStabilizationEnabled,
);
}

View file

@ -114,8 +114,9 @@ class _ChangeLogViewState extends State<ChangeLogView> {
Text(context.lang.openChangeLog),
Switch(
value: !userService.currentUser.hideChangeLog,
onChanged: (_) =>
updateUser((u) => u.hideChangeLog = !u.hideChangeLog),
onChanged: (_) => UserService.update(
(u) => u.hideChangeLog = !u.hideChangeLog,
),
),
],
),

View file

@ -19,7 +19,7 @@ class HelpView extends StatefulWidget {
class _HelpViewState extends State<HelpView> {
Future<void> toggleAllowErrorTrackingViaSentry() async {
await updateUser(
await UserService.update(
(u) => u.allowErrorTrackingViaSentry = !u.allowErrorTrackingViaSentry,
);
}
@ -128,7 +128,7 @@ class _HelpViewState extends State<HelpView> {
'Do you want to enable the developer settings?',
);
if (okay) {
await updateUser((u) => u.isDeveloper = true);
await UserService.update((u) => u.isDeveloper = true);
}
},
title: const Text(

View file

@ -25,14 +25,14 @@ class _PrivacyViewState extends State<PrivacyView> {
: context.lang.settingsScreenLockAuthMessageEnable,
);
if (!isAuth) return;
await updateUser((u) {
await UserService.update((u) {
u.screenLockEnabled = !u.screenLockEnabled;
});
setState(() {});
}
Future<void> toggleTypingIndicators() async {
await updateUser((u) {
await UserService.update((u) {
u.typingIndicators = !u.typingIndicators;
});
setState(() {});

View file

@ -33,7 +33,7 @@ class _UserDiscoverySettingsViewState extends State<UserDiscoverySettingsView> {
userService.currentUser.userDiscoveryThreshold !=
_userDiscoveryThreshold;
await updateUser((u) {
await UserService.update((u) {
u
..minimumRequiredImagesExchanged = _minimumRequiredImagesExchanged
..userDiscoveryThreshold = _userDiscoveryThreshold;

View file

@ -24,7 +24,7 @@ class _ModifyAvatarViewState extends State<ModifyAvatarView> {
}
Future<void> updateUserAvatar(String json, String svg) async {
await updateUser(
await UserService.update(
(u) => u
..avatarJson = json
..avatarSvg = svg

View file

@ -48,7 +48,7 @@ class _ProfileViewState extends State<ProfileView> {
}
Future<void> updateUserDisplayName(String displayName) async {
await updateUser(
await UserService.update(
(u) => u
..displayName = displayName
..avatarCounter = u.avatarCounter + 1,
@ -93,7 +93,7 @@ class _ProfileViewState extends State<ProfileView> {
await removeTwonlySafeFromServer();
unawaited(performTwonlySafeBackup(force: true));
await updateUser(
await UserService.update(
(u) => u
..username = username
..avatarCounter = u.avatarCounter + 1,

View file

@ -50,7 +50,7 @@ class _UserStudyQuestionnaireViewState
Future<void> _submitData() async {
await KeyValueStore.put(userStudySurveyKey, _responses);
await updateUser((u) {
await UserService.update((u) {
// generate a random participants id to identify data send later while keeping the user anonym
u
..userStudyParticipantsToken = getRandomString(25)

View file

@ -86,7 +86,7 @@ class _UserStudyWelcomeViewState extends State<UserStudyWelcomeView> {
Center(
child: GestureDetector(
onTap: () async {
await updateUser(
await UserService.update(
(u) => u.askedForUserStudyPermission = true,
);
if (context.mounted) context.pop();