From df9b055ba76eb38b50169aa9592d756e06ccd905 Mon Sep 17 00:00:00 2001 From: otsmr Date: Mon, 27 Oct 2025 20:44:14 +0100 Subject: [PATCH] fix verification shield and remove backup notice --- lib/src/database/daos/groups.dao.dart | 11 + lib/src/database/daos/messages.dao.dart | 10 +- lib/src/database/daos/reactions.dao.dart | 2 +- lib/src/services/api.service.dart | 1 - .../api/mediafiles/download.service.dart | 4 +- lib/src/services/api/messages.dart | 9 +- lib/src/services/signal/identity.signal.dart | 6 +- lib/src/services/signal/session.signal.dart | 7 +- .../twonly_safe/common.twonly_safe.dart | 7 +- .../create_backup.twonly_safe.dart | 19 +- .../camera_preview_controller_view.dart | 17 +- .../image_editor/modules/all_emojis.dart | 5 +- lib/src/views/chats/chat_list.view.dart | 8 +- .../backup_notice.card.dart | 96 ---- lib/src/views/chats/chat_messages.view.dart | 7 +- .../message_send_state_icon.dart | 5 +- .../reaction_buttons.component.dart | 7 +- lib/src/views/components/flame.dart | 1 + lib/src/views/components/verified_shield.dart | 84 +++- lib/src/views/contact/contact.view.dart | 2 +- .../views/settings/backup/backup.view.dart | 10 +- .../backup/twonly_safe_server.view.dart | 6 +- .../views/settings/settings_main.view.dart | 432 +++++++++--------- .../subscription/additional_users.view.dart | 5 +- 24 files changed, 340 insertions(+), 421 deletions(-) delete mode 100644 lib/src/views/chats/chat_list_components/backup_notice.card.dart diff --git a/lib/src/database/daos/groups.dao.dart b/lib/src/database/daos/groups.dao.dart index 09f9fbc..27eec3f 100644 --- a/lib/src/database/daos/groups.dao.dart +++ b/lib/src/database/daos/groups.dao.dart @@ -91,6 +91,17 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { return query.map((row) => row.readTable(contacts)).get(); } + Stream> watchGroupContact(String groupId) { + final query = (select(contacts).join([ + leftOuterJoin( + groupMembers, + groupMembers.contactId.equalsExp(contacts.userId), + ), + ]) + ..where(groupMembers.groupId.equals(groupId))); + return query.map((row) => row.readTable(contacts)).watch(); + } + Stream> watchGroups() { return select(groups).watch(); } diff --git a/lib/src/database/daos/messages.dao.dart b/lib/src/database/daos/messages.dao.dart index 17fefb0..02aac49 100644 --- a/lib/src/database/daos/messages.dao.dart +++ b/lib/src/database/daos/messages.dao.dart @@ -32,10 +32,12 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { Stream> watchMessageNotOpened(String groupId) { return (select(messages) - ..where((t) => - t.openedAt.isNull() & - t.groupId.equals(groupId) & - t.isDeletedFromSender.equals(false)) + ..where( + (t) => + t.openedAt.isNull() & + t.groupId.equals(groupId) & + t.isDeletedFromSender.equals(false), + ) ..orderBy([(t) => OrderingTerm.desc(t.createdAt)])) .watch(); } diff --git a/lib/src/database/daos/reactions.dao.dart b/lib/src/database/daos/reactions.dao.dart index 752d6a8..997d3f8 100644 --- a/lib/src/database/daos/reactions.dao.dart +++ b/lib/src/database/daos/reactions.dao.dart @@ -61,7 +61,7 @@ class ReactionsDao extends DatabaseAccessor with _$ReactionsDaoMixin { messages, messages.messageId.equalsExp(reactions.messageId), useColumns: false, - ) + ), ], ) ..where(messages.groupId.equals(groupId)) diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 71781b7..3683f2d 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -94,7 +94,6 @@ class ApiService { unawaited(retransmitRawBytes()); unawaited(tryTransmitMessages()); unawaited(tryDownloadAllMediaFiles()); - unawaited(notifyContactsAboutProfileChange()); twonlyDB.markUpdated(); unawaited(syncFlameCounters()); unawaited(setupNotificationWithUsers()); diff --git a/lib/src/services/api/mediafiles/download.service.dart b/lib/src/services/api/mediafiles/download.service.dart index 9f4e910..7c57699 100644 --- a/lib/src/services/api/mediafiles/download.service.dart +++ b/lib/src/services/api/mediafiles/download.service.dart @@ -16,7 +16,6 @@ import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; Future tryDownloadAllMediaFiles({bool force = false}) async { // This is called when WebSocket is newly connected, so allow all downloads to be restarted. @@ -44,8 +43,7 @@ Map> defaultAutoDownloadOptions = { Future isAllowedToDownload({required bool isVideo}) async { final connectivityResult = await Connectivity().checkConnectivity(); - final user = await getUser(); - final options = user!.autoDownloadOptions ?? defaultAutoDownloadOptions; + final options = gUser.autoDownloadOptions ?? defaultAutoDownloadOptions; if (connectivityResult.contains(ConnectivityResult.mobile)) { if (isVideo) { diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.dart index e748f54..b6591f4 100644 --- a/lib/src/services/api/messages.dart +++ b/lib/src/services/api/messages.dart @@ -16,7 +16,6 @@ import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/signal/encryption.signal.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; final lockRetransmission = Mutex(); @@ -277,15 +276,13 @@ Future notifyContactAboutOpeningMessage( } Future notifyContactsAboutProfileChange({int? onlyToContact}) async { - final user = await getUser(); - if (user == null) return; - if (user.avatarSvg == null) return; + if (gUser.avatarSvg == null) return; final encryptedContent = pb.EncryptedContent( contactUpdate: pb.EncryptedContent_ContactUpdate( type: pb.EncryptedContent_ContactUpdate_Type.UPDATE, - avatarSvgCompressed: gzip.encode(utf8.encode(user.avatarSvg!)), - displayName: user.displayName, + avatarSvgCompressed: gzip.encode(utf8.encode(gUser.avatarSvg!)), + displayName: gUser.displayName, ), ); diff --git a/lib/src/services/signal/identity.signal.dart b/lib/src/services/signal/identity.signal.dart index c4554d7..f2e3129 100644 --- a/lib/src/services/signal/identity.signal.dart +++ b/lib/src/services/signal/identity.signal.dart @@ -20,13 +20,11 @@ Future getSignalIdentityKeyPair() async { // This function runs after the clients authenticated with the server. // It then checks if it should update a new session key Future signalHandleNewServerConnection() async { - final user = await getUser(); - if (user == null) return; - if (user.signalLastSignedPreKeyUpdated != null) { + if (gUser.signalLastSignedPreKeyUpdated != null) { final fortyEightHoursAgo = DateTime.now().subtract(const Duration(hours: 48)); final isYoungerThan48Hours = - (user.signalLastSignedPreKeyUpdated!).isAfter(fortyEightHoursAgo); + (gUser.signalLastSignedPreKeyUpdated!).isAfter(fortyEightHoursAgo); if (isYoungerThan48Hours) { // The key does live for 48 hours then it expires and a new key is generated. return; diff --git a/lib/src/services/signal/session.signal.dart b/lib/src/services/signal/session.signal.dart index 19ee1a0..48480c7 100644 --- a/lib/src/services/signal/session.signal.dart +++ b/lib/src/services/signal/session.signal.dart @@ -1,10 +1,10 @@ import 'dart:typed_data'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; import 'package:twonly/src/services/signal/consts.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/storage.dart'; Future createNewSignalSession(Response_UserData userData) async { final SignalProtocolStore? signalStore = await getSignalStore(); @@ -84,8 +84,7 @@ Future deleteSessionWithTarget(int target) async { Future generateSessionFingerPrint(int target) async { final signalStore = await getSignalStore(); - final user = await getUser(); - if (signalStore == null || user == null) return null; + if (signalStore == null) return null; try { final targetIdentity = await signalStore .getIdentity(SignalProtocolAddress(target.toString(), defaultDeviceId)); @@ -93,7 +92,7 @@ Future generateSessionFingerPrint(int target) async { final generator = NumericFingerprintGenerator(5200); final localFingerprint = generator.createFor( 1, - Uint8List.fromList([user.userId]), + Uint8List.fromList([gUser.userId]), (await signalStore.getIdentityKeyPair()).getPublicKey(), Uint8List.fromList([target]), targetIdentity, diff --git a/lib/src/services/twonly_safe/common.twonly_safe.dart b/lib/src/services/twonly_safe/common.twonly_safe.dart index 630ef5d..5e022b4 100644 --- a/lib/src/services/twonly_safe/common.twonly_safe.dart +++ b/lib/src/services/twonly_safe/common.twonly_safe.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:drift/drift.dart'; import 'package:hashlib/hashlib.dart'; import 'package:http/http.dart' as http; +import 'package:twonly/globals.dart'; import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart'; import 'package:twonly/src/utils/log.dart'; @@ -10,10 +11,8 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; Future enableTwonlySafe(String password) async { - final user = await getUser(); - if (user == null) return; - - final (backupId, encryptionKey) = await getMasterKey(password, user.username); + final (backupId, encryptionKey) = + await getMasterKey(password, gUser.username); await updateUserdata((user) { user.twonlySafeBackup = TwonlySafeBackup( diff --git a/lib/src/services/twonly_safe/create_backup.twonly_safe.dart b/lib/src/services/twonly_safe/create_backup.twonly_safe.dart index 42bc911..80dd6e3 100644 --- a/lib/src/services/twonly_safe/create_backup.twonly_safe.dart +++ b/lib/src/services/twonly_safe/create_backup.twonly_safe.dart @@ -10,6 +10,7 @@ import 'package:drift_flutter/drift_flutter.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/src/constants/secure_storage_keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/json/userdata.dart'; @@ -21,19 +22,17 @@ import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/settings/backup/backup.view.dart'; Future performTwonlySafeBackup({bool force = false}) async { - final user = await getUser(); - - if (user == null || user.twonlySafeBackup == null) { + if (gUser.twonlySafeBackup == null) { return; } - if (user.twonlySafeBackup!.backupUploadState == + if (gUser.twonlySafeBackup!.backupUploadState == LastBackupUploadState.pending) { Log.warn('Backup upload is already pending.'); return; } - final lastUpdateTime = user.twonlySafeBackup!.lastBackupDone; + final lastUpdateTime = gUser.twonlySafeBackup!.lastBackupDone; if (!force && lastUpdateTime != null) { if (lastUpdateTime .isAfter(DateTime.now().subtract(const Duration(days: 1)))) { @@ -117,8 +116,8 @@ Future performTwonlySafeBackup({bool force = false}) async { final backupHash = uint8ListToHex((await Sha256().hash(backupBytes)).bytes); - if (user.twonlySafeBackup!.lastBackupDone == null || - user.twonlySafeBackup!.lastBackupDone! + if (gUser.twonlySafeBackup!.lastBackupDone == null || + gUser.twonlySafeBackup!.lastBackupDone! .isAfter(DateTime.now().subtract(const Duration(days: 90)))) { force = true; } @@ -144,7 +143,7 @@ Future performTwonlySafeBackup({bool force = false}) async { final secretBox = await chacha20.encrypt( backupBytes, - secretKey: SecretKey(user.twonlySafeBackup!.encryptionKey), + secretKey: SecretKey(gUser.twonlySafeBackup!.encryptionKey), nonce: nonce, ); @@ -165,8 +164,8 @@ Future performTwonlySafeBackup({bool force = false}) async { 'Create twonly Safe backup with a size of ${encryptedBackupBytes.length} bytes.', ); - if (user.backupServer != null) { - if (encryptedBackupBytes.length > user.backupServer!.maxBackupBytes) { + if (gUser.backupServer != null) { + if (encryptedBackupBytes.length > gUser.backupServer!.maxBackupBytes) { Log.error('Backup is to big for the alternative backup server.'); await updateUserdata((user) { user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed; diff --git a/lib/src/views/camera/camera_preview_controller_view.dart b/lib/src/views/camera/camera_preview_controller_view.dart index b4bb4e4..edea3bc 100644 --- a/lib/src/views/camera/camera_preview_controller_view.dart +++ b/lib/src/views/camera/camera_preview_controller_view.dart @@ -181,17 +181,12 @@ class _CameraPreviewViewState extends State { Future initAsync() async { hasAudioPermission = await Permission.microphone.isGranted; - if (!hasAudioPermission) { - final user = await getUser(); - if (user != null) { - if (!user.requestedAudioPermission) { - await updateUserdata((u) { - u.requestedAudioPermission = true; - return u; - }); - await requestMicrophonePermission(); - } - } + if (!hasAudioPermission && !gUser.requestedAudioPermission) { + await updateUserdata((u) { + u.requestedAudioPermission = true; + return u; + }); + await requestMicrophonePermission(); } if (!mounted) return; setState(() {}); diff --git a/lib/src/views/camera/image_editor/modules/all_emojis.dart b/lib/src/views/camera/image_editor/modules/all_emojis.dart index 18775ea..423342a 100755 --- a/lib/src/views/camera/image_editor/modules/all_emojis.dart +++ b/lib/src/views/camera/image_editor/modules/all_emojis.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/camera/image_editor/data/data.dart'; import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; @@ -22,10 +23,8 @@ class _EmojisState extends State { } Future initAsync() async { - final user = await getUser(); - if (user == null) return; setState(() { - lastUsed = user.lastUsedEditorEmojis ?? []; + lastUsed = gUser.lastUsedEditorEmojis ?? []; lastUsed.addAll(emojis); }); } diff --git a/lib/src/views/chats/chat_list.view.dart b/lib/src/views/chats/chat_list.view.dart index e2491ae..ae9e4e1 100644 --- a/lib/src/views/chats/chat_list.view.dart +++ b/lib/src/views/chats/chat_list.view.dart @@ -11,7 +11,6 @@ import 'package:twonly/src/providers/connection.provider.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/chats/add_new_user.view.dart'; -import 'package:twonly/src/views/chats/chat_list_components/backup_notice.card.dart'; import 'package:twonly/src/views/chats/chat_list_components/connection_info.comp.dart'; import 'package:twonly/src/views/chats/chat_list_components/feedback_btn.dart'; import 'package:twonly/src/views/chats/chat_list_components/group_list_item.dart'; @@ -237,13 +236,8 @@ class _ChatListViewState extends State { : ListView.builder( itemCount: _groupsPinned.length + (_groupsPinned.isNotEmpty ? 1 : 0) + - _groupsNotPinned.length + - 1, + _groupsNotPinned.length, itemBuilder: (context, index) { - if (index == 0) { - return const BackupNoticeCard(); - } - index -= 1; // Check if the index is for the pinned users if (index < _groupsPinned.length) { final group = _groupsPinned[index]; diff --git a/lib/src/views/chats/chat_list_components/backup_notice.card.dart b/lib/src/views/chats/chat_list_components/backup_notice.card.dart deleted file mode 100644 index d5b9a36..0000000 --- a/lib/src/views/chats/chat_list_components/backup_notice.card.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.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'; - -class BackupNoticeCard extends StatefulWidget { - const BackupNoticeCard({super.key}); - - @override - State createState() => _BackupNoticeCardState(); -} - -class _BackupNoticeCardState extends State { - bool showBackupNotice = false; - - @override - void initState() { - super.initState(); - unawaited(initAsync()); - } - - Future initAsync() async { - final user = await getUser(); - showBackupNotice = false; - if (user != null && - (user.nextTimeToShowBackupNotice == null || - DateTime.now().isAfter(user.nextTimeToShowBackupNotice!))) { - if (user.twonlySafeBackup == null) { - showBackupNotice = true; - } - } - if (mounted) { - setState(() {}); - } - } - - @override - Widget build(BuildContext context) { - if (!showBackupNotice) return Container(); - - return Card( - elevation: 4, - margin: const EdgeInsets.all(10), - child: Padding( - padding: const EdgeInsets.all(10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.lang.backupNoticeTitle, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 5), - Text( - context.lang.backupNoticeDesc, - style: const TextStyle(fontSize: 14), - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - ElevatedButton( - onPressed: () async { - await updateUserdata((user) { - user.nextTimeToShowBackupNotice = - DateTime.now().add(const Duration(days: 7)); - return user; - }); - await initAsync(); - }, - child: Text(context.lang.backupNoticeLater), - ), - const SizedBox(width: 10), - FilledButton( - onPressed: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const BackupView(), - ), - ); - }, - child: Text(context.lang.backupNoticeOpenBackup), - ), - ], - ), - ], - ), - ), - ); - } -} diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index 6d6ef42..c29b4c6 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -17,6 +17,8 @@ import 'package:twonly/src/views/chats/chat_messages_components/chat_date_chip.d import 'package:twonly/src/views/chats/chat_messages_components/chat_list_entry.dart'; import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; +import 'package:twonly/src/views/components/flame.dart'; +import 'package:twonly/src/views/components/verified_shield.dart'; import 'package:twonly/src/views/contact/contact.view.dart'; import 'package:twonly/src/views/groups/group.view.dart'; import 'package:twonly/src/views/tutorial/tutorials.dart'; @@ -242,8 +244,9 @@ class _ChatMessagesViewState extends State { children: [ Text(group.groupName), const SizedBox(width: 10), - // if (group.verified) - // VerifiedShield(key: verifyShieldKey, group), + VerifiedShield(key: verifyShieldKey, group: group), + const SizedBox(width: 10), + FlameCounterWidget(groupId: group.groupId), ], ), ), diff --git a/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart b/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart index f8abbed..9e50231 100644 --- a/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart +++ b/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart @@ -5,7 +5,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; @@ -202,7 +201,7 @@ class _MessageSendStateIconState extends State { SizedBox( height: 18, child: EmojiAnimation(emoji: widget.lastReaction!.emoji), - ) + ), ]; } else { icons = [ @@ -218,7 +217,7 @@ class _MessageSendStateIconState extends State { ), ), ), - ) + ), ]; } // Log.info("DISPLAY REACTION"); diff --git a/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart index 75146c4..d5678fa 100644 --- a/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart +++ b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart @@ -1,6 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:twonly/src/utils/storage.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/src/views/chats/media_viewer_components/emoji_reactions_row.component.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; @@ -39,9 +39,8 @@ class _ReactionButtonsState extends State { } Future initAsync() async { - final user = await getUser(); - if (user != null && user.preSelectedEmojies != null) { - selectedEmojis = user.preSelectedEmojies!; + if (gUser.preSelectedEmojies != null) { + selectedEmojis = gUser.preSelectedEmojies!; } setState(() {}); } diff --git a/lib/src/views/components/flame.dart b/lib/src/views/components/flame.dart index 7753e55..7512489 100644 --- a/lib/src/views/components/flame.dart +++ b/lib/src/views/components/flame.dart @@ -55,6 +55,7 @@ class _FlameCounterWidgetState extends State { @override Widget build(BuildContext context) { + if (flameCounter < 1) return Container(); return Row( children: [ if (widget.prefix) const SizedBox(width: 5), diff --git a/lib/src/views/components/verified_shield.dart b/lib/src/views/components/verified_shield.dart index 4c3523d..6c8b2d8 100644 --- a/lib/src/views/components/verified_shield.dart +++ b/lib/src/views/components/verified_shield.dart @@ -1,38 +1,82 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/views/contact/contact_verify.view.dart'; -class VerifiedShield extends StatelessWidget { - const VerifiedShield(this.contact, {super.key, this.size = 18}); - final Contact contact; +class VerifiedShield extends StatefulWidget { + const VerifiedShield({ + this.contact, + this.group, + super.key, + this.size = 18, + }); + final Group? group; + final Contact? contact; final double size; + @override + State createState() => _VerifiedShieldState(); +} + +class _VerifiedShieldState extends State { + bool isVerified = false; + Contact? contact; + + StreamSubscription>? stream; + + @override + void initState() { + if (widget.group != null) { + stream = twonlyDB.groupsDao + .watchGroupContact(widget.group!.groupId) + .listen((contacts) { + if (contacts.length == 1) { + contact = contacts.first; + } + setState(() { + isVerified = contacts.any((t) => t.verified); + }); + }); + } else if (widget.contact != null) { + isVerified = widget.contact!.verified; + contact = widget.contact; + } + + super.initState(); + } + + @override + void dispose() { + stream?.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { return GestureDetector( - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return ContactVerifyView(contact); + onTap: (contact == null) + ? null + : () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return ContactVerifyView(contact!); + }, + ), + ); }, - ), - ); - }, child: Tooltip( - message: contact.verified + message: isVerified ? 'You verified this contact' : 'You have not verifies this contact.', child: FaIcon( - contact.verified - ? FontAwesomeIcons.shieldHeart - : Icons.gpp_maybe_rounded, - color: contact.verified - ? Theme.of(context).colorScheme.primary - : Colors.red, - size: size, + isVerified ? FontAwesomeIcons.shieldHeart : Icons.gpp_maybe_rounded, + color: + isVerified ? Theme.of(context).colorScheme.primary : Colors.red, + size: widget.size, ), ), ); diff --git a/lib/src/views/contact/contact.view.dart b/lib/src/views/contact/contact.view.dart index 5ff2d8e..c38d53e 100644 --- a/lib/src/views/contact/contact.view.dart +++ b/lib/src/views/contact/contact.view.dart @@ -116,7 +116,7 @@ class _ContactViewState extends State { children: [ Padding( padding: const EdgeInsets.only(right: 10), - child: VerifiedShield(contact), + child: VerifiedShield(key: GlobalKey(), contact: contact), ), Text( getContactDisplayName(contact), diff --git a/lib/src/views/settings/backup/backup.view.dart b/lib/src/views/settings/backup/backup.view.dart index 87daaab..92a42eb 100644 --- a/lib/src/views/settings/backup/backup.view.dart +++ b/lib/src/views/settings/backup/backup.view.dart @@ -1,12 +1,11 @@ import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart'; import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/settings/backup/twonly_safe_backup.view.dart'; @@ -48,11 +47,10 @@ class _BackupViewState extends State { } Future initAsync() async { - final user = await getUser(); - twonlySafeBackup = user?.twonlySafeBackup; + twonlySafeBackup = gUser.twonlySafeBackup; backupServer = defaultBackupServer; - if (user?.backupServer != null) { - backupServer = user!.backupServer!; + if (gUser.backupServer != null) { + backupServer = gUser.backupServer!; } setState(() {}); } diff --git a/lib/src/views/settings/backup/twonly_safe_server.view.dart b/lib/src/views/settings/backup/twonly_safe_server.view.dart index 973f87a..a7c2cf8 100644 --- a/lib/src/views/settings/backup/twonly_safe_server.view.dart +++ b/lib/src/views/settings/backup/twonly_safe_server.view.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:http/http.dart' as http; +import 'package:twonly/globals.dart'; import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; @@ -30,9 +31,8 @@ class _TwonlySafeServerViewState extends State { } Future initAsync() async { - final user = await getUser(); - if (user?.backupServer != null) { - final uri = Uri.parse(user!.backupServer!.serverUrl); + if (gUser.backupServer != null) { + final uri = Uri.parse(gUser.backupServer!.serverUrl); // remove user auth data final serverUrl = Uri( scheme: uri.scheme, diff --git a/lib/src/views/settings/settings_main.view.dart b/lib/src/views/settings/settings_main.view.dart index c27b5a4..b58efcd 100644 --- a/lib/src/views/settings/settings_main.view.dart +++ b/lib/src/views/settings/settings_main.view.dart @@ -1,10 +1,7 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:twonly/src/model/json/userdata.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/better_list_title.dart'; import 'package:twonly/src/views/settings/account.view.dart'; @@ -28,247 +25,232 @@ class SettingsMainView extends StatefulWidget { } class _SettingsMainViewState extends State { - UserData? userData; - - @override - void initState() { - super.initState(); - unawaited(initAsync()); - } - - Future initAsync() async { - userData = await getUser(); - setState(() {}); - } - @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(context.lang.settingsTitle), ), - body: (userData == null) - ? null - : ListView( + body: ListView( + children: [ + Padding( + padding: const EdgeInsets.all(30), + child: Row( children: [ - Padding( - padding: const EdgeInsets.all(30), - child: Row( - children: [ - Expanded( - child: GestureDetector( - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const ProfileView(); - }, - ), - ); - await initAsync(); - }, - child: ColoredBox( - color: context.color.surface.withAlpha(0), - child: Row( - children: [ - AvatarIcon( - userData: userData, - fontSize: 30, - ), - Container(width: 20, color: Colors.transparent), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userData!.displayName, - style: const TextStyle(fontSize: 20), - textAlign: TextAlign.left, - ), - Text( - userData!.username, - style: const TextStyle( - fontSize: 14, - ), - textAlign: TextAlign.left, - ), - ], - ), - ], - ), - ), - ), - ), - // Align( - // alignment: Alignment.centerRight, - // child: IconButton( - // onPressed: () {}, - // icon: FaIcon(FontAwesomeIcons.qrcode), - // ), - // ) - ], - ), - ), - BetterListTile( - icon: FontAwesomeIcons.user, - text: context.lang.settingsAccount, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const AccountView(); - }, - ), - ); - }, - ), - BetterListTile( - icon: FontAwesomeIcons.shieldHeart, - text: context.lang.settingsSubscription, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const SubscriptionView(); - }, - ), - ); - }, - ), - BetterListTile( - icon: Icons.lock_clock_rounded, - text: context.lang.settingsBackup, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const BackupView(); - }, - ), - ); - }, - ), - const Divider(), - BetterListTile( - icon: FontAwesomeIcons.sun, - text: context.lang.settingsAppearance, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const AppearanceView(); - }, - ), - ); - }, - ), - BetterListTile( - icon: FontAwesomeIcons.comment, - text: context.lang.settingsChats, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const ChatSettingsView(); - }, - ), - ); - }, - ), - BetterListTile( - icon: FontAwesomeIcons.lock, - text: context.lang.settingsPrivacy, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const PrivacyView(); - }, - ), - ); - }, - ), - BetterListTile( - icon: FontAwesomeIcons.bell, - text: context.lang.settingsNotification, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const NotificationView(); - }, - ), - ); - }, - ), - BetterListTile( - icon: FontAwesomeIcons.chartPie, - iconSize: 15, - text: context.lang.settingsStorageData, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const DataAndStorageView(); - }, - ), - ); - }, - ), - const Divider(), - BetterListTile( - icon: FontAwesomeIcons.circleQuestion, - text: context.lang.settingsHelp, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const HelpView(); - }, - ), - ); - }, - ), - if (userData != null && userData!.isDeveloper) - BetterListTile( - icon: FontAwesomeIcons.code, - text: 'Developer Settings', + Expanded( + child: GestureDetector( onTap: () async { await Navigator.push( context, MaterialPageRoute( builder: (context) { - return const DeveloperSettingsView(); + return const ProfileView(); }, ), ); + setState(() {}); }, - ), - BetterListTile( - icon: FontAwesomeIcons.shareFromSquare, - text: context.lang.inviteFriends, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const ShareWithFriendsView(); - }, + child: ColoredBox( + color: context.color.surface.withAlpha(0), + child: Row( + children: [ + AvatarIcon( + userData: gUser, + fontSize: 30, + ), + Container(width: 20, color: Colors.transparent), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + gUser.displayName, + style: const TextStyle(fontSize: 20), + textAlign: TextAlign.left, + ), + Text( + gUser.username, + style: const TextStyle( + fontSize: 14, + ), + textAlign: TextAlign.left, + ), + ], + ), + ], ), - ); - }, + ), + ), ), + // Align( + // alignment: Alignment.centerRight, + // child: IconButton( + // onPressed: () {}, + // icon: FaIcon(FontAwesomeIcons.qrcode), + // ), + // ) ], ), + ), + BetterListTile( + icon: FontAwesomeIcons.user, + text: context.lang.settingsAccount, + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const AccountView(); + }, + ), + ); + }, + ), + BetterListTile( + icon: FontAwesomeIcons.shieldHeart, + text: context.lang.settingsSubscription, + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const SubscriptionView(); + }, + ), + ); + }, + ), + BetterListTile( + icon: Icons.lock_clock_rounded, + text: context.lang.settingsBackup, + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const BackupView(); + }, + ), + ); + }, + ), + const Divider(), + BetterListTile( + icon: FontAwesomeIcons.sun, + text: context.lang.settingsAppearance, + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const AppearanceView(); + }, + ), + ); + }, + ), + BetterListTile( + icon: FontAwesomeIcons.comment, + text: context.lang.settingsChats, + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const ChatSettingsView(); + }, + ), + ); + }, + ), + BetterListTile( + icon: FontAwesomeIcons.lock, + text: context.lang.settingsPrivacy, + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const PrivacyView(); + }, + ), + ); + }, + ), + BetterListTile( + icon: FontAwesomeIcons.bell, + text: context.lang.settingsNotification, + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const NotificationView(); + }, + ), + ); + }, + ), + BetterListTile( + icon: FontAwesomeIcons.chartPie, + iconSize: 15, + text: context.lang.settingsStorageData, + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const DataAndStorageView(); + }, + ), + ); + }, + ), + const Divider(), + BetterListTile( + icon: FontAwesomeIcons.circleQuestion, + text: context.lang.settingsHelp, + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const HelpView(); + }, + ), + ); + }, + ), + if (gUser.isDeveloper) + BetterListTile( + icon: FontAwesomeIcons.code, + text: 'Developer Settings', + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const DeveloperSettingsView(); + }, + ), + ); + }, + ), + BetterListTile( + icon: FontAwesomeIcons.shareFromSquare, + text: context.lang.inviteFriends, + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const ShareWithFriendsView(); + }, + ), + ); + }, + ), + ], + ), ); } } diff --git a/lib/src/views/settings/subscription/additional_users.view.dart b/lib/src/views/settings/subscription/additional_users.view.dart index 5a76e88..1bb21dd 100644 --- a/lib/src/views/settings/subscription/additional_users.view.dart +++ b/lib/src/views/settings/subscription/additional_users.view.dart @@ -23,10 +23,9 @@ Future?> loadAdditionalUserInvites() async { }); return ballance; } - final user = await getUser(); - if (user != null && user.lastPlanBallance != null) { + if (gUser.lastPlanBallance != null) { try { - final decoded = jsonDecode(user.additionalUserInvites!) as List; + final decoded = jsonDecode(gUser.additionalUserInvites!) as List; return decoded.map(Response_AddAccountsInvite.fromJson).toList(); } catch (e) { Log.error('could not parse additional user json: $e');