mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 21:18:40 +00:00
use mutex when updating user
This commit is contained in:
parent
6946164d1e
commit
20030ccd14
23 changed files with 194 additions and 136 deletions
|
|
@ -44,6 +44,10 @@ class UserData {
|
||||||
|
|
||||||
DateTime? signalLastSignedPreKeyUpdated;
|
DateTime? signalLastSignedPreKeyUpdated;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool identityBackupEnabled = false;
|
||||||
|
DateTime? identityBackupLastBackupTime;
|
||||||
|
|
||||||
final int userId;
|
final int userId;
|
||||||
|
|
||||||
factory UserData.fromJson(Map<String, dynamic> json) =>
|
factory UserData.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,12 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
..signalLastSignedPreKeyUpdated =
|
..signalLastSignedPreKeyUpdated =
|
||||||
json['signalLastSignedPreKeyUpdated'] == null
|
json['signalLastSignedPreKeyUpdated'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['signalLastSignedPreKeyUpdated'] as String);
|
: DateTime.parse(json['signalLastSignedPreKeyUpdated'] as String)
|
||||||
|
..identityBackupEnabled = json['identityBackupEnabled'] as bool? ?? false
|
||||||
|
..identityBackupLastBackupTime =
|
||||||
|
json['identityBackupLastBackupTime'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['identityBackupLastBackupTime'] as String);
|
||||||
|
|
||||||
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'username': instance.username,
|
'username': instance.username,
|
||||||
|
|
@ -69,6 +74,9 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'myBestFriendContactId': instance.myBestFriendContactId,
|
'myBestFriendContactId': instance.myBestFriendContactId,
|
||||||
'signalLastSignedPreKeyUpdated':
|
'signalLastSignedPreKeyUpdated':
|
||||||
instance.signalLastSignedPreKeyUpdated?.toIso8601String(),
|
instance.signalLastSignedPreKeyUpdated?.toIso8601String(),
|
||||||
|
'identityBackupEnabled': instance.identityBackupEnabled,
|
||||||
|
'identityBackupLastBackupTime':
|
||||||
|
instance.identityBackupLastBackupTime?.toIso8601String(),
|
||||||
'userId': instance.userId,
|
'userId': instance.userId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,9 @@ class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
var user = await getUser();
|
await updateUserdata((user) {
|
||||||
if (user != null) {
|
|
||||||
user.themeMode = newThemeMode;
|
user.themeMode = newThemeMode;
|
||||||
await updateUser(user);
|
return user;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/app.dart';
|
import 'package:twonly/app.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pbserver.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pbserver.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||||
|
|
@ -347,11 +346,10 @@ class ApiService {
|
||||||
server.Response_Ok ok = result.value;
|
server.Response_Ok ok = result.value;
|
||||||
if (ok.hasAuthenticated()) {
|
if (ok.hasAuthenticated()) {
|
||||||
server.Response_Authenticated authenticated = ok.authenticated;
|
server.Response_Authenticated authenticated = ok.authenticated;
|
||||||
UserData? user = await getUser();
|
updateUserdata((user) {
|
||||||
if (user != null) {
|
|
||||||
user.subscriptionPlan = authenticated.plan;
|
user.subscriptionPlan = authenticated.plan;
|
||||||
await updateUser(user);
|
return user;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
Log.info("websocket is authenticated");
|
Log.info("websocket is authenticated");
|
||||||
onAuthenticated();
|
onAuthenticated();
|
||||||
|
|
|
||||||
|
|
@ -34,20 +34,24 @@ Future<ErrorCode?> isAllowedToSend() async {
|
||||||
return ErrorCode.PlanNotAllowed;
|
return ErrorCode.PlanNotAllowed;
|
||||||
}
|
}
|
||||||
if (user.subscriptionPlan == "Free") {
|
if (user.subscriptionPlan == "Free") {
|
||||||
|
int? todaysImageCounter = user.todaysImageCounter;
|
||||||
if (user.lastImageSend != null && user.todaysImageCounter != null) {
|
if (user.lastImageSend != null && user.todaysImageCounter != null) {
|
||||||
if (isToday(user.lastImageSend!)) {
|
if (isToday(user.lastImageSend!)) {
|
||||||
if (user.todaysImageCounter == 3) {
|
if (user.todaysImageCounter == 3) {
|
||||||
return ErrorCode.PlanLimitReached;
|
return ErrorCode.PlanLimitReached;
|
||||||
}
|
}
|
||||||
user.todaysImageCounter = user.todaysImageCounter! + 1;
|
todaysImageCounter = user.todaysImageCounter! + 1;
|
||||||
} else {
|
} else {
|
||||||
user.todaysImageCounter = 1;
|
todaysImageCounter = 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
user.todaysImageCounter = 1;
|
todaysImageCounter = 1;
|
||||||
}
|
}
|
||||||
user.lastImageSend = DateTime.now();
|
await updateUserdata((user) {
|
||||||
await updateUser(user);
|
user.lastImageSend = DateTime.now();
|
||||||
|
user.todaysImageCounter = todaysImageCounter;
|
||||||
|
return user;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
lib/src/services/backup.identitiy.service.dart
Normal file
20
lib/src/services/backup.identitiy.service.dart
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
|
Future<bool> isIdentityBackupEnabled() async {
|
||||||
|
final user = await getUser();
|
||||||
|
if (user == null) return false;
|
||||||
|
return user.identityBackupEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<DateTime?> getLastIdentityBackup() async {
|
||||||
|
final user = await getUser();
|
||||||
|
if (user == null) return null;
|
||||||
|
return user.identityBackupLastBackupTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future enableIdentityBackup() async {
|
||||||
|
await updateUserdata((user) {
|
||||||
|
user.identityBackupEnabled = false;
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -21,8 +21,10 @@ Future syncFlameCounters() async {
|
||||||
contacts.firstWhere((x) => x.totalMediaCounter == maxMessageCounter);
|
contacts.firstWhere((x) => x.totalMediaCounter == maxMessageCounter);
|
||||||
|
|
||||||
if (user.myBestFriendContactId != bestFriend.userId) {
|
if (user.myBestFriendContactId != bestFriend.userId) {
|
||||||
user.myBestFriendContactId = bestFriend.userId;
|
await updateUserdata((user) {
|
||||||
await updateUser(user);
|
user.myBestFriendContactId = bestFriend.userId;
|
||||||
|
return user;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Contact contact in contacts) {
|
for (Contact contact in contacts) {
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,10 @@ Future signalHandleNewServerConnection() async {
|
||||||
Log.error("could not generate a new signed pre key!");
|
Log.error("could not generate a new signed pre key!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
user.signalLastSignedPreKeyUpdated = DateTime.now();
|
await updateUserdata((user) {
|
||||||
await updateUser(user);
|
user.signalLastSignedPreKeyUpdated = DateTime.now();
|
||||||
|
return user;
|
||||||
|
});
|
||||||
Result res = await apiService.updateSignedPreKey(
|
Result res = await apiService.updateSignedPreKey(
|
||||||
signedPreKey.id,
|
signedPreKey.id,
|
||||||
signedPreKey.getKeyPair().publicKey.serialize(),
|
signedPreKey.getKeyPair().publicKey.serialize(),
|
||||||
|
|
@ -47,10 +49,10 @@ Future signalHandleNewServerConnection() async {
|
||||||
);
|
);
|
||||||
if (res.isError) {
|
if (res.isError) {
|
||||||
Log.error("could not update the signed pre key: ${res.error}");
|
Log.error("could not update the signed pre key: ${res.error}");
|
||||||
final UserData? user = await getUser();
|
await updateUserdata((user) {
|
||||||
if (user == null) return;
|
user.signalLastSignedPreKeyUpdated = null;
|
||||||
user.signalLastSignedPreKeyUpdated = null;
|
return user;
|
||||||
await updateUser(user);
|
});
|
||||||
} else {
|
} else {
|
||||||
Log.info("updated signed pre key");
|
Log.info("updated signed pre key");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
|
|
@ -33,18 +34,27 @@ Future<UserData?> getUser() async {
|
||||||
|
|
||||||
Future updateUsersPlan(BuildContext context, String planId) async {
|
Future updateUsersPlan(BuildContext context, String planId) async {
|
||||||
context.read<CustomChangeProvider>().plan = planId;
|
context.read<CustomChangeProvider>().plan = planId;
|
||||||
var user = await getUser();
|
|
||||||
if (user != null) {
|
await updateUserdata((user) {
|
||||||
user.subscriptionPlan = planId;
|
user.subscriptionPlan = planId;
|
||||||
await updateUser(user);
|
return user;
|
||||||
}
|
});
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.read<CustomChangeProvider>().updatePlan(planId);
|
context.read<CustomChangeProvider>().updatePlan(planId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future updateUser(UserData userData) async {
|
Mutex updateProtection = Mutex();
|
||||||
final storage = FlutterSecureStorage();
|
|
||||||
storage.write(key: "userData", value: jsonEncode(userData));
|
Future<UserData?> updateUserdata(Function(UserData userData) updateUser) async {
|
||||||
|
return await updateProtection.protect<UserData?>(() async {
|
||||||
|
final user = await getUser();
|
||||||
|
if (user == null) return null;
|
||||||
|
UserData updated = updateUser(user);
|
||||||
|
final storage = FlutterSecureStorage();
|
||||||
|
storage.write(key: "userData", value: jsonEncode(updated));
|
||||||
|
return user;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> deleteLocalUserData() async {
|
Future<bool> deleteLocalUserData() async {
|
||||||
|
|
|
||||||
|
|
@ -558,11 +558,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
useHighQuality = !useHighQuality;
|
useHighQuality = !useHighQuality;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
var user = await getUser();
|
await updateUserdata((user) {
|
||||||
if (user != null) {
|
|
||||||
user.useHighQuality = useHighQuality;
|
user.useHighQuality = useHighQuality;
|
||||||
updateUser(user);
|
return user;
|
||||||
}
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (!hasAudioPermission)
|
if (!hasAudioPermission)
|
||||||
|
|
|
||||||
|
|
@ -29,21 +29,21 @@ class _EmojisState extends State<Emojis> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future selectEmojis(String emoji) async {
|
Future selectEmojis(String emoji) async {
|
||||||
final user = await getUser();
|
await updateUserdata((user) {
|
||||||
if (user == null) return;
|
if (user.lastUsedEditorEmojis == null) {
|
||||||
if (user.lastUsedEditorEmojis == null) {
|
user.lastUsedEditorEmojis = [emoji];
|
||||||
user.lastUsedEditorEmojis = [emoji];
|
} else {
|
||||||
} else {
|
if (user.lastUsedEditorEmojis!.contains(emoji)) {
|
||||||
if (user.lastUsedEditorEmojis!.contains(emoji)) {
|
user.lastUsedEditorEmojis!.remove(emoji);
|
||||||
user.lastUsedEditorEmojis!.remove(emoji);
|
}
|
||||||
|
user.lastUsedEditorEmojis!.insert(0, emoji);
|
||||||
|
if (user.lastUsedEditorEmojis!.length > 12) {
|
||||||
|
user.lastUsedEditorEmojis = user.lastUsedEditorEmojis!.sublist(0, 12);
|
||||||
|
}
|
||||||
|
user.lastUsedEditorEmojis!.toSet().toList();
|
||||||
}
|
}
|
||||||
user.lastUsedEditorEmojis!.insert(0, emoji);
|
return user;
|
||||||
if (user.lastUsedEditorEmojis!.length > 12) {
|
});
|
||||||
user.lastUsedEditorEmojis = user.lastUsedEditorEmojis!.sublist(0, 12);
|
|
||||||
}
|
|
||||||
user.lastUsedEditorEmojis!.toSet().toList();
|
|
||||||
}
|
|
||||||
await updateUser(user);
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.pop(
|
Navigator.pop(
|
||||||
context,
|
context,
|
||||||
|
|
|
||||||
|
|
@ -233,11 +233,10 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
maxShowTime = gMediaShowInfinite;
|
maxShowTime = gMediaShowInfinite;
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
var user = await getUser();
|
await updateUserdata((user) {
|
||||||
if (user != null) {
|
|
||||||
user.defaultShowTime = maxShowTime;
|
user.defaultShowTime = maxShowTime;
|
||||||
updateUser(user);
|
return user;
|
||||||
}
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/src/services/backup.identitiy.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/settings/backup/twonly_identity_backup.view.dart';
|
import 'package:twonly/src/views/settings/backup/twonly_identity_backup.view.dart';
|
||||||
|
|
||||||
|
|
@ -22,10 +23,10 @@ class _BackupViewState extends State<BackupView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future initAsync() async {
|
Future initAsync() async {
|
||||||
setState(() {
|
_twonlyIdBackupEnabled = await isIdentityBackupEnabled();
|
||||||
_twonlyIdBackupEnabled = true;
|
_twonlyIdLastBackup = await getLastIdentityBackup();
|
||||||
_dataBackupEnabled = false;
|
_dataBackupEnabled = false;
|
||||||
});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -37,7 +38,7 @@ class _BackupViewState extends State<BackupView> {
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
BackupOption(
|
BackupOption(
|
||||||
title: 'twonly-Identity Backup',
|
title: 'twonly Safe',
|
||||||
description:
|
description:
|
||||||
'Back up your twonly identity, as this is the only way to restore your account if you uninstall or lose your phone.',
|
'Back up your twonly identity, as this is the only way to restore your account if you uninstall or lose your phone.',
|
||||||
lastBackup: _twonlyIdLastBackup,
|
lastBackup: _twonlyIdLastBackup,
|
||||||
|
|
@ -51,7 +52,7 @@ class _BackupViewState extends State<BackupView> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BackupOption(
|
BackupOption(
|
||||||
title: 'Daten-Backup',
|
title: 'Daten-Backup (Coming Soon)',
|
||||||
description:
|
description:
|
||||||
'This backup contains besides of your twonly-Identity also all of your media files. This backup will also be encrypted using a password chosen by the user but stored locally on the smartphone. You then have to ensure to manually copy it onto your laptop or device of your choice.',
|
'This backup contains besides of your twonly-Identity also all of your media files. This backup will also be encrypted using a password chosen by the user but stored locally on the smartphone. You then have to ensure to manually copy it onto your laptop or device of your choice.',
|
||||||
autoBackupEnabled: _dataBackupEnabled,
|
autoBackupEnabled: _dataBackupEnabled,
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,14 @@ class _TwonlyIdentityBackupViewState extends State<TwonlyIdentityBackupView> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("twonly-Identity Backup"),
|
title: Text("twonly Safe"),
|
||||||
|
),
|
||||||
|
body: ListView(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Backup of your twonly-Identity. As twonly does not have any second factor like your phone number or email, this backup contains your twonly-Identity. If you lose your device, the only option to recover is with the twonly-ID Backup. This backup will be protected by a password chosen by you in the next step and anonymously uploaded to the twonly servers. Read more [here](https://twonly.eu/s/backup).'),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: ListView(children: [
|
|
||||||
Text(
|
|
||||||
'Backup of your twonly-Identity. As twonly does not have any second factor like your phone number or email, this backup contains your twonly-Identity. If you lose your device, the only option to recover is with the twonly-ID Backup. This backup will be protected by a password chosen by you in the next step and anonymously uploaded to the twonly servers. Read more [here](https://twonly.eu/s/backup).'),
|
|
||||||
]),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,10 @@ class _ChatReactionSelectionView extends State<ChatReactionSelectionView> {
|
||||||
} else {
|
} else {
|
||||||
if (selectedEmojis.length < 12) {
|
if (selectedEmojis.length < 12) {
|
||||||
selectedEmojis.add(emoji);
|
selectedEmojis.add(emoji);
|
||||||
var user = await getUser();
|
await updateUserdata((user) {
|
||||||
if (user != null) {
|
|
||||||
user.preSelectedEmojies = selectedEmojis;
|
user.preSelectedEmojies = selectedEmojis;
|
||||||
await updateUser(user);
|
return user;
|
||||||
}
|
});
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
|
|
@ -98,11 +97,10 @@ class _ChatReactionSelectionView extends State<ChatReactionSelectionView> {
|
||||||
selectedEmojis =
|
selectedEmojis =
|
||||||
EmojiAnimation.animatedIcons.keys.toList().sublist(0, 6);
|
EmojiAnimation.animatedIcons.keys.toList().sublist(0, 6);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
var user = await getUser();
|
await updateUserdata((user) {
|
||||||
if (user != null) {
|
|
||||||
user.preSelectedEmojies = selectedEmojis;
|
user.preSelectedEmojies = selectedEmojis;
|
||||||
await updateUser(user);
|
return user;
|
||||||
}
|
});
|
||||||
},
|
},
|
||||||
child: Icon(Icons.settings_backup_restore_rounded),
|
child: Icon(Icons.settings_backup_restore_rounded),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,10 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleStoreInGallery() async {
|
void toggleStoreInGallery() async {
|
||||||
final user = await getUser();
|
await updateUserdata((u) {
|
||||||
if (user == null) return;
|
u.storeMediaFilesInGallery = !storeMediaFilesInGallery;
|
||||||
user.storeMediaFilesInGallery = !storeMediaFilesInGallery;
|
return u;
|
||||||
await updateUser(user);
|
});
|
||||||
initAsync();
|
initAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,10 +179,12 @@ class _AutoDownloadOptionsDialogState extends State<AutoDownloadOptionsDialog> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the onUpdate callback to notify the parent widget
|
// Call the onUpdate callback to notify the parent widget
|
||||||
final user = await getUser();
|
|
||||||
if (user == null) return;
|
await updateUserdata((u) {
|
||||||
user.autoDownloadOptions = autoDownloadOptions;
|
u.autoDownloadOptions = autoDownloadOptions;
|
||||||
await updateUser(user);
|
return u;
|
||||||
|
});
|
||||||
|
|
||||||
widget.onUpdate();
|
widget.onUpdate();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,10 @@ class HelpView extends StatelessWidget {
|
||||||
title: Text(context.lang.settingsResetTutorials),
|
title: Text(context.lang.settingsResetTutorials),
|
||||||
subtitle: Text(context.lang.settingsResetTutorialsDesc),
|
subtitle: Text(context.lang.settingsResetTutorialsDesc),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final user = await getUser();
|
updateUserdata((user) {
|
||||||
if (user == null) return;
|
user.tutorialDisplayed = [];
|
||||||
user.tutorialDisplayed = [];
|
return user;
|
||||||
await updateUser(user);
|
});
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import 'dart:math';
|
||||||
import 'package:avatar_maker/avatar_maker.dart';
|
import 'package:avatar_maker/avatar_maker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import "package:get/get.dart";
|
import "package:get/get.dart";
|
||||||
|
|
@ -12,17 +11,16 @@ class ModifyAvatar extends StatelessWidget {
|
||||||
const ModifyAvatar({super.key});
|
const ModifyAvatar({super.key});
|
||||||
|
|
||||||
Future updateUserAvatar(String json, String svg) async {
|
Future updateUserAvatar(String json, String svg) async {
|
||||||
UserData? user = await getUser();
|
await updateUserdata((user) {
|
||||||
if (user == null) return null;
|
user.avatarJson = json;
|
||||||
|
user.avatarSvg = svg;
|
||||||
user.avatarJson = json;
|
if (user.avatarCounter == null) {
|
||||||
user.avatarSvg = svg;
|
user.avatarCounter = 1;
|
||||||
if (user.avatarCounter == null) {
|
} else {
|
||||||
user.avatarCounter = 1;
|
user.avatarCounter = user.avatarCounter! + 1;
|
||||||
} else {
|
}
|
||||||
user.avatarCounter = user.avatarCounter! + 1;
|
return user;
|
||||||
}
|
});
|
||||||
await updateUser(user);
|
|
||||||
await notifyContactsAboutProfileChange();
|
await notifyContactsAboutProfileChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,16 +29,17 @@ class _ProfileViewState extends State<ProfileView> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future updateUserDisplayname(String displayName) async {
|
Future updateUserDisplayName(String displayName) async {
|
||||||
UserData? user = await getUser();
|
await updateUserdata((user) {
|
||||||
if (user == null) return null;
|
user.displayName = displayName;
|
||||||
user.displayName = displayName;
|
if (user.avatarCounter == null) {
|
||||||
if (user.avatarCounter == null) {
|
user.avatarCounter = 1;
|
||||||
user.avatarCounter = 1;
|
} else {
|
||||||
} else {
|
user.avatarCounter = user.avatarCounter! + 1;
|
||||||
user.avatarCounter = user.avatarCounter! + 1;
|
}
|
||||||
}
|
return user;
|
||||||
await updateUser(user);
|
});
|
||||||
|
|
||||||
await notifyContactsAboutProfileChange();
|
await notifyContactsAboutProfileChange();
|
||||||
initAsync();
|
initAsync();
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +84,7 @@ class _ProfileViewState extends State<ProfileView> {
|
||||||
final displayName =
|
final displayName =
|
||||||
await showDisplayNameChangeDialog(context, user!.displayName);
|
await showDisplayNameChangeDialog(context, user!.displayName);
|
||||||
if (context.mounted && displayName != null && displayName != "") {
|
if (context.mounted && displayName != null && displayName != "") {
|
||||||
updateUserDisplayname(displayName);
|
updateUserDisplayName(displayName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/settings/account.view.dart';
|
import 'package:twonly/src/views/settings/account.view.dart';
|
||||||
import 'package:twonly/src/views/settings/appearance.view.dart';
|
import 'package:twonly/src/views/settings/appearance.view.dart';
|
||||||
|
import 'package:twonly/src/views/settings/backup/backup.view.dart';
|
||||||
import 'package:twonly/src/views/settings/chat/chat_settings.view.dart';
|
import 'package:twonly/src/views/settings/chat/chat_settings.view.dart';
|
||||||
import 'package:twonly/src/views/settings/data_and_storage.view.dart';
|
import 'package:twonly/src/views/settings/data_and_storage.view.dart';
|
||||||
import 'package:twonly/src/views/settings/notification.view.dart';
|
import 'package:twonly/src/views/settings/notification.view.dart';
|
||||||
|
|
@ -120,16 +121,16 @@ class _SettingsMainViewState extends State<SettingsMainView> {
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// BetterListTile(
|
BetterListTile(
|
||||||
// icon: Icons.lock_clock_rounded,
|
icon: Icons.lock_clock_rounded,
|
||||||
// text: context.lang.settingsBackup,
|
text: context.lang.settingsBackup,
|
||||||
// onTap: () {
|
onTap: () {
|
||||||
// Navigator.push(context,
|
Navigator.push(context,
|
||||||
// MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(builder: (context) {
|
||||||
// return BackupView();
|
return BackupView();
|
||||||
// }));
|
}));
|
||||||
// },
|
},
|
||||||
// ),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
BetterListTile(
|
BetterListTile(
|
||||||
icon: FontAwesomeIcons.sun,
|
icon: FontAwesomeIcons.sun,
|
||||||
|
|
|
||||||
|
|
@ -14,24 +14,27 @@ import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
||||||
|
|
||||||
Future<List<Response_AddAccountsInvite>?> loadAdditionalUserInvites() async {
|
Future<List<Response_AddAccountsInvite>?> loadAdditionalUserInvites() async {
|
||||||
List<Response_AddAccountsInvite>? ballance;
|
final ballance = await apiService.getAdditionalUserInvites();
|
||||||
final user = await getUser();
|
|
||||||
if (user == null) return ballance;
|
|
||||||
ballance = await apiService.getAdditionalUserInvites();
|
|
||||||
if (ballance != null) {
|
if (ballance != null) {
|
||||||
user.additionalUserInvites =
|
await updateUserdata((u) {
|
||||||
jsonEncode(ballance.map((x) => x.writeToJson()).toList());
|
u.additionalUserInvites =
|
||||||
await updateUser(user);
|
jsonEncode(ballance.map((x) => x.writeToJson()).toList());
|
||||||
} else if (user.lastPlanBallance != null) {
|
return u;
|
||||||
|
});
|
||||||
|
return ballance;
|
||||||
|
}
|
||||||
|
final user = await getUser();
|
||||||
|
if (user != null && user.lastPlanBallance != null) {
|
||||||
try {
|
try {
|
||||||
List<String> decoded = jsonDecode(user.additionalUserInvites!);
|
List<String> decoded = jsonDecode(user.additionalUserInvites!);
|
||||||
ballance =
|
return decoded
|
||||||
decoded.map((x) => Response_AddAccountsInvite.fromJson(x)).toList();
|
.map((x) => Response_AddAccountsInvite.fromJson(x))
|
||||||
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error("from json: $e");
|
Log.error("from json: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ballance;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AdditionalUsersView extends StatefulWidget {
|
class AdditionalUsersView extends StatefulWidget {
|
||||||
|
|
|
||||||
|
|
@ -36,16 +36,18 @@ String localePrizing(BuildContext context, int cents) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Response_PlanBallance?> loadPlanBalance({bool useCache = true}) async {
|
Future<Response_PlanBallance?> loadPlanBalance({bool useCache = true}) async {
|
||||||
Response_PlanBallance? ballance;
|
final ballance = await apiService.getPlanBallance();
|
||||||
final user = await getUser();
|
|
||||||
if (user == null) return ballance;
|
|
||||||
ballance = await apiService.getPlanBallance();
|
|
||||||
if (ballance != null) {
|
if (ballance != null) {
|
||||||
user.lastPlanBallance = ballance.writeToJson();
|
updateUserdata((u) {
|
||||||
await updateUser(user);
|
u.lastPlanBallance = ballance.writeToJson();
|
||||||
} else if (user.lastPlanBallance != null && useCache) {
|
return u;
|
||||||
|
});
|
||||||
|
return ballance;
|
||||||
|
}
|
||||||
|
final user = await getUser();
|
||||||
|
if (user != null && user.lastPlanBallance != null && useCache) {
|
||||||
try {
|
try {
|
||||||
ballance = Response_PlanBallance.fromJson(
|
return Response_PlanBallance.fromJson(
|
||||||
user.lastPlanBallance!,
|
user.lastPlanBallance!,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,12 @@ Future<bool> checkIfTutorialAlreadyShown(String tutorialId) async {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
user.tutorialDisplayed!.add(tutorialId);
|
user.tutorialDisplayed!.add(tutorialId);
|
||||||
await updateUser(user);
|
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.tutorialDisplayed = user.tutorialDisplayed;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue