mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 13:08:42 +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();
|
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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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(() {});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
|
|
@ -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/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),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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(() {});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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(() {});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue