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