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