mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 03:42:13 +00:00
Fix: Some message where not marked as opened.
This commit is contained in:
parent
11c0ad908e
commit
0204a41d43
8 changed files with 240 additions and 187 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -10,6 +10,9 @@
|
|||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
*.sqlite
|
||||
*.sqlite-shm
|
||||
*.sqlite-wal
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
- New: Tutorial on how to use zoom.
|
||||
- New: Manage storage view.
|
||||
- Improved: Media thumbnails for faster loading.
|
||||
- Fix: Some message where not marked as opened.
|
||||
|
||||
## 0.2.12
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,5 @@ class AppState {
|
|||
static bool isInBackgroundTask = false;
|
||||
static bool allowErrorTrackingViaSentry = false;
|
||||
static bool gotMessageFromServer = false;
|
||||
static int latestAppVersionId = 115;
|
||||
|
||||
static int latestAppVersionId = 116;
|
||||
}
|
||||
|
|
|
|||
153
lib/main.dart
153
lib/main.dart
|
|
@ -1,10 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
|
@ -15,33 +11,24 @@ import 'package:twonly/core/frb_generated.dart';
|
|||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/callbacks/callbacks.dart';
|
||||
import 'package:twonly/src/constants/secure_storage.keys.dart';
|
||||
import 'package:twonly/src/database/signal/signal_signed_pre_key_store.dart'
|
||||
show getSignalSignedPreKeyStoreOld;
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/json/signal_identity.model.dart';
|
||||
import 'package:twonly/src/providers/connection.provider.dart';
|
||||
import 'package:twonly/src/providers/image_editor.provider.dart';
|
||||
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||
import 'package:twonly/src/providers/settings.provider.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/media_background.api.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
||||
import 'package:twonly/src/services/background/callback_dispatcher.background.dart';
|
||||
import 'package:twonly/src/services/backup.service.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/services/memories/memories.service.dart';
|
||||
import 'package:twonly/src/services/migrations.service.dart';
|
||||
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
||||
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/services/user_discovery.service.dart';
|
||||
import 'package:twonly/src/utils/avatars.dart';
|
||||
import 'package:twonly/src/utils/exclusive_access.utils.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/secure_storage.dart';
|
||||
import 'package:twonly/src/utils/startup_guard.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
|
||||
|
||||
final _initMutex = Mutex();
|
||||
|
||||
|
|
@ -167,144 +154,6 @@ void main() async {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> runMigrations() async {
|
||||
if (userService.currentUser.appVersion < 90) {
|
||||
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
|
||||
await twonlyDB.mediaFilesDao.updateAllRetransmissionUploadingState();
|
||||
await UserService.update((u) => u.appVersion = 90);
|
||||
}
|
||||
|
||||
if (userService.currentUser.appVersion < 91) {
|
||||
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
|
||||
await makeMigrationToVersion91();
|
||||
await UserService.update((u) => u.appVersion = 91);
|
||||
}
|
||||
|
||||
if (userService.currentUser.appVersion < 109) {
|
||||
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||
for (final contact in contacts) {
|
||||
if (contact.verified) {
|
||||
await twonlyDB.keyVerificationDao.addKeyVerification(
|
||||
contact.userId,
|
||||
VerificationType.migratedFromOldVersion,
|
||||
);
|
||||
}
|
||||
}
|
||||
await UserService.update((u) {
|
||||
u
|
||||
..appVersion = 109
|
||||
..skipSetupPages = true;
|
||||
if (u.avatarSvg == null) {
|
||||
u.currentSetupPage = SetupPages.profile.name;
|
||||
} else {
|
||||
u.currentSetupPage = SetupPages.shareYourFriends.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (userService.currentUser.appVersion < 113) {
|
||||
var migrationSuccess = true;
|
||||
final signalIdentity = await SecureStorage.instance.read(
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
key: SecureStorageKeys.signalIdentity,
|
||||
);
|
||||
|
||||
if (signalIdentity != null) {
|
||||
try {
|
||||
final decoded = jsonDecode(signalIdentity);
|
||||
final identity = SignalIdentity.fromJson(
|
||||
decoded as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
await RustKeyManager.importSignalIdentity(
|
||||
identityKeyPairStructure: identity.identityKeyPairU8List,
|
||||
registrationId: identity.registrationId,
|
||||
signedPreKeyStore: await getSignalSignedPreKeyStoreOld(),
|
||||
);
|
||||
Log.info('Importing signal identiy to the rust key manager');
|
||||
|
||||
// Clean up old keys after successful migration
|
||||
await SecureStorage.instance.delete(
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
key: SecureStorageKeys.signalIdentity,
|
||||
);
|
||||
await SecureStorage.instance.delete(
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
key: SecureStorageKeys.signalSignedPreKey,
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error('Failed to migrate signal identity: $e');
|
||||
migrationSuccess = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (migrationSuccess) {
|
||||
await UserService.update((u) {
|
||||
u
|
||||
..appVersion = 113
|
||||
..canUseLoginTokenForAuth = false
|
||||
// As usernames changes where not considered in the old version force users
|
||||
// to reenter there passwords.
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
..twonlySafeBackup?.encryptionKey = []
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
..twonlySafeBackup?.backupId = [];
|
||||
});
|
||||
}
|
||||
}
|
||||
if (userService.currentUser.appVersion < 114) {
|
||||
final allMedia = await twonlyDB.mediaFilesDao
|
||||
.select(twonlyDB.mediaFiles)
|
||||
.get();
|
||||
for (final media in allMedia) {
|
||||
if (media.createdAtMonth == null) {
|
||||
final monthStr = DateFormat('MMMM yyyy').format(media.createdAt);
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
media.mediaId,
|
||||
MediaFilesCompanion(createdAtMonth: Value(monthStr)),
|
||||
);
|
||||
}
|
||||
}
|
||||
await UserService.update((u) => u.appVersion = 114);
|
||||
}
|
||||
|
||||
if (userService.currentUser.appVersion < 115) {
|
||||
var migrationSuccess = true;
|
||||
try {
|
||||
final rustStore = await RustKeyManager.loadSignedPrekeys();
|
||||
for (final entry in rustStore.entries) {
|
||||
final companion = SignalSignedPreKeyStoresCompanion(
|
||||
signedPreKeyId: Value(entry.key),
|
||||
signedPreKey: Value(entry.value),
|
||||
);
|
||||
await twonlyDB
|
||||
.into(twonlyDB.signalSignedPreKeyStores)
|
||||
.insert(
|
||||
companion,
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
await RustKeyManager.removeSignedPrekey(signedPreKeyId: entry.key);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error('Failed to migrate signed prekeys to Drift: $e');
|
||||
migrationSuccess = false;
|
||||
}
|
||||
if (migrationSuccess) {
|
||||
await UserService.update((u) => u.appVersion = 115);
|
||||
}
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
assert(
|
||||
AppState.latestAppVersionId == 115,
|
||||
'Forgot to update the target version in runMigrations() after incrementing AppState.latestAppVersionId.',
|
||||
);
|
||||
assert(
|
||||
AppState.latestAppVersionId == userService.currentUser.appVersion,
|
||||
"Migration incomplete: currentUser.appVersion (${userService.currentUser.appVersion}) does not match AppState.latestAppVersionId (${AppState.latestAppVersionId}). Ensure the user's appVersion is updated in the migration block.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> postStartupTasks() async {
|
||||
Log.info('Post startup started.');
|
||||
unawaited(MemoriesService.prewarmCache());
|
||||
|
|
|
|||
|
|
@ -249,41 +249,49 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
}
|
||||
|
||||
Future<void> handleMessagesOpened(
|
||||
int contactId,
|
||||
Value<int> contactId,
|
||||
List<String> messageIds,
|
||||
DateTime timestamp,
|
||||
) async {
|
||||
await batch((batch) async {
|
||||
try {
|
||||
await twonlyDB.batch((batch) async {
|
||||
for (final messageId in messageIds) {
|
||||
batch.insert(
|
||||
messageActions,
|
||||
MessageActionsCompanion(
|
||||
messageId: Value(messageId),
|
||||
contactId: Value(contactId),
|
||||
contactId: contactId,
|
||||
type: const Value(MessageActionType.openedAt),
|
||||
actionAt: Value(timestamp),
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
}
|
||||
|
||||
for (final messageId in messageIds) {
|
||||
try {
|
||||
final isOpenedByAll = await haveAllMembers(
|
||||
messageId,
|
||||
MessageActionType.openedAt,
|
||||
);
|
||||
final now = clock.now();
|
||||
|
||||
batch.update(
|
||||
twonlyDB.messages,
|
||||
await (update(
|
||||
messages,
|
||||
)..where((tbl) => tbl.messageId.equals(messageId))).write(
|
||||
MessagesCompanion(
|
||||
openedAt: Value(now),
|
||||
openedByAll: Value(isOpenedByAll ? now : null),
|
||||
),
|
||||
where: (tbl) => tbl.messageId.equals(messageId),
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> handleMessageAckByServer(
|
||||
|
|
@ -309,6 +317,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
String messageId,
|
||||
MessageActionType action,
|
||||
) async {
|
||||
try {
|
||||
final message = await twonlyDB.messagesDao
|
||||
.getMessageById(messageId)
|
||||
.getSingleOrNull();
|
||||
|
|
@ -319,11 +328,16 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
|
||||
final actions =
|
||||
await (select(messageActions)..where(
|
||||
(t) => t.type.equals(action.name) & t.messageId.equals(messageId),
|
||||
(t) =>
|
||||
t.type.equals(action.name) & t.messageId.equals(messageId),
|
||||
))
|
||||
.get();
|
||||
|
||||
return members.length == actions.length;
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateMessageId(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||
import 'package:twonly/src/services/api/utils.api.dart';
|
||||
|
|
@ -14,7 +15,7 @@ Future<void> handleMessageUpdate(
|
|||
);
|
||||
try {
|
||||
await twonlyDB.messagesDao.handleMessagesOpened(
|
||||
contactId,
|
||||
Value(contactId),
|
||||
messageUpdate.multipleTargetMessageIds,
|
||||
fromTimestamp(messageUpdate.timestamp),
|
||||
);
|
||||
|
|
|
|||
184
lib/src/services/migrations.service.dart
Normal file
184
lib/src/services/migrations.service.dart
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/secure_storage.keys.dart';
|
||||
import 'package:twonly/src/database/signal/signal_signed_pre_key_store.dart'
|
||||
show getSignalSignedPreKeyStoreOld;
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/json/signal_identity.model.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/secure_storage.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
|
||||
|
||||
Future<void> runMigrations() async {
|
||||
if (userService.currentUser.appVersion < 90) {
|
||||
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
|
||||
await twonlyDB.mediaFilesDao.updateAllRetransmissionUploadingState();
|
||||
await UserService.update((u) => u.appVersion = 90);
|
||||
}
|
||||
|
||||
if (userService.currentUser.appVersion < 91) {
|
||||
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
|
||||
await makeMigrationToVersion91();
|
||||
await UserService.update((u) => u.appVersion = 91);
|
||||
}
|
||||
|
||||
if (userService.currentUser.appVersion < 109) {
|
||||
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||
for (final contact in contacts) {
|
||||
if (contact.verified) {
|
||||
await twonlyDB.keyVerificationDao.addKeyVerification(
|
||||
contact.userId,
|
||||
VerificationType.migratedFromOldVersion,
|
||||
);
|
||||
}
|
||||
}
|
||||
await UserService.update((u) {
|
||||
u
|
||||
..appVersion = 109
|
||||
..skipSetupPages = true;
|
||||
if (u.avatarSvg == null) {
|
||||
u.currentSetupPage = SetupPages.profile.name;
|
||||
} else {
|
||||
u.currentSetupPage = SetupPages.shareYourFriends.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (userService.currentUser.appVersion < 113) {
|
||||
var migrationSuccess = true;
|
||||
final signalIdentity = await SecureStorage.instance.read(
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
key: SecureStorageKeys.signalIdentity,
|
||||
);
|
||||
|
||||
if (signalIdentity != null) {
|
||||
try {
|
||||
final decoded = jsonDecode(signalIdentity);
|
||||
final identity = SignalIdentity.fromJson(
|
||||
decoded as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
await RustKeyManager.importSignalIdentity(
|
||||
identityKeyPairStructure: identity.identityKeyPairU8List,
|
||||
registrationId: identity.registrationId,
|
||||
signedPreKeyStore: await getSignalSignedPreKeyStoreOld(),
|
||||
);
|
||||
Log.info('Importing signal identify to the rust key manager');
|
||||
|
||||
// Clean up old keys after successful migration
|
||||
await SecureStorage.instance.delete(
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
key: SecureStorageKeys.signalIdentity,
|
||||
);
|
||||
await SecureStorage.instance.delete(
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
key: SecureStorageKeys.signalSignedPreKey,
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error('Failed to migrate signal identity: $e');
|
||||
migrationSuccess = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (migrationSuccess) {
|
||||
await UserService.update((u) {
|
||||
u
|
||||
..appVersion = 113
|
||||
..canUseLoginTokenForAuth = false
|
||||
// As usernames changes where not considered in the old version force users
|
||||
// to reenter there passwords.
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
..twonlySafeBackup?.encryptionKey = []
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
..twonlySafeBackup?.backupId = [];
|
||||
});
|
||||
}
|
||||
}
|
||||
if (userService.currentUser.appVersion < 114) {
|
||||
final allMedia = await twonlyDB.mediaFilesDao
|
||||
.select(twonlyDB.mediaFiles)
|
||||
.get();
|
||||
for (final media in allMedia) {
|
||||
if (media.createdAtMonth == null) {
|
||||
final monthStr = DateFormat('MMMM yyyy').format(media.createdAt);
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
media.mediaId,
|
||||
MediaFilesCompanion(createdAtMonth: Value(monthStr)),
|
||||
);
|
||||
}
|
||||
}
|
||||
await UserService.update((u) => u.appVersion = 114);
|
||||
}
|
||||
|
||||
if (userService.currentUser.appVersion < 115) {
|
||||
var migrationSuccess = true;
|
||||
try {
|
||||
final rustStore = await RustKeyManager.loadSignedPrekeys();
|
||||
for (final entry in rustStore.entries) {
|
||||
final companion = SignalSignedPreKeyStoresCompanion(
|
||||
signedPreKeyId: Value(entry.key),
|
||||
signedPreKey: Value(entry.value),
|
||||
);
|
||||
await twonlyDB
|
||||
.into(twonlyDB.signalSignedPreKeyStores)
|
||||
.insert(
|
||||
companion,
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
await RustKeyManager.removeSignedPrekey(signedPreKeyId: entry.key);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error('Failed to migrate signed prekeys to Drift: $e');
|
||||
migrationSuccess = false;
|
||||
}
|
||||
if (migrationSuccess) {
|
||||
await UserService.update((u) => u.appVersion = 115);
|
||||
}
|
||||
}
|
||||
|
||||
if (userService.currentUser.appVersion < 116) {
|
||||
// Because of a Bug in the handleMessagesOpened function, some messages where not marked as opened. So use the logs,
|
||||
// to mark the files as opened.
|
||||
final logs = await loadLogFile();
|
||||
final openedMessages = logs.split(
|
||||
'messages.c2c.dart:12 > Opened message [',
|
||||
);
|
||||
for (final opened in openedMessages) {
|
||||
final messageIds = opened.split(']');
|
||||
if (messageIds.isNotEmpty) {
|
||||
final now = clock.now();
|
||||
for (final messageId in messageIds.first.split(',')) {
|
||||
await (twonlyDB.update(
|
||||
twonlyDB.messages,
|
||||
)..where((tbl) => tbl.messageId.equals(messageId))).write(
|
||||
MessagesCompanion(
|
||||
openedAt: Value(now),
|
||||
openedByAll: Value(now),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
await UserService.update((u) => u.appVersion = 116);
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
assert(
|
||||
AppState.latestAppVersionId == 116,
|
||||
'Forgot to update the target version in runMigrations() after incrementing AppState.latestAppVersionId.',
|
||||
);
|
||||
assert(
|
||||
AppState.latestAppVersionId == userService.currentUser.appVersion,
|
||||
"Migration incomplete: currentUser.appVersion (${userService.currentUser.appVersion}) does not match AppState.latestAppVersionId (${AppState.latestAppVersionId}). Ensure the user's appVersion is updated in the migration block.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -205,6 +205,8 @@ class _MessageInfoViewState extends State<MessageInfoView> {
|
|||
Text(
|
||||
'${context.lang.received}: ${friendlyDateTime(context, widget.message.ackByServer!)}',
|
||||
),
|
||||
if (userService.currentUser.isDeveloper)
|
||||
Text('ID: ${widget.message.messageId}'),
|
||||
if (messageHistory.isNotEmpty) ...[
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue