diff --git a/.gitignore b/.gitignore
index 9d085fd2..5451128a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,9 @@
.history
.svn/
.swiftpm/
+*.sqlite
+*.sqlite-shm
+*.sqlite-wal
migrate_working_dir/
# IntelliJ related
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ffda2166..3ed082a8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## 0.2.13
+
+- 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
- New: Automatically mark identical media as opened across all chats (Settings > Chats).
diff --git a/README.md b/README.md
index fcaeebe7..0b1e68ee 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# twonly
-
+
This repository contains the complete source code of the [twonly](https://twonly.eu) app. twonly is a replacement for Snapchat, but its purpose is not to replace instant messaging apps, as there are already [many fantastic alternatives](https://www.messenger-matrix.de/messenger-matrix-en.html) out there. It was started because I liked the basic features of Snapchat, such as opening with the camera, the easy-to-use image editor, and the focus on sending fun pictures to friends. But I was annoyed by Snapchat's forced AI chat, receiving random messages to follow strangers, and not knowing how my sent images/text messages were encrypted, if at all. I am also very critical of the direction in which the US is currently moving and therefore try to avoid US providers wherever possible.
diff --git a/assets/fonts/NotoColorEmoji.ttf b/assets/fonts/NotoColorEmoji.ttf
new file mode 100644
index 00000000..943741df
Binary files /dev/null and b/assets/fonts/NotoColorEmoji.ttf differ
diff --git a/docs/header.png b/docs/header.png
deleted file mode 100644
index 6588b0b8..00000000
Binary files a/docs/header.png and /dev/null differ
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
index 6c738da9..88277029 100644
--- a/ios/Runner/AppDelegate.swift
+++ b/ios/Runner/AppDelegate.swift
@@ -13,7 +13,7 @@ import workmanager_apple
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
UNUserNotificationCenter.current().delegate = self
-
+
if let registrar = self.registrar(forPlugin: "VideoCompressionChannel") {
VideoCompressionChannel.register(with: registrar.messenger())
}
@@ -32,20 +32,22 @@ import workmanager_apple
WorkmanagerPlugin.registerBGProcessingTask(
withIdentifier: "eu.twonly.processing_task"
)
-
+
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
- override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
+ override func application(
+ _ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]
+ ) -> Bool {
- let sharingIntent = SwiftFlutterSharingIntentPlugin.instance
- if sharingIntent.hasSameSchemePrefix(url: url) {
- return sharingIntent.application(app, open: url, options: options)
- }
+ let sharingIntent = SwiftFlutterSharingIntentPlugin.instance
+ if sharingIntent.hasSameSchemePrefix(url: url) {
+ return sharingIntent.application(app, open: url, options: options)
+ }
- // Proceed url handling for other Flutter libraries like app_links
- return super.application(app, open: url, options:options)
- }
+ // Proceed url handling for other Flutter libraries like app_links
+ return super.application(app, open: url, options: options)
+ }
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
@@ -58,7 +60,8 @@ import workmanager_apple
NSLog(
"Application delegate method userNotificationCenter:didReceive:withCompletionHandler: is called with user info: %@",
response.notification.request.content.userInfo)
- //...
+ super.userNotificationCenter(
+ center, didReceive: response, withCompletionHandler: completionHandler)
}
override func userNotificationCenter(
@@ -86,4 +89,4 @@ import workmanager_apple
completionHandler([.alert, .sound])
}
-}
+}
\ No newline at end of file
diff --git a/lib/globals.dart b/lib/globals.dart
index f3bd47b7..90bc7deb 100644
--- a/lib/globals.dart
+++ b/lib/globals.dart
@@ -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;
}
diff --git a/lib/main.dart b/lib/main.dart
index ed54402e..a4ed4ed5 100644
--- a/lib/main.dart
+++ b/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,34 +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();
@@ -168,144 +154,6 @@ void main() async {
);
}
-Future 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,
- );
-
- 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 postStartupTasks() async {
Log.info('Post startup started.');
unawaited(MemoriesService.prewarmCache());
diff --git a/lib/src/constants/routes.keys.dart b/lib/src/constants/routes.keys.dart
index d6690619..2835d4e1 100644
--- a/lib/src/constants/routes.keys.dart
+++ b/lib/src/constants/routes.keys.dart
@@ -37,6 +37,7 @@ class Routes {
'/settings/privacy/user_discovery';
static const String settingsNotification = '/settings/notification';
static const String settingsStorage = '/settings/storage_data';
+ static const String settingsStorageManage = '/settings/storage_data/manage';
static const String settingsStorageImport = '/settings/storage_data/import';
static const String settingsStorageExport = '/settings/storage_data/export';
static const String settingsHelp = '/settings/help';
diff --git a/lib/src/database/daos/mediafiles.dao.dart b/lib/src/database/daos/mediafiles.dao.dart
index 24c91479..d22d3a80 100644
--- a/lib/src/database/daos/mediafiles.dao.dart
+++ b/lib/src/database/daos/mediafiles.dao.dart
@@ -114,16 +114,15 @@ class MediaFilesDao extends DatabaseAccessor
.get();
}
- Future> getAllNonHashedStoredMediaFiles() async {
+ Future> getAllMediaFilesPendingMigration() async {
return (select(mediaFiles)..where(
- (t) => t.stored.equals(true) & t.storedFileHash.isNull(),
- ))
- .get();
- }
-
- Future> getAllUnanalyzedStoredMediaFiles() async {
- return (select(mediaFiles)..where(
- (t) => t.stored.equals(true) & t.hasCropAnalyzed.equals(false),
+ (t) =>
+ t.stored.equals(true) &
+ (t.storedFileHash.isNull() |
+ t.hasCropAnalyzed.equals(false) |
+ (t.hasThumbnail.equals(false) &
+ t.type.equals(MediaType.audio.name).not()) |
+ t.sizeInBytes.isNull()),
))
.get();
}
@@ -185,4 +184,17 @@ class MediaFilesDao extends DatabaseAccessor
final rows = await query.get();
return rows.map((row) => row.readTable(db.messages).messageId).toList();
}
+
+ Future