fix verification shield and remove backup notice

This commit is contained in:
otsmr 2025-10-27 20:44:14 +01:00
parent 350fb899e1
commit df9b055ba7
24 changed files with 340 additions and 421 deletions

View file

@ -91,6 +91,17 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
return query.map((row) => row.readTable(contacts)).get(); return query.map((row) => row.readTable(contacts)).get();
} }
Stream<List<Contact>> 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<List<Group>> watchGroups() { Stream<List<Group>> watchGroups() {
return select(groups).watch(); return select(groups).watch();
} }

View file

@ -32,10 +32,12 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
Stream<List<Message>> watchMessageNotOpened(String groupId) { Stream<List<Message>> watchMessageNotOpened(String groupId) {
return (select(messages) return (select(messages)
..where((t) => ..where(
(t) =>
t.openedAt.isNull() & t.openedAt.isNull() &
t.groupId.equals(groupId) & t.groupId.equals(groupId) &
t.isDeletedFromSender.equals(false)) t.isDeletedFromSender.equals(false),
)
..orderBy([(t) => OrderingTerm.desc(t.createdAt)])) ..orderBy([(t) => OrderingTerm.desc(t.createdAt)]))
.watch(); .watch();
} }

View file

@ -61,7 +61,7 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
messages, messages,
messages.messageId.equalsExp(reactions.messageId), messages.messageId.equalsExp(reactions.messageId),
useColumns: false, useColumns: false,
) ),
], ],
) )
..where(messages.groupId.equals(groupId)) ..where(messages.groupId.equals(groupId))

View file

@ -94,7 +94,6 @@ class ApiService {
unawaited(retransmitRawBytes()); unawaited(retransmitRawBytes());
unawaited(tryTransmitMessages()); unawaited(tryTransmitMessages());
unawaited(tryDownloadAllMediaFiles()); unawaited(tryDownloadAllMediaFiles());
unawaited(notifyContactsAboutProfileChange());
twonlyDB.markUpdated(); twonlyDB.markUpdated();
unawaited(syncFlameCounters()); unawaited(syncFlameCounters());
unawaited(setupNotificationWithUsers()); unawaited(setupNotificationWithUsers());

View file

@ -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/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
Future<void> tryDownloadAllMediaFiles({bool force = false}) async { Future<void> tryDownloadAllMediaFiles({bool force = false}) async {
// This is called when WebSocket is newly connected, so allow all downloads to be restarted. // This is called when WebSocket is newly connected, so allow all downloads to be restarted.
@ -44,8 +43,7 @@ Map<String, List<String>> defaultAutoDownloadOptions = {
Future<bool> isAllowedToDownload({required bool isVideo}) async { Future<bool> isAllowedToDownload({required bool isVideo}) async {
final connectivityResult = await Connectivity().checkConnectivity(); final connectivityResult = await Connectivity().checkConnectivity();
final user = await getUser(); final options = gUser.autoDownloadOptions ?? defaultAutoDownloadOptions;
final options = user!.autoDownloadOptions ?? defaultAutoDownloadOptions;
if (connectivityResult.contains(ConnectivityResult.mobile)) { if (connectivityResult.contains(ConnectivityResult.mobile)) {
if (isVideo) { if (isVideo) {

View file

@ -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/services/signal/encryption.signal.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
final lockRetransmission = Mutex(); final lockRetransmission = Mutex();
@ -277,15 +276,13 @@ Future<void> notifyContactAboutOpeningMessage(
} }
Future<void> notifyContactsAboutProfileChange({int? onlyToContact}) async { Future<void> notifyContactsAboutProfileChange({int? onlyToContact}) async {
final user = await getUser(); if (gUser.avatarSvg == null) return;
if (user == null) return;
if (user.avatarSvg == null) return;
final encryptedContent = pb.EncryptedContent( final encryptedContent = pb.EncryptedContent(
contactUpdate: pb.EncryptedContent_ContactUpdate( contactUpdate: pb.EncryptedContent_ContactUpdate(
type: pb.EncryptedContent_ContactUpdate_Type.UPDATE, type: pb.EncryptedContent_ContactUpdate_Type.UPDATE,
avatarSvgCompressed: gzip.encode(utf8.encode(user.avatarSvg!)), avatarSvgCompressed: gzip.encode(utf8.encode(gUser.avatarSvg!)),
displayName: user.displayName, displayName: gUser.displayName,
), ),
); );

View file

@ -20,13 +20,11 @@ Future<IdentityKeyPair?> getSignalIdentityKeyPair() async {
// This function runs after the clients authenticated with the server. // This function runs after the clients authenticated with the server.
// It then checks if it should update a new session key // It then checks if it should update a new session key
Future<void> signalHandleNewServerConnection() async { Future<void> signalHandleNewServerConnection() async {
final user = await getUser(); if (gUser.signalLastSignedPreKeyUpdated != null) {
if (user == null) return;
if (user.signalLastSignedPreKeyUpdated != null) {
final fortyEightHoursAgo = final fortyEightHoursAgo =
DateTime.now().subtract(const Duration(hours: 48)); DateTime.now().subtract(const Duration(hours: 48));
final isYoungerThan48Hours = final isYoungerThan48Hours =
(user.signalLastSignedPreKeyUpdated!).isAfter(fortyEightHoursAgo); (gUser.signalLastSignedPreKeyUpdated!).isAfter(fortyEightHoursAgo);
if (isYoungerThan48Hours) { if (isYoungerThan48Hours) {
// The key does live for 48 hours then it expires and a new key is generated. // The key does live for 48 hours then it expires and a new key is generated.
return; return;

View file

@ -1,10 +1,10 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; 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/model/protobuf/api/websocket/server_to_client.pb.dart';
import 'package:twonly/src/services/signal/consts.signal.dart'; import 'package:twonly/src/services/signal/consts.signal.dart';
import 'package:twonly/src/services/signal/utils.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/storage.dart';
Future<bool> createNewSignalSession(Response_UserData userData) async { Future<bool> createNewSignalSession(Response_UserData userData) async {
final SignalProtocolStore? signalStore = await getSignalStore(); final SignalProtocolStore? signalStore = await getSignalStore();
@ -84,8 +84,7 @@ Future<void> deleteSessionWithTarget(int target) async {
Future<Fingerprint?> generateSessionFingerPrint(int target) async { Future<Fingerprint?> generateSessionFingerPrint(int target) async {
final signalStore = await getSignalStore(); final signalStore = await getSignalStore();
final user = await getUser(); if (signalStore == null) return null;
if (signalStore == null || user == null) return null;
try { try {
final targetIdentity = await signalStore final targetIdentity = await signalStore
.getIdentity(SignalProtocolAddress(target.toString(), defaultDeviceId)); .getIdentity(SignalProtocolAddress(target.toString(), defaultDeviceId));
@ -93,7 +92,7 @@ Future<Fingerprint?> generateSessionFingerPrint(int target) async {
final generator = NumericFingerprintGenerator(5200); final generator = NumericFingerprintGenerator(5200);
final localFingerprint = generator.createFor( final localFingerprint = generator.createFor(
1, 1,
Uint8List.fromList([user.userId]), Uint8List.fromList([gUser.userId]),
(await signalStore.getIdentityKeyPair()).getPublicKey(), (await signalStore.getIdentityKeyPair()).getPublicKey(),
Uint8List.fromList([target]), Uint8List.fromList([target]),
targetIdentity, targetIdentity,

View file

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:hashlib/hashlib.dart'; import 'package:hashlib/hashlib.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:twonly/globals.dart';
import 'package:twonly/src/model/json/userdata.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/services/twonly_safe/create_backup.twonly_safe.dart';
import 'package:twonly/src/utils/log.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'; import 'package:twonly/src/utils/storage.dart';
Future<void> enableTwonlySafe(String password) async { Future<void> enableTwonlySafe(String password) async {
final user = await getUser(); final (backupId, encryptionKey) =
if (user == null) return; await getMasterKey(password, gUser.username);
final (backupId, encryptionKey) = await getMasterKey(password, user.username);
await updateUserdata((user) { await updateUserdata((user) {
user.twonlySafeBackup = TwonlySafeBackup( user.twonlySafeBackup = TwonlySafeBackup(

View file

@ -10,6 +10,7 @@ import 'package:drift_flutter/drift_flutter.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:path_provider/path_provider.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/constants/secure_storage_keys.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/json/userdata.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'; import 'package:twonly/src/views/settings/backup/backup.view.dart';
Future<void> performTwonlySafeBackup({bool force = false}) async { Future<void> performTwonlySafeBackup({bool force = false}) async {
final user = await getUser(); if (gUser.twonlySafeBackup == null) {
if (user == null || user.twonlySafeBackup == null) {
return; return;
} }
if (user.twonlySafeBackup!.backupUploadState == if (gUser.twonlySafeBackup!.backupUploadState ==
LastBackupUploadState.pending) { LastBackupUploadState.pending) {
Log.warn('Backup upload is already pending.'); Log.warn('Backup upload is already pending.');
return; return;
} }
final lastUpdateTime = user.twonlySafeBackup!.lastBackupDone; final lastUpdateTime = gUser.twonlySafeBackup!.lastBackupDone;
if (!force && lastUpdateTime != null) { if (!force && lastUpdateTime != null) {
if (lastUpdateTime if (lastUpdateTime
.isAfter(DateTime.now().subtract(const Duration(days: 1)))) { .isAfter(DateTime.now().subtract(const Duration(days: 1)))) {
@ -117,8 +116,8 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
final backupHash = uint8ListToHex((await Sha256().hash(backupBytes)).bytes); final backupHash = uint8ListToHex((await Sha256().hash(backupBytes)).bytes);
if (user.twonlySafeBackup!.lastBackupDone == null || if (gUser.twonlySafeBackup!.lastBackupDone == null ||
user.twonlySafeBackup!.lastBackupDone! gUser.twonlySafeBackup!.lastBackupDone!
.isAfter(DateTime.now().subtract(const Duration(days: 90)))) { .isAfter(DateTime.now().subtract(const Duration(days: 90)))) {
force = true; force = true;
} }
@ -144,7 +143,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
final secretBox = await chacha20.encrypt( final secretBox = await chacha20.encrypt(
backupBytes, backupBytes,
secretKey: SecretKey(user.twonlySafeBackup!.encryptionKey), secretKey: SecretKey(gUser.twonlySafeBackup!.encryptionKey),
nonce: nonce, nonce: nonce,
); );
@ -165,8 +164,8 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
'Create twonly Safe backup with a size of ${encryptedBackupBytes.length} bytes.', 'Create twonly Safe backup with a size of ${encryptedBackupBytes.length} bytes.',
); );
if (user.backupServer != null) { if (gUser.backupServer != null) {
if (encryptedBackupBytes.length > user.backupServer!.maxBackupBytes) { if (encryptedBackupBytes.length > gUser.backupServer!.maxBackupBytes) {
Log.error('Backup is to big for the alternative backup server.'); Log.error('Backup is to big for the alternative backup server.');
await updateUserdata((user) { await updateUserdata((user) {
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed; user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed;

View file

@ -181,18 +181,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
Future<void> initAsync() async { Future<void> initAsync() async {
hasAudioPermission = await Permission.microphone.isGranted; hasAudioPermission = await Permission.microphone.isGranted;
if (!hasAudioPermission) { if (!hasAudioPermission && !gUser.requestedAudioPermission) {
final user = await getUser();
if (user != null) {
if (!user.requestedAudioPermission) {
await updateUserdata((u) { await updateUserdata((u) {
u.requestedAudioPermission = true; u.requestedAudioPermission = true;
return u; return u;
}); });
await requestMicrophonePermission(); await requestMicrophonePermission();
} }
}
}
if (!mounted) return; if (!mounted) return;
setState(() {}); setState(() {});
} }

View file

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/utils/storage.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/data.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
@ -22,10 +23,8 @@ class _EmojisState extends State<Emojis> {
} }
Future<void> initAsync() async { Future<void> initAsync() async {
final user = await getUser();
if (user == null) return;
setState(() { setState(() {
lastUsed = user.lastUsedEditorEmojis ?? []; lastUsed = gUser.lastUsedEditorEmojis ?? [];
lastUsed.addAll(emojis); lastUsed.addAll(emojis);
}); });
} }

View file

@ -11,7 +11,6 @@ import 'package:twonly/src/providers/connection.provider.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/chats/add_new_user.view.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/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/feedback_btn.dart';
import 'package:twonly/src/views/chats/chat_list_components/group_list_item.dart'; import 'package:twonly/src/views/chats/chat_list_components/group_list_item.dart';
@ -237,13 +236,8 @@ class _ChatListViewState extends State<ChatListView> {
: ListView.builder( : ListView.builder(
itemCount: _groupsPinned.length + itemCount: _groupsPinned.length +
(_groupsPinned.isNotEmpty ? 1 : 0) + (_groupsPinned.isNotEmpty ? 1 : 0) +
_groupsNotPinned.length + _groupsNotPinned.length,
1,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == 0) {
return const BackupNoticeCard();
}
index -= 1;
// Check if the index is for the pinned users // Check if the index is for the pinned users
if (index < _groupsPinned.length) { if (index < _groupsPinned.length) {
final group = _groupsPinned[index]; final group = _groupsPinned[index];

View file

@ -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<BackupNoticeCard> createState() => _BackupNoticeCardState();
}
class _BackupNoticeCardState extends State<BackupNoticeCard> {
bool showBackupNotice = false;
@override
void initState() {
super.initState();
unawaited(initAsync());
}
Future<void> 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),
),
],
),
],
),
),
);
}
}

View file

@ -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/chat_list_entry.dart';
import 'package:twonly/src/views/chats/chat_messages_components/response_container.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/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/contact/contact.view.dart';
import 'package:twonly/src/views/groups/group.view.dart'; import 'package:twonly/src/views/groups/group.view.dart';
import 'package:twonly/src/views/tutorial/tutorials.dart'; import 'package:twonly/src/views/tutorial/tutorials.dart';
@ -242,8 +244,9 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
children: [ children: [
Text(group.groupName), Text(group.groupName),
const SizedBox(width: 10), const SizedBox(width: 10),
// if (group.verified) VerifiedShield(key: verifyShieldKey, group: group),
// VerifiedShield(key: verifyShieldKey, group), const SizedBox(width: 10),
FlameCounterWidget(groupId: group.groupId),
], ],
), ),
), ),

View file

@ -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/mediafiles.table.dart';
import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart';
import 'package:twonly/src/database/twonly.db.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/utils/misc.dart';
import 'package:twonly/src/views/components/animate_icon.dart'; import 'package:twonly/src/views/components/animate_icon.dart';
@ -202,7 +201,7 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
SizedBox( SizedBox(
height: 18, height: 18,
child: EmojiAnimation(emoji: widget.lastReaction!.emoji), child: EmojiAnimation(emoji: widget.lastReaction!.emoji),
) ),
]; ];
} else { } else {
icons = [ icons = [
@ -218,7 +217,7 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
), ),
), ),
), ),
) ),
]; ];
} }
// Log.info("DISPLAY REACTION"); // Log.info("DISPLAY REACTION");

View file

@ -1,6 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; 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/chats/media_viewer_components/emoji_reactions_row.component.dart';
import 'package:twonly/src/views/components/animate_icon.dart'; import 'package:twonly/src/views/components/animate_icon.dart';
@ -39,9 +39,8 @@ class _ReactionButtonsState extends State<ReactionButtons> {
} }
Future<void> initAsync() async { Future<void> initAsync() async {
final user = await getUser(); if (gUser.preSelectedEmojies != null) {
if (user != null && user.preSelectedEmojies != null) { selectedEmojis = gUser.preSelectedEmojies!;
selectedEmojis = user.preSelectedEmojies!;
} }
setState(() {}); setState(() {});
} }

View file

@ -55,6 +55,7 @@ class _FlameCounterWidgetState extends State<FlameCounterWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (flameCounter < 1) return Container();
return Row( return Row(
children: [ children: [
if (widget.prefix) const SizedBox(width: 5), if (widget.prefix) const SizedBox(width: 5),

View file

@ -1,38 +1,82 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/views/contact/contact_verify.view.dart'; import 'package:twonly/src/views/contact/contact_verify.view.dart';
class VerifiedShield extends StatelessWidget { class VerifiedShield extends StatefulWidget {
const VerifiedShield(this.contact, {super.key, this.size = 18}); const VerifiedShield({
final Contact contact; this.contact,
this.group,
super.key,
this.size = 18,
});
final Group? group;
final Contact? contact;
final double size; final double size;
@override
State<VerifiedShield> createState() => _VerifiedShieldState();
}
class _VerifiedShieldState extends State<VerifiedShield> {
bool isVerified = false;
Contact? contact;
StreamSubscription<List<Contact>>? 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: () async { onTap: (contact == null)
? null
: () async {
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) { builder: (context) {
return ContactVerifyView(contact); return ContactVerifyView(contact!);
}, },
), ),
); );
}, },
child: Tooltip( child: Tooltip(
message: contact.verified message: isVerified
? 'You verified this contact' ? 'You verified this contact'
: 'You have not verifies this contact.', : 'You have not verifies this contact.',
child: FaIcon( child: FaIcon(
contact.verified isVerified ? FontAwesomeIcons.shieldHeart : Icons.gpp_maybe_rounded,
? FontAwesomeIcons.shieldHeart color:
: Icons.gpp_maybe_rounded, isVerified ? Theme.of(context).colorScheme.primary : Colors.red,
color: contact.verified size: widget.size,
? Theme.of(context).colorScheme.primary
: Colors.red,
size: size,
), ),
), ),
); );

View file

@ -116,7 +116,7 @@ class _ContactViewState extends State<ContactView> {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(right: 10), padding: const EdgeInsets.only(right: 10),
child: VerifiedShield(contact), child: VerifiedShield(key: GlobalKey(), contact: contact),
), ),
Text( Text(
getContactDisplayName(contact), getContactDisplayName(contact),

View file

@ -1,12 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/model/json/userdata.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/common.twonly_safe.dart';
import 'package:twonly/src/services/twonly_safe/create_backup.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/misc.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/views/settings/backup/twonly_safe_backup.view.dart'; import 'package:twonly/src/views/settings/backup/twonly_safe_backup.view.dart';
@ -48,11 +47,10 @@ class _BackupViewState extends State<BackupView> {
} }
Future<void> initAsync() async { Future<void> initAsync() async {
final user = await getUser(); twonlySafeBackup = gUser.twonlySafeBackup;
twonlySafeBackup = user?.twonlySafeBackup;
backupServer = defaultBackupServer; backupServer = defaultBackupServer;
if (user?.backupServer != null) { if (gUser.backupServer != null) {
backupServer = user!.backupServer!; backupServer = gUser.backupServer!;
} }
setState(() {}); setState(() {});
} }

View file

@ -5,6 +5,7 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:twonly/globals.dart';
import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/model/json/userdata.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -30,9 +31,8 @@ class _TwonlySafeServerViewState extends State<TwonlySafeServerView> {
} }
Future<void> initAsync() async { Future<void> initAsync() async {
final user = await getUser(); if (gUser.backupServer != null) {
if (user?.backupServer != null) { final uri = Uri.parse(gUser.backupServer!.serverUrl);
final uri = Uri.parse(user!.backupServer!.serverUrl);
// remove user auth data // remove user auth data
final serverUrl = Uri( final serverUrl = Uri(
scheme: uri.scheme, scheme: uri.scheme,

View file

@ -1,10 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/components/avatar_icon.component.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/components/better_list_title.dart';
import 'package:twonly/src/views/settings/account.view.dart'; import 'package:twonly/src/views/settings/account.view.dart';
@ -28,28 +25,13 @@ class SettingsMainView extends StatefulWidget {
} }
class _SettingsMainViewState extends State<SettingsMainView> { class _SettingsMainViewState extends State<SettingsMainView> {
UserData? userData;
@override
void initState() {
super.initState();
unawaited(initAsync());
}
Future<void> initAsync() async {
userData = await getUser();
setState(() {});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(context.lang.settingsTitle), title: Text(context.lang.settingsTitle),
), ),
body: (userData == null) body: ListView(
? null
: ListView(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(30), padding: const EdgeInsets.all(30),
@ -66,14 +48,14 @@ class _SettingsMainViewState extends State<SettingsMainView> {
}, },
), ),
); );
await initAsync(); setState(() {});
}, },
child: ColoredBox( child: ColoredBox(
color: context.color.surface.withAlpha(0), color: context.color.surface.withAlpha(0),
child: Row( child: Row(
children: [ children: [
AvatarIcon( AvatarIcon(
userData: userData, userData: gUser,
fontSize: 30, fontSize: 30,
), ),
Container(width: 20, color: Colors.transparent), Container(width: 20, color: Colors.transparent),
@ -81,12 +63,12 @@ class _SettingsMainViewState extends State<SettingsMainView> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
userData!.displayName, gUser.displayName,
style: const TextStyle(fontSize: 20), style: const TextStyle(fontSize: 20),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
Text( Text(
userData!.username, gUser.username,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
), ),
@ -238,7 +220,7 @@ class _SettingsMainViewState extends State<SettingsMainView> {
); );
}, },
), ),
if (userData != null && userData!.isDeveloper) if (gUser.isDeveloper)
BetterListTile( BetterListTile(
icon: FontAwesomeIcons.code, icon: FontAwesomeIcons.code,
text: 'Developer Settings', text: 'Developer Settings',

View file

@ -23,10 +23,9 @@ Future<List<Response_AddAccountsInvite>?> loadAdditionalUserInvites() async {
}); });
return ballance; return ballance;
} }
final user = await getUser(); if (gUser.lastPlanBallance != null) {
if (user != null && user.lastPlanBallance != null) {
try { try {
final decoded = jsonDecode(user.additionalUserInvites!) as List<String>; final decoded = jsonDecode(gUser.additionalUserInvites!) as List<String>;
return decoded.map(Response_AddAccountsInvite.fromJson).toList(); return decoded.map(Response_AddAccountsInvite.fromJson).toList();
} catch (e) { } catch (e) {
Log.error('could not parse additional user json: $e'); Log.error('could not parse additional user json: $e');