mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:28:41 +00:00
fix verification shield and remove backup notice
This commit is contained in:
parent
350fb899e1
commit
df9b055ba7
24 changed files with 340 additions and 421 deletions
|
|
@ -91,6 +91,17 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
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() {
|
||||
return select(groups).watch();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,10 +32,12 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
|
||||
Stream<List<Message>> 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
|
|||
messages,
|
||||
messages.messageId.equalsExp(reactions.messageId),
|
||||
useColumns: false,
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
..where(messages.groupId.equals(groupId))
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ class ApiService {
|
|||
unawaited(retransmitRawBytes());
|
||||
unawaited(tryTransmitMessages());
|
||||
unawaited(tryDownloadAllMediaFiles());
|
||||
unawaited(notifyContactsAboutProfileChange());
|
||||
twonlyDB.markUpdated();
|
||||
unawaited(syncFlameCounters());
|
||||
unawaited(setupNotificationWithUsers());
|
||||
|
|
|
|||
|
|
@ -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<void> 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<String, List<String>> defaultAutoDownloadOptions = {
|
|||
Future<bool> 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) {
|
||||
|
|
|
|||
|
|
@ -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<void> notifyContactAboutOpeningMessage(
|
|||
}
|
||||
|
||||
Future<void> 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,
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,13 +20,11 @@ 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 {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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<bool> createNewSignalSession(Response_UserData userData) async {
|
||||
final SignalProtocolStore? signalStore = await getSignalStore();
|
||||
|
|
@ -84,8 +84,7 @@ Future<void> deleteSessionWithTarget(int target) async {
|
|||
|
||||
Future<Fingerprint?> 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<Fingerprint?> 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,
|
||||
|
|
|
|||
|
|
@ -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<void> 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(
|
||||
|
|
|
|||
|
|
@ -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<void> 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<void> 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<void> 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<void> 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;
|
||||
|
|
|
|||
|
|
@ -181,17 +181,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
Future<void> 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(() {});
|
||||
|
|
|
|||
|
|
@ -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<Emojis> {
|
|||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
final user = await getUser();
|
||||
if (user == null) return;
|
||||
setState(() {
|
||||
lastUsed = user.lastUsedEditorEmojis ?? [];
|
||||
lastUsed = gUser.lastUsedEditorEmojis ?? [];
|
||||
lastUsed.addAll(emojis);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ChatListView> {
|
|||
: 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];
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ChatMessagesView> {
|
|||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<MessageSendStateIcon> {
|
|||
SizedBox(
|
||||
height: 18,
|
||||
child: EmojiAnimation(emoji: widget.lastReaction!.emoji),
|
||||
)
|
||||
),
|
||||
];
|
||||
} else {
|
||||
icons = [
|
||||
|
|
@ -218,7 +217,7 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
// Log.info("DISPLAY REACTION");
|
||||
|
|
|
|||
|
|
@ -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<ReactionButtons> {
|
|||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
final user = await getUser();
|
||||
if (user != null && user.preSelectedEmojies != null) {
|
||||
selectedEmojis = user.preSelectedEmojies!;
|
||||
if (gUser.preSelectedEmojies != null) {
|
||||
selectedEmojis = gUser.preSelectedEmojies!;
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ class _FlameCounterWidgetState extends State<FlameCounterWidget> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (flameCounter < 1) return Container();
|
||||
return Row(
|
||||
children: [
|
||||
if (widget.prefix) const SizedBox(width: 5),
|
||||
|
|
|
|||
|
|
@ -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<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
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ class _ContactViewState extends State<ContactView> {
|
|||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: VerifiedShield(contact),
|
||||
child: VerifiedShield(key: GlobalKey(), contact: contact),
|
||||
),
|
||||
Text(
|
||||
getContactDisplayName(contact),
|
||||
|
|
|
|||
|
|
@ -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<BackupView> {
|
|||
}
|
||||
|
||||
Future<void> 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(() {});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<TwonlySafeServerView> {
|
|||
}
|
||||
|
||||
Future<void> 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,
|
||||
|
|
|
|||
|
|
@ -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<SettingsMainView> {
|
||||
UserData? userData;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
unawaited(initAsync());
|
||||
}
|
||||
|
||||
Future<void> 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();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,9 @@ Future<List<Response_AddAccountsInvite>?> 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<String>;
|
||||
final decoded = jsonDecode(gUser.additionalUserInvites!) as List<String>;
|
||||
return decoded.map(Response_AddAccountsInvite.fromJson).toList();
|
||||
} catch (e) {
|
||||
Log.error('could not parse additional user json: $e');
|
||||
|
|
|
|||
Loading…
Reference in a new issue