move signal to drift

This commit is contained in:
otsmr 2025-03-16 20:51:38 +01:00
parent 8fbb5b001c
commit 0b084f795a
57 changed files with 2100 additions and 736 deletions

View file

@ -1,9 +1,7 @@
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/providers/api_provider.dart'; import 'package:twonly/src/providers/api_provider.dart';
import 'package:twonly/src/providers/db_provider.dart';
late ApiProvider apiProvider; late ApiProvider apiProvider;
// uses for background notification // uses for background notification
late DbProvider dbProvider;
late TwonlyDatabase twonlyDatabase; late TwonlyDatabase twonlyDatabase;

View file

@ -1,12 +1,10 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/providers/api_provider.dart'; import 'package:twonly/src/providers/api_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/src/providers/db_provider.dart';
import 'package:twonly/src/providers/hive.dart'; import 'package:twonly/src/providers/hive.dart';
import 'package:twonly/src/providers/send_next_media_to.dart'; import 'package:twonly/src/providers/send_next_media_to.dart';
import 'package:twonly/src/providers/settings_change_provider.dart'; import 'package:twonly/src/providers/settings_change_provider.dart';
@ -39,14 +37,9 @@ void main() async {
await initMediaStorage(); await initMediaStorage();
await initFCMService(); await initFCMService();
dbProvider = DbProvider();
await dbProvider.ready;
apiProvider = ApiProvider(); apiProvider = ApiProvider();
twonlyDatabase = TwonlyDatabase(); twonlyDatabase = TwonlyDatabase();
FlutterForegroundTask.initCommunicationPort();
runApp( runApp(
MultiProvider( MultiProvider(
providers: [ providers: [

View file

@ -1,4 +1,3 @@
import 'package:cv/cv.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart'; import 'package:lottie/lottie.dart';
@ -30,8 +29,7 @@ class EmojiAnimation extends StatelessWidget {
// Check if the emoji has a corresponding Lottie animation // Check if the emoji has a corresponding Lottie animation
if (animatedIcons.containsKey(emoji)) { if (animatedIcons.containsKey(emoji)) {
return Lottie.asset( return Lottie.asset("assets/animated_icons/${animatedIcons[emoji]}");
"assets/animated_icons/${animatedIcons.getValue(emoji)}");
} else { } else {
return Text( return Text(
emoji, emoji,

View file

@ -2,8 +2,8 @@ import 'dart:collection';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/components/verified_shield.dart'; import 'package:twonly/src/components/verified_shield.dart';
import 'package:twonly/src/database/contacts_db.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/components/flame.dart'; import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/headline.dart'; import 'package:twonly/src/components/headline.dart';
@ -142,7 +142,8 @@ class UserCheckbox extends StatelessWidget {
], ],
), ),
StreamBuilder( StreamBuilder(
stream: twonlyDatabase.watchFlameCounter(user.userId), stream: twonlyDatabase.contactsDao
.watchFlameCounter(user.userId),
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data! == 0) { if (!snapshot.hasData || snapshot.data! == 0) {
return Container(); return Container();

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/components/animate_icon.dart'; import 'package:twonly/src/components/animate_icon.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
class FlameCounterWidget extends StatelessWidget { class FlameCounterWidget extends StatelessWidget {
final Contact user; final Contact user;

View file

@ -2,9 +2,9 @@ import 'dart:collection';
import 'dart:convert'; 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:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/messages_db.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
enum MessageSendState { enum MessageSendState {

View file

@ -1,7 +1,4 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
class PermissionHandlerView extends StatefulWidget { class PermissionHandlerView extends StatefulWidget {
@ -48,28 +45,6 @@ class PermissionHandlerViewState extends State<PermissionHandlerView> {
// } // }
} }
if (Platform.isAndroid) {
// Android 12+, there are restrictions on starting a foreground service.
//
// To restart the service on device reboot or unexpected problem, you need to allow below permission.
if (!await FlutterForegroundTask.isIgnoringBatteryOptimizations) {
// This function requires `android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` permission.
await FlutterForegroundTask.requestIgnoreBatteryOptimization();
}
// Use this utility only if you provide services that require long-term survival,
// such as exact alarm service, healthcare service, or Bluetooth communication.
//
// This utility requires the "android.permission.SCHEDULE_EXACT_ALARM" permission.
// Using this permission may make app distribution difficult due to Google policy.
// if (!await FlutterForegroundTask.canScheduleExactAlarms) {
// When you call this function, will be gone to the settings page.
// So you need to explain to the user why set it.
// await FlutterForegroundTask.openAlarmsAndRemindersSettings();
// }
}
/*{Permission.camera: PermissionStatus.granted, Permission.storage: PermissionStatus.granted}*/
return statuses; return statuses;
} }

View file

@ -2,7 +2,7 @@ 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:pie_menu/pie_menu.dart'; import 'package:pie_menu/pie_menu.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/providers/send_next_media_to.dart'; import 'package:twonly/src/providers/send_next_media_to.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/chats/chat_item_details_view.dart'; import 'package:twonly/src/views/chats/chat_item_details_view.dart';

View file

@ -1,6 +1,6 @@
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/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
class VerifiedShield extends StatelessWidget { class VerifiedShield extends StatelessWidget {
final Contact contact; final Contact contact;

View file

@ -1,145 +1,15 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:logging/logging.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:path_provider/path_provider.dart';
import 'package:twonly/src/database/contacts_db.dart';
import 'package:twonly/src/database/messages_db.dart';
import 'package:twonly/src/model/json/message.dart';
part 'database.g.dart'; part 'contacts_dao.g.dart';
// You can then create a database class that includes this table @DriftAccessor(tables: [Contacts])
@DriftDatabase(tables: [Contacts, Messages]) class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
class TwonlyDatabase extends _$TwonlyDatabase { with _$ContactsDaoMixin {
TwonlyDatabase() : super(_openConnection()); // this constructor is required so that the main database can create an instance
// of this object.
@override ContactsDao(TwonlyDatabase db) : super(db);
int get schemaVersion => 1;
static QueryExecutor _openConnection() {
return driftDatabase(
name: 'twonly_main_db',
native: const DriftNativeOptions(
databaseDirectory: getApplicationSupportDirectory,
),
);
}
// ------------
Stream<List<Message>> watchMessageNotOpened(int contactId) {
return (select(messages)
..where((t) => t.openedAt.isNull() & t.contactId.equals(contactId))
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
.watch();
}
Stream<List<Message>> watchMediaMessageNotOpened(int contactId) {
return (select(messages)
..where((t) =>
t.openedAt.isNull() &
t.contactId.equals(contactId) &
t.kind.equals(MessageKind.media.name))
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
.watch();
}
Stream<List<Message>> watchLastMessage(int contactId) {
return (select(messages)
..where((t) => t.contactId.equals(contactId))
..orderBy([(t) => OrderingTerm.desc(t.sendAt)])
..limit(1))
.watch();
}
Stream<List<Message>> watchAllMessagesFrom(int contactId) {
return (select(messages)
..where((t) =>
t.contactId.equals(contactId) &
t.contentJson.isNotNull() &
(t.openedAt.isNull() |
t.openedAt.isBiggerThanValue(
DateTime.now().subtract(Duration(days: 1)))))
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
.watch();
}
Future removeOldMessages() {
return (update(messages)
..where((t) =>
t.openedAt.isSmallerThanValue(
DateTime.now().subtract(Duration(days: 1))) &
t.kind.equals(MessageKind.textMessage.name)))
.write(MessagesCompanion(contentJson: Value(null)));
}
Future<List<Message>> getAllMessagesPendingDownloading() {
return (select(messages)
..where(
(t) =>
t.downloadState.equals(DownloadState.downloaded.index).not() &
t.messageOtherId.isNotNull() &
t.kind.equals(MessageKind.media.name),
))
.get();
}
Future<List<Message>> getAllMessagesForRetransmitting() {
return (select(messages)..where((t) => t.acknowledgeByServer.equals(false)))
.get();
}
Future openedAllTextMessages(int contactId) {
final updates = MessagesCompanion(openedAt: Value(DateTime.now()));
return (update(messages)
..where((t) =>
t.contactId.equals(contactId) &
t.openedAt.isNull() &
t.kind.equals(MessageKind.textMessage.name)))
.write(updates);
}
Future updateMessageByOtherUser(
int userId, int messageId, MessagesCompanion updatedValues) {
return (update(messages)
..where((c) =>
c.contactId.equals(userId) & c.messageId.equals(messageId)))
.write(updatedValues);
}
Future updateMessageByOtherMessageId(
int userId, int messageOtherId, MessagesCompanion updatedValues) {
return (update(messages)
..where((c) =>
c.contactId.equals(userId) &
c.messageOtherId.equals(messageOtherId)))
.write(updatedValues);
}
Future updateMessageByMessageId(
int messageId, MessagesCompanion updatedValues) {
return (update(messages)..where((c) => c.messageId.equals(messageId)))
.write(updatedValues);
}
Future<int?> insertMessage(MessagesCompanion message) async {
try {
return await into(messages).insert(message);
} catch (e) {
Logger("twonlyDatabase").shout("Error while inserting message: $e");
return null;
}
}
Future deleteMessageById(int messageId) {
return (delete(messages)..where((t) => t.messageId.equals(messageId))).go();
}
SingleOrNullSelectable<Message> getMessageByMessageId(int messageId) {
return select(messages)..where((t) => t.messageId.equals(messageId));
}
// ------------
Future<int> insertContact(ContactsCompanion contact) async { Future<int> insertContact(ContactsCompanion contact) async {
try { try {

View file

@ -0,0 +1,8 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'contacts_dao.dart';
// ignore_for_file: type=lint
mixin _$ContactsDaoMixin on DatabaseAccessor<TwonlyDatabase> {
$ContactsTable get contacts => attachedDatabase.contacts;
}

View file

@ -0,0 +1,127 @@
import 'package:drift/drift.dart';
import 'package:logging/logging.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/json_models/message.dart';
part 'messages_dao.g.dart';
@DriftAccessor(tables: [Messages])
class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
with _$MessagesDaoMixin {
// this constructor is required so that the main database can create an instance
// of this object.
MessagesDao(TwonlyDatabase db) : super(db);
Stream<List<Message>> watchMessageNotOpened(int contactId) {
return (select(messages)
..where((t) => t.openedAt.isNull() & t.contactId.equals(contactId))
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
.watch();
}
Stream<List<Message>> watchMediaMessageNotOpened(int contactId) {
return (select(messages)
..where((t) =>
t.openedAt.isNull() &
t.contactId.equals(contactId) &
t.kind.equals(MessageKind.media.name))
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
.watch();
}
Stream<List<Message>> watchLastMessage(int contactId) {
return (select(messages)
..where((t) => t.contactId.equals(contactId))
..orderBy([(t) => OrderingTerm.desc(t.sendAt)])
..limit(1))
.watch();
}
Stream<List<Message>> watchAllMessagesFrom(int contactId) {
return (select(messages)
..where((t) =>
t.contactId.equals(contactId) &
t.contentJson.isNotNull() &
(t.openedAt.isNull() |
t.openedAt.isBiggerThanValue(
DateTime.now().subtract(Duration(days: 1)))))
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
.watch();
}
Future removeOldMessages() {
return (update(messages)
..where((t) =>
t.openedAt.isSmallerThanValue(
DateTime.now().subtract(Duration(days: 1))) &
t.kind.equals(MessageKind.textMessage.name)))
.write(MessagesCompanion(contentJson: Value(null)));
}
Future<List<Message>> getAllMessagesPendingDownloading() {
return (select(messages)
..where(
(t) =>
t.downloadState.equals(DownloadState.downloaded.index).not() &
t.messageOtherId.isNotNull() &
t.kind.equals(MessageKind.media.name),
))
.get();
}
Future<List<Message>> getAllMessagesForRetransmitting() {
return (select(messages)..where((t) => t.acknowledgeByServer.equals(false)))
.get();
}
Future openedAllTextMessages(int contactId) {
final updates = MessagesCompanion(openedAt: Value(DateTime.now()));
return (update(messages)
..where((t) =>
t.contactId.equals(contactId) &
t.openedAt.isNull() &
t.kind.equals(MessageKind.textMessage.name)))
.write(updates);
}
Future updateMessageByOtherUser(
int userId, int messageId, MessagesCompanion updatedValues) {
return (update(messages)
..where((c) =>
c.contactId.equals(userId) & c.messageId.equals(messageId)))
.write(updatedValues);
}
Future updateMessageByOtherMessageId(
int userId, int messageOtherId, MessagesCompanion updatedValues) {
return (update(messages)
..where((c) =>
c.contactId.equals(userId) &
c.messageOtherId.equals(messageOtherId)))
.write(updatedValues);
}
Future updateMessageByMessageId(
int messageId, MessagesCompanion updatedValues) {
return (update(messages)..where((c) => c.messageId.equals(messageId)))
.write(updatedValues);
}
Future<int?> insertMessage(MessagesCompanion message) async {
try {
return await into(messages).insert(message);
} catch (e) {
Logger("twonlyDatabase").shout("Error while inserting message: $e");
return null;
}
}
Future deleteMessageById(int messageId) {
return (delete(messages)..where((t) => t.messageId.equals(messageId))).go();
}
SingleOrNullSelectable<Message> getMessageByMessageId(int messageId) {
return select(messages)..where((t) => t.messageId.equals(messageId));
}
}

View file

@ -0,0 +1,9 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'messages_dao.dart';
// ignore_for_file: type=lint
mixin _$MessagesDaoMixin on DatabaseAccessor<TwonlyDatabase> {
$ContactsTable get contacts => attachedDatabase.contacts;
$MessagesTable get messages => attachedDatabase.messages;
}

View file

@ -1,5 +1,5 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
class Contacts extends Table { class Contacts extends Table {
IntColumn get userId => integer()(); IntColumn get userId => integer()();

View file

@ -1,6 +1,6 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:twonly/src/database/contacts_db.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/json_models/message.dart';
enum DownloadState { enum DownloadState {
pending, pending,
@ -8,6 +8,7 @@ enum DownloadState {
downloaded, downloaded,
} }
@DataClassName('Message')
class Messages extends Table { class Messages extends Table {
IntColumn get contactId => integer().references(Contacts, #userId)(); IntColumn get contactId => integer().references(Contacts, #userId)();

View file

@ -0,0 +1,12 @@
import 'package:drift/drift.dart';
@DataClassName('SignalIdentityKeyStore')
class SignalIdentityKeyStores extends Table {
IntColumn get deviceId => integer()();
TextColumn get name => text()();
BlobColumn get identityKey => blob()();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
@override
Set<Column> get primaryKey => {deviceId, name};
}

View file

@ -0,0 +1,11 @@
import 'package:drift/drift.dart';
@DataClassName('SignalPreKeyStore')
class SignalPreKeyStores extends Table {
IntColumn get preKeyId => integer()();
BlobColumn get preKey => blob()();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
@override
Set<Column> get primaryKey => {preKeyId};
}

View file

@ -0,0 +1,10 @@
import 'package:drift/drift.dart';
@DataClassName('SignalSenderKeyStore')
class SignalSenderKeyStores extends Table {
TextColumn get senderKeyName => text()();
BlobColumn get senderKey => blob()();
@override
Set<Column> get primaryKey => {senderKeyName};
}

View file

@ -0,0 +1,12 @@
import 'package:drift/drift.dart';
@DataClassName('SignalSessionStore')
class SignalSessionStores extends Table {
IntColumn get deviceId => integer()();
TextColumn get name => text()();
BlobColumn get sessionRecord => blob()();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
@override
Set<Column> get primaryKey => {deviceId, name};
}

View file

@ -0,0 +1,42 @@
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/daos/messages_dao.dart';
import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/database/tables/signal_identity_key_store_table.dart';
import 'package:twonly/src/database/tables/signal_pre_key_store_table.dart';
import 'package:twonly/src/database/tables/signal_sender_key_store_table.dart';
import 'package:twonly/src/database/tables/signal_session_store_table.dart';
import 'package:twonly/src/json_models/message.dart';
part 'twonly_database.g.dart';
// You can then create a database class that includes this table
@DriftDatabase(tables: [
Contacts,
Messages,
SignalIdentityKeyStores,
SignalPreKeyStores,
SignalSenderKeyStores,
SignalSessionStores
], daos: [
MessagesDao,
ContactsDao
])
class TwonlyDatabase extends _$TwonlyDatabase {
TwonlyDatabase() : super(_openConnection());
@override
int get schemaVersion => 1;
static QueryExecutor _openConnection() {
return driftDatabase(
name: 'twonly_database',
native: const DriftNativeOptions(
databaseDirectory: getApplicationSupportDirectory,
),
);
}
}

View file

@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_data.dart'; part of 'userdata.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator

View file

@ -1,36 +0,0 @@
import 'dart:typed_data';
import 'package:cv/cv.dart';
import 'package:sqflite_sqlcipher/sqflite.dart';
class DbSignalIdentityKeyStore extends CvModelBase {
static const tableName = "signal_identity_key_store";
static const columnDeviceId = "device_id";
final deviceId = CvField<int>(columnDeviceId);
static const columnName = "name";
final name = CvField<String>(columnName);
static const columnIdentityKey = "identity_key";
final identityKey = CvField<Uint8List>(columnIdentityKey);
static const columnCreatedAt = "created_at";
final createdAt = CvField<DateTime>(columnCreatedAt);
static Future setupDatabaseTable(Database db) async {
String createTableString = """
CREATE TABLE IF NOT EXISTS $tableName (
$columnDeviceId INTEGER NOT NULL,
$columnName TEXT NOT NULL,
$columnIdentityKey BLOB NOT NULL,
$columnCreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ($columnDeviceId, $columnName)
)
""";
await db.execute(createTableString);
}
@override
List<CvField> get fields => [deviceId, name, identityKey, createdAt];
}

View file

@ -1,31 +0,0 @@
import 'dart:typed_data';
import 'package:cv/cv.dart';
import 'package:sqflite_sqlcipher/sqflite.dart';
class DbSignalPreKeyStore extends CvModelBase {
static const tableName = "signal_pre_key_store";
static const columnPreKeyId = "pre_key_id";
final preKeyId = CvField<int>(columnPreKeyId);
static const columnPreKey = "pre_key";
final preKey = CvField<Uint8List>(columnPreKey);
static const columnCreatedAt = "created_at";
final createdAt = CvField<DateTime>(columnCreatedAt);
static Future setupDatabaseTable(Database db) async {
String createTableString = """
CREATE TABLE IF NOT EXISTS $tableName (
$columnPreKeyId INTEGER NOT NULL,
$columnPreKey BLOB NOT NULL,
$columnCreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ($columnPreKeyId)
)
""";
await db.execute(createTableString);
}
@override
List<CvField> get fields => [preKeyId, preKey, createdAt];
}

View file

@ -1,31 +0,0 @@
import 'dart:typed_data';
import 'package:cv/cv.dart';
import 'package:sqflite_sqlcipher/sqflite.dart';
class DbSignalSenderKeyStore extends CvModelBase {
static const tableName = "signal_sender_key_store";
static const columnSenderKeyName = "sender_key_name";
final senderKeyName = CvField<String>(columnSenderKeyName);
static const columnSenderKey = "sender_key";
final senderKey = CvField<Uint8List>(columnSenderKey);
static const columnCreatedAt = "created_at";
final createdAt = CvField<DateTime>(columnCreatedAt);
static Future setupDatabaseTable(Database db) async {
String createTableString = """
CREATE TABLE IF NOT EXISTS $tableName (
$columnSenderKeyName TEXT NOT NULL,
$columnSenderKey BLOB NOT NULL,
$columnCreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ($columnSenderKeyName)
)
""";
await db.execute(createTableString);
}
@override
List<CvField> get fields => [senderKeyName, senderKey, createdAt];
}

View file

@ -1,35 +0,0 @@
import 'dart:typed_data';
import 'package:cv/cv.dart';
import 'package:sqflite_sqlcipher/sqflite.dart';
class DbSignalSessionStore extends CvModelBase {
static const tableName = "signal_session_store";
static const columnDeviceId = "device_id";
final deviceId = CvField<int>(columnDeviceId);
static const columnName = "name";
final name = CvField<String>(columnName);
static const columnSessionRecord = "session_record";
final sessionRecord = CvField<Uint8List>(columnSessionRecord);
static const columnCreatedAt = "created_at";
final createdAt = CvField<DateTime>(columnCreatedAt);
static Future setupDatabaseTable(Database db) async {
String createTableString = """
CREATE TABLE IF NOT EXISTS $tableName (
$columnDeviceId INTEGER NOT NULL,
$columnName TEXT NOT NULL,
$columnSessionRecord BLOB NOT NULL,
$columnCreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ($columnDeviceId, $columnName)
)
""";
await db.execute(createTableString);
}
@override
List<CvField> get fields => [deviceId, name, sessionRecord, createdAt];
}

View file

@ -3,9 +3,9 @@ import 'package:drift/drift.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/messages_db.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/proto/api/error.pb.dart'; import 'package:twonly/src/proto/api/error.pb.dart';
import 'package:twonly/src/providers/api/api_utils.dart'; import 'package:twonly/src/providers/api/api_utils.dart';
import 'package:twonly/src/providers/hive.dart'; import 'package:twonly/src/providers/hive.dart';
@ -70,7 +70,7 @@ Future<Result> encryptAndSendMessage(
if (resp.isSuccess) { if (resp.isSuccess) {
if (messageId != null) { if (messageId != null) {
await twonlyDatabase.updateMessageByMessageId( await twonlyDatabase.messagesDao.updateMessageByMessageId(
messageId, messageId,
MessagesCompanion(acknowledgeByServer: Value(true)), MessagesCompanion(acknowledgeByServer: Value(true)),
); );
@ -86,7 +86,7 @@ Future sendTextMessage(int target, String message) async {
DateTime messageSendAt = DateTime.now(); DateTime messageSendAt = DateTime.now();
int? messageId = await twonlyDatabase.insertMessage( int? messageId = await twonlyDatabase.messagesDao.insertMessage(
MessagesCompanion( MessagesCompanion(
contactId: Value(target), contactId: Value(target),
kind: Value(MessageKind.textMessage), kind: Value(MessageKind.textMessage),

View file

@ -5,9 +5,9 @@ import 'package:drift/drift.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/app.dart'; import 'package:twonly/src/app.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/messages_db.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/proto/api/server_to_client.pb.dart'; import 'package:twonly/src/proto/api/server_to_client.pb.dart';
import 'package:twonly/src/providers/api/api.dart'; import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/api/api_utils.dart'; import 'package:twonly/src/providers/api/api_utils.dart';
@ -19,7 +19,7 @@ Future tryDownloadAllMediaFiles() async {
return; return;
} }
List<Message> messages = List<Message> messages =
await twonlyDatabase.getAllMessagesPendingDownloading(); await twonlyDatabase.messagesDao.getAllMessagesPendingDownloading();
for (Message message in messages) { for (Message message in messages) {
MessageContent? content = MessageContent? content =
@ -163,7 +163,7 @@ class ImageUploader {
final downloadToken = uploadState.downloadTokens.removeLast(); final downloadToken = uploadState.downloadTokens.removeLast();
twonlyDatabase.incFlameCounter( twonlyDatabase.contactsDao.incFlameCounter(
targetUserId, targetUserId,
false, false,
metadata.messageSendAt, metadata.messageSendAt,
@ -215,7 +215,7 @@ Future sendImage(
// at this point it is safe inform the user about the process of sending the image.. // at this point it is safe inform the user about the process of sending the image..
for (final userId in metadata.userIds) { for (final userId in metadata.userIds) {
int? messageId = await twonlyDatabase.insertMessage( int? messageId = await twonlyDatabase.messagesDao.insertMessage(
MessagesCompanion( MessagesCompanion(
contactId: Value(userId), contactId: Value(userId),
kind: Value(MessageKind.media), kind: Value(MessageKind.media),
@ -284,7 +284,7 @@ Future tryDownloadMedia(
box.put("${content.downloadToken!}_messageId", messageId); box.put("${content.downloadToken!}_messageId", messageId);
await twonlyDatabase.updateMessageByOtherUser( await twonlyDatabase.messagesDao.updateMessageByOtherUser(
fromUserId, fromUserId,
messageId, messageId,
MessagesCompanion( MessagesCompanion(
@ -307,7 +307,7 @@ Future<Uint8List?> getDownloadedMedia(
// await userOpenedOtherMessage(otherUserId, messageOtherId); // await userOpenedOtherMessage(otherUserId, messageOtherId);
notifyContactAboutOpeningMessage(message.contactId, message.messageOtherId!); notifyContactAboutOpeningMessage(message.contactId, message.messageOtherId!);
twonlyDatabase.updateMessageByMessageId( twonlyDatabase.messagesDao.updateMessageByMessageId(
message.messageId, MessagesCompanion(openedAt: Value(DateTime.now()))); message.messageId, MessagesCompanion(openedAt: Value(DateTime.now())));
box.delete(downloadToken.toString()); box.delete(downloadToken.toString());

View file

@ -7,9 +7,9 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/app.dart'; import 'package:twonly/src/app.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/messages_db.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client; import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client;
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart'; import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
import 'package:twonly/src/proto/api/error.pb.dart'; import 'package:twonly/src/proto/api/error.pb.dart';
@ -76,7 +76,7 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
if (data.fin && data.data.isEmpty) { if (data.fin && data.data.isEmpty) {
// media file was deleted by the server. remove the media from device // media file was deleted by the server. remove the media from device
await twonlyDatabase.deleteMessageById(messageId); await twonlyDatabase.messagesDao.deleteMessageById(messageId);
box.delete(boxId); box.delete(boxId);
box.delete("${data.downloadToken}_downloaded"); box.delete("${data.downloadToken}_downloaded");
var ok = client.Response_Ok()..none = true; var ok = client.Response_Ok()..none = true;
@ -111,8 +111,9 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
// Uint8List? rawBytes = // Uint8List? rawBytes =
// await SignalHelper.decryptBytes(downloadedBytes, fromUserId); // await SignalHelper.decryptBytes(downloadedBytes, fromUserId);
Message? msg = Message? msg = await twonlyDatabase.messagesDao
await twonlyDatabase.getMessageByMessageId(messageId).getSingleOrNull(); .getMessageByMessageId(messageId)
.getSingleOrNull();
if (msg == null) { if (msg == null) {
Logger("server_messages") Logger("server_messages")
.info("messageId not found in database. Ignoring download"); .info("messageId not found in database. Ignoring download");
@ -141,13 +142,13 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
} catch (e) { } catch (e) {
Logger("server_messages").info("Decryption error: $e"); Logger("server_messages").info("Decryption error: $e");
// deleting message as this is an invalid image // deleting message as this is an invalid image
await twonlyDatabase.deleteMessageById(messageId); await twonlyDatabase.messagesDao.deleteMessageById(messageId);
// answers with ok, so the server will delete the message // answers with ok, so the server will delete the message
var ok = client.Response_Ok()..none = true; var ok = client.Response_Ok()..none = true;
return client.Response()..ok = ok; return client.Response()..ok = ok;
} }
await twonlyDatabase.updateMessageByOtherUser( await twonlyDatabase.messagesDao.updateMessageByOtherUser(
msg.contactId, msg.contactId,
messageId, messageId,
MessagesCompanion(downloadState: Value(DownloadState.downloaded)), MessagesCompanion(downloadState: Value(DownloadState.downloaded)),
@ -168,7 +169,8 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
if (username.isSuccess) { if (username.isSuccess) {
Uint8List name = username.value.userdata.username; Uint8List name = username.value.userdata.username;
int added = await twonlyDatabase.insertContact(ContactsCompanion( int added =
await twonlyDatabase.contactsDao.insertContact(ContactsCompanion(
username: Value(utf8.decode(name)), username: Value(utf8.decode(name)),
userId: Value(fromUserId), userId: Value(fromUserId),
requested: Value(true), requested: Value(true),
@ -184,23 +186,23 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
break; break;
case MessageKind.opened: case MessageKind.opened:
final update = MessagesCompanion(openedAt: Value(message.timestamp)); final update = MessagesCompanion(openedAt: Value(message.timestamp));
await twonlyDatabase.updateMessageByOtherUser( await twonlyDatabase.messagesDao.updateMessageByOtherUser(
fromUserId, fromUserId,
message.messageId!, message.messageId!,
update, update,
); );
break; break;
case MessageKind.rejectRequest: case MessageKind.rejectRequest:
await twonlyDatabase.deleteContactByUserId(fromUserId); await twonlyDatabase.contactsDao.deleteContactByUserId(fromUserId);
break; break;
case MessageKind.acceptRequest: case MessageKind.acceptRequest:
final update = ContactsCompanion(accepted: Value(true)); final update = ContactsCompanion(accepted: Value(true));
twonlyDatabase.updateContact(fromUserId, update); twonlyDatabase.contactsDao.updateContact(fromUserId, update);
localPushNotificationNewMessage(fromUserId.toInt(), message, 8888888); localPushNotificationNewMessage(fromUserId.toInt(), message, 8888888);
break; break;
case MessageKind.ack: case MessageKind.ack:
final update = MessagesCompanion(acknowledgeByUser: Value(true)); final update = MessagesCompanion(acknowledgeByUser: Value(true));
await twonlyDatabase.updateMessageByOtherUser( await twonlyDatabase.messagesDao.updateMessageByOtherUser(
fromUserId, fromUserId,
message.messageId!, message.messageId!,
update, update,
@ -226,7 +228,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
sendAt: Value(message.timestamp), sendAt: Value(message.timestamp),
); );
final messageId = await twonlyDatabase.insertMessage( final messageId = await twonlyDatabase.messagesDao.insertMessage(
update, update,
); );
@ -246,7 +248,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
); );
if (message.kind == MessageKind.media) { if (message.kind == MessageKind.media) {
twonlyDatabase.incFlameCounter( twonlyDatabase.contactsDao.incFlameCounter(
fromUserId, fromUserId,
true, true,
message.timestamp, message.timestamp,

View file

@ -1,72 +0,0 @@
import 'dart:async';
import 'dart:math';
import 'package:twonly/src/model/identity_key_store_model.dart';
import 'package:twonly/src/model/pre_key_model.dart';
import 'package:twonly/src/model/sender_key_store_model.dart';
import 'package:twonly/src/model/session_store_model.dart';
import 'package:sqflite_sqlcipher/sqflite.dart';
import 'package:twonly/src/utils/misc.dart';
class DbProvider {
Database? db;
static const String dbName = 'twonly.db';
Future openPath(String path) async {
// Read the password for the database from the secure storage. If there is no, then create a
// new cryptographically secure random password with 32 bytes and store them in the secure storage.
// So if someone dumps the app for example using a tool like Cellebrite including the apps
// content he is not able to access the databases content.
// (https://signal.org/blog/cellebrite-and-clickbait/)
//
// CHECK: Does this actually improve the security or is this just security through obscurity and
// can be removed as it does increase complexity?
// Current thoughts: As the database password is at some point in the memory the attacker could
// just dump it. Questions here: What capabilities must the attacker have to do that?
final storage = getSecureStorage();
var password = await storage.read(key: "sqflite_database_password");
if (password == null) {
var secureRandom = Random.secure();
password = "";
for (var i = 0; i < 32; i++) {
password = "$password${String.fromCharCode(secureRandom.nextInt(256))}";
}
await storage.write(key: "sqflite_database_password", value: password);
}
db = await openDatabase(path, password: password);
if (db != null) {
await setupDatabaseTable(db!);
}
}
Future<Database?> get ready async {
if (db == null) {
await open();
}
return db;
}
Future setupDatabaseTable(Database db) async {
await DbSignalSessionStore.setupDatabaseTable(db);
await DbSignalPreKeyStore.setupDatabaseTable(db);
await DbSignalSenderKeyStore.setupDatabaseTable(db);
await DbSignalIdentityKeyStore.setupDatabaseTable(db);
}
Future open() async {
await openPath(await fixPath(dbName));
}
Future remove() async {
await deleteDatabase(await fixPath(dbName));
// await _createDb(db!);
}
Future<String> fixPath(String path) async => path;
Future close() async {
await db!.close();
}
}

View file

@ -3,7 +3,7 @@ import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/app.dart'; import 'package:twonly/src/app.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/providers/api_provider.dart'; import 'package:twonly/src/providers/api_provider.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';

View file

@ -4,9 +4,9 @@ import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/contacts_db.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/model/json/message.dart' as my; import 'package:twonly/src/json_models/message.dart' as my;
/// Streams are created so that app can respond to notification-related events /// Streams are created so that app can respond to notification-related events
/// since the plugin is initialized in the `main` function /// since the plugin is initialized in the `main` function
@ -167,8 +167,9 @@ String getPushNotificationText(String key, String userName) {
Future localPushNotificationNewMessage( Future localPushNotificationNewMessage(
int fromUserId, my.MessageJson message, int messageId) async { int fromUserId, my.MessageJson message, int messageId) async {
Contact? user = Contact? user = await twonlyDatabase.contactsDao
await twonlyDatabase.getContactByUserId(fromUserId).getSingleOrNull(); .getContactByUserId(fromUserId)
.getSingleOrNull();
if (user == null) return; if (user == null) return;

View file

@ -1,14 +1,8 @@
import 'dart:typed_data';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/model/identity_key_store_model.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/src/database/twonly_database.dart';
bool eq<E>(List<E>? list1, List<E>? list2) =>
ListEquality<E>().equals(list1, list2);
// make it easier to read
typedef DB = DbSignalIdentityKeyStore;
class ConnectIdentityKeyStore extends IdentityKeyStore { class ConnectIdentityKeyStore extends IdentityKeyStore {
ConnectIdentityKeyStore(this.identityKeyPair, this.localRegistrationId); ConnectIdentityKeyStore(this.identityKeyPair, this.localRegistrationId);
@ -18,15 +12,15 @@ class ConnectIdentityKeyStore extends IdentityKeyStore {
@override @override
Future<IdentityKey?> getIdentity(SignalProtocolAddress address) async { Future<IdentityKey?> getIdentity(SignalProtocolAddress address) async {
var dbIdentityKey = (await dbProvider.db!.query(DB.tableName, SignalIdentityKeyStore? identity =
columns: [DB.columnIdentityKey], await (twonlyDatabase.select(twonlyDatabase.signalIdentityKeyStores)
where: '${DB.columnDeviceId} = ? AND ${DB.columnName} = ?', ..where((t) =>
whereArgs: <Object?>[address.getDeviceId(), address.getName()])); t.deviceId.equals(address.getDeviceId()) &
if (dbIdentityKey.isEmpty) { t.name.equals(address.getName())))
return null; .getSingleOrNull();
}
Uint8List identityKey = dbIdentityKey.first.cast()[DB.columnIdentityKey]; if (identity == null) return null;
return IdentityKey.fromBytes(identityKey, 0); return IdentityKey.fromBytes(identity.identityKey, 0);
} }
@override @override
@ -42,7 +36,8 @@ class ConnectIdentityKeyStore extends IdentityKeyStore {
if (identityKey == null) { if (identityKey == null) {
return false; return false;
} }
return trusted == null || eq(trusted.serialize(), identityKey.serialize()); return trusted == null ||
ListEquality().equals(trusted.serialize(), identityKey.serialize());
} }
@override @override
@ -52,16 +47,23 @@ class ConnectIdentityKeyStore extends IdentityKeyStore {
return false; return false;
} }
if (await getIdentity(address) == null) { if (await getIdentity(address) == null) {
await dbProvider.db!.insert(DB.tableName, { await twonlyDatabase.into(twonlyDatabase.signalIdentityKeyStores).insert(
DB.columnDeviceId: address.getDeviceId(), SignalIdentityKeyStoresCompanion(
DB.columnName: address.getName(), deviceId: Value(address.getDeviceId()),
DB.columnIdentityKey: identityKey.serialize() name: Value(address.getName()),
}); identityKey: Value(identityKey.serialize()),
),
);
} else { } else {
await dbProvider.db!.update( await (twonlyDatabase.update(twonlyDatabase.signalIdentityKeyStores)
DB.tableName, {DB.columnIdentityKey: identityKey.serialize()}, ..where((t) =>
where: '${DB.columnDeviceId} = ? AND ${DB.columnName} = ?', t.deviceId.equals(address.getDeviceId()) &
whereArgs: <Object?>[address.getDeviceId(), address.getName()]); t.name.equals(address.getName())))
.write(
SignalIdentityKeyStoresCompanion(
identityKey: Value(identityKey.serialize()),
),
);
} }
return true; return true;
} }

View file

@ -1,49 +1,52 @@
import 'dart:typed_data'; import 'package:drift/drift.dart';
import 'package:logging/logging.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/model/pre_key_model.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/src/database/twonly_database.dart';
typedef DB = DbSignalPreKeyStore;
class ConnectPreKeyStore extends PreKeyStore { class ConnectPreKeyStore extends PreKeyStore {
@override @override
Future<bool> containsPreKey(int preKeyId) async { Future<bool> containsPreKey(int preKeyId) async {
final dbPreKey = await dbProvider.db!.query(DB.tableName, final preKeyRecord =
columns: [DB.columnPreKey], await (twonlyDatabase.select(twonlyDatabase.signalPreKeyStores)
where: '${DB.columnPreKeyId} = ?', ..where((tbl) => tbl.preKeyId.equals(preKeyId)))
whereArgs: <Object?>[preKeyId]); .get();
return dbPreKey.isNotEmpty; return preKeyRecord.isNotEmpty;
} }
@override @override
Future<PreKeyRecord> loadPreKey(int preKeyId) async { Future<PreKeyRecord> loadPreKey(int preKeyId) async {
final dbPreKey = await dbProvider.db!.query(DB.tableName, final preKeyRecord =
columns: [DB.columnPreKey], await (twonlyDatabase.select(twonlyDatabase.signalPreKeyStores)
where: '${DB.columnPreKeyId} = ?', ..where((tbl) => tbl.preKeyId.equals(preKeyId)))
whereArgs: <Object?>[preKeyId]); .get();
if (dbPreKey.isEmpty) { if (preKeyRecord.isEmpty) {
throw InvalidKeyIdException('No such preKey record! - $preKeyId'); throw InvalidKeyIdException('No such preKey record! - $preKeyId');
} }
Uint8List preKey = dbPreKey.first.cast()[DB.columnPreKey]; Uint8List preKey = preKeyRecord.first.preKey;
return PreKeyRecord.fromBuffer(preKey); return PreKeyRecord.fromBuffer(preKey);
} }
@override @override
Future<void> removePreKey(int preKeyId) async { Future<void> removePreKey(int preKeyId) async {
await dbProvider.db!.delete(DB.tableName, await (twonlyDatabase.delete(twonlyDatabase.signalPreKeyStores)
where: '${DB.columnPreKeyId} = ?', ..where((tbl) => tbl.preKeyId.equals(preKeyId)))
whereArgs: <Object?>[DB.columnPreKeyId]); .go();
} }
@override @override
Future<void> storePreKey(int preKeyId, PreKeyRecord record) async { Future<void> storePreKey(int preKeyId, PreKeyRecord record) async {
if (!await containsPreKey(preKeyId)) { final preKeyCompanion = SignalPreKeyStoresCompanion(
await dbProvider.db!.insert(DB.tableName, preKeyId: Value(preKeyId),
{DB.columnPreKeyId: preKeyId, DB.columnPreKey: record.serialize()}); preKey: Value(record.serialize()),
} else { );
await dbProvider.db!.update(
DB.tableName, {DB.columnPreKey: record.serialize()}, try {
where: '${DB.columnPreKeyId} = ?', whereArgs: <Object?>[preKeyId]); await twonlyDatabase
.into(twonlyDatabase.signalPreKeyStores)
.insert(preKeyCompanion);
} catch (e) {
Logger("pre_key_store").shout("$e");
} }
} }
} }

View file

@ -1,31 +1,30 @@
import 'dart:typed_data'; import 'package:drift/drift.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/model/sender_key_store_model.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
typedef DB = DbSignalSenderKeyStore;
class ConnectSenderKeyStore extends SenderKeyStore { class ConnectSenderKeyStore extends SenderKeyStore {
@override @override
Future<SenderKeyRecord> loadSenderKey(SenderKeyName senderKeyName) async { Future<SenderKeyRecord> loadSenderKey(SenderKeyName senderKeyName) async {
final dbSenderKey = await dbProvider.db!.query(DB.tableName, SignalSenderKeyStore? identity =
columns: [DB.columnSenderKey], await (twonlyDatabase.select(twonlyDatabase.signalSenderKeyStores)
where: '${DB.columnSenderKeyName} = ?', ..where((t) => t.senderKeyName.equals(senderKeyName.serialize())))
whereArgs: <Object?>[senderKeyName.serialize()]); .getSingleOrNull();
if (dbSenderKey.isEmpty) { if (identity == null) {
throw InvalidKeyIdException( throw InvalidKeyIdException(
'No such sender key record! - $senderKeyName'); 'No such sender key record! - $senderKeyName');
} }
Uint8List preKey = dbSenderKey.first.cast()[DB.columnSenderKey]; return SenderKeyRecord.fromSerialized(identity.senderKey);
return SenderKeyRecord.fromSerialized(preKey);
} }
@override @override
Future<void> storeSenderKey( Future<void> storeSenderKey(
SenderKeyName senderKeyName, SenderKeyRecord record) async { SenderKeyName senderKeyName, SenderKeyRecord record) async {
await dbProvider.db!.insert(DB.tableName, { await twonlyDatabase.into(twonlyDatabase.signalSenderKeyStores).insert(
DB.columnSenderKeyName: senderKeyName.serialize(), SignalSenderKeyStoresCompanion(
DB.columnSenderKey: record.serialize() senderKey: Value(record.serialize()),
}); senderKeyName: Value(senderKeyName.serialize()),
),
);
} }
} }

View file

@ -1,73 +1,81 @@
import 'dart:typed_data'; import 'package:drift/drift.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/model/session_store_model.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/src/database/twonly_database.dart';
// make it easier to read
typedef DB = DbSignalSessionStore;
class ConnectSessionStore extends SessionStore { class ConnectSessionStore extends SessionStore {
ConnectSessionStore();
@override @override
Future<bool> containsSession(SignalProtocolAddress address) async { Future<bool> containsSession(SignalProtocolAddress address) async {
var list = (await dbProvider.db!.query(DB.tableName, final sessions =
columns: [], await (twonlyDatabase.select(twonlyDatabase.signalSessionStores)
where: '${DB.columnDeviceId} = ? AND ${DB.columnName} = ?', ..where((tbl) =>
whereArgs: <Object?>[address.getDeviceId(), address.getName()])); tbl.deviceId.equals(address.getDeviceId()) &
return list.isNotEmpty; tbl.name.equals(address.getName())))
.get();
return sessions.isNotEmpty;
} }
@override @override
Future<void> deleteAllSessions(String name) async { Future<void> deleteAllSessions(String name) async {
await dbProvider.db!.delete(DB.tableName, await (twonlyDatabase.delete(twonlyDatabase.signalSessionStores)
where: '${DB.columnName} = ?', whereArgs: <Object?>[name]); ..where((tbl) => tbl.name.equals(name)))
.go();
} }
@override @override
Future<void> deleteSession(SignalProtocolAddress address) async { Future<void> deleteSession(SignalProtocolAddress address) async {
await dbProvider.db!.delete(DB.tableName, await (twonlyDatabase.delete(twonlyDatabase.signalSessionStores)
where: '${DB.columnDeviceId} = ? AND ${DB.columnName} = ?', ..where((tbl) =>
whereArgs: <Object?>[address.getDeviceId(), address.getName()]); tbl.deviceId.equals(address.getDeviceId()) &
tbl.name.equals(address.getName())))
.go();
} }
@override @override
Future<List<int>> getSubDeviceSessions(String name) async { Future<List<int>> getSubDeviceSessions(String name) async {
var deviceIds = (await dbProvider.db!.query(DB.tableName, final deviceIds = await (twonlyDatabase
columns: [DB.columnDeviceId], .select(twonlyDatabase.signalSessionStores)
where: '${DB.columnDeviceId} != 1 AND ${DB.columnName} = ?', ..where(
whereArgs: <Object?>[name])); (tbl) => tbl.deviceId.equals(1).not() & tbl.name.equals(name)))
return deviceIds.cast(); .get();
return deviceIds.map((row) => row.deviceId).toList();
} }
@override @override
Future<SessionRecord> loadSession(SignalProtocolAddress address) async { Future<SessionRecord> loadSession(SignalProtocolAddress address) async {
var dbSession = (await dbProvider.db!.query(DB.tableName, final dbSession =
columns: [DB.columnSessionRecord], await (twonlyDatabase.select(twonlyDatabase.signalSessionStores)
where: '${DB.columnDeviceId} = ? AND ${DB.columnName} = ?', ..where((tbl) =>
whereArgs: <Object?>[address.getDeviceId(), address.getName()])); tbl.deviceId.equals(address.getDeviceId()) &
tbl.name.equals(address.getName())))
.get();
if (dbSession.isEmpty) { if (dbSession.isEmpty) {
return SessionRecord(); return SessionRecord();
} }
Uint8List session = dbSession.first.cast()[DB.columnSessionRecord]; Uint8List session = dbSession.first.sessionRecord;
return SessionRecord.fromSerialized(session); return SessionRecord.fromSerialized(session);
} }
@override @override
Future<void> storeSession( Future<void> storeSession(
SignalProtocolAddress address, SessionRecord record) async { SignalProtocolAddress address, SessionRecord record) async {
final sessionCompanion = SignalSessionStoresCompanion(
deviceId: Value(address.getDeviceId()),
name: Value(address.getName()),
sessionRecord: Value(record.serialize()),
);
if (!await containsSession(address)) { if (!await containsSession(address)) {
await dbProvider.db!.insert(DB.tableName, { await twonlyDatabase
DB.columnDeviceId: address.getDeviceId(), .into(twonlyDatabase.signalSessionStores)
DB.columnName: address.getName(), .insert(sessionCompanion);
DB.columnSessionRecord: record.serialize()
});
} else { } else {
await dbProvider.db!.update( await (twonlyDatabase.update(twonlyDatabase.signalSessionStores)
DB.tableName, {DB.columnSessionRecord: record.serialize()}, ..where((tbl) =>
where: '${DB.columnDeviceId} = ? AND ${DB.columnName} = ?', tbl.deviceId.equals(address.getDeviceId()) &
whereArgs: <Object?>[address.getDeviceId(), address.getName()]); tbl.name.equals(address.getName())))
.write(sessionCompanion);
} }
} }
} }

View file

@ -10,8 +10,7 @@ import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:sqflite_sqlcipher/sqflite.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/database.dart';
import 'package:twonly/src/proto/api/error.pb.dart'; import 'package:twonly/src/proto/api/error.pb.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -20,18 +19,6 @@ extension ShortCutsExtension on BuildContext {
TwonlyDatabase get db => Provider.of<TwonlyDatabase>(this); TwonlyDatabase get db => Provider.of<TwonlyDatabase>(this);
} }
// Function to check if a column exists
Future<bool> columnExists(
Database db, String tableName, String columnName) async {
final result = await db.rawQuery('PRAGMA table_info($tableName)');
for (var row in result) {
if (row['name'] == columnName) {
return true; // Column exists
}
}
return false; // Column does not exist
}
Future<void> writeLogToFile(LogRecord record) async { Future<void> writeLogToFile(LogRecord record) async {
final directory = await getApplicationDocumentsDirectory(); final directory = await getApplicationDocumentsDirectory();
final logFile = File('${directory.path}/app.log'); final logFile = File('${directory.path}/app.log');

View file

@ -3,9 +3,9 @@ import 'dart:io';
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:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/model/json/signal_identity.dart'; import 'package:twonly/src/json_models/signal_identity.dart';
import 'package:twonly/src/model/json/user_data.dart'; import 'package:twonly/src/json_models/userdata.dart';
import 'package:twonly/src/proto/api/server_to_client.pb.dart'; import 'package:twonly/src/proto/api/server_to_client.pb.dart';
import 'package:twonly/src/signal/connect_signal_protocol_store.dart'; import 'package:twonly/src/signal/connect_signal_protocol_store.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';

View file

@ -1,8 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/src/json_models/userdata.dart';
import 'package:twonly/src/model/json/user_data.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
Future<bool> isUserCreated() async { Future<bool> isUserCreated() async {
@ -15,7 +14,7 @@ Future<bool> isUserCreated() async {
Future<UserData?> getUser() async { Future<UserData?> getUser() async {
final storage = getSecureStorage(); final storage = getSecureStorage();
String? userJson = await storage.read(key: "user_data"); String? userJson = await storage.read(key: "userData");
if (userJson == null) { if (userJson == null) {
return null; return null;
} }
@ -30,16 +29,11 @@ Future<UserData?> getUser() async {
} }
Future<bool> deleteLocalUserData() async { Future<bool> deleteLocalUserData() async {
final storage = getSecureStorage();
var password = await storage.read(key: "sqflite_database_password");
await dbProvider.remove();
final appDir = await getApplicationSupportDirectory(); final appDir = await getApplicationSupportDirectory();
if (appDir.existsSync()) { if (appDir.existsSync()) {
appDir.deleteSync(recursive: true); appDir.deleteSync(recursive: true);
} }
final storage = getSecureStorage();
await storage.write(key: "sqflite_database_password", value: password);
await storage.deleteAll(); await storage.deleteAll();
return true; return true;
} }

View file

@ -5,8 +5,8 @@ import 'package:twonly/globals.dart';
import 'package:twonly/src/components/image_editor/action_button.dart'; import 'package:twonly/src/components/image_editor/action_button.dart';
import 'package:twonly/src/components/media_view_sizing.dart'; import 'package:twonly/src/components/media_view_sizing.dart';
import 'package:twonly/src/components/notification_badge.dart'; import 'package:twonly/src/components/notification_badge.dart';
import 'package:twonly/src/database/contacts_db.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/providers/api/media.dart'; import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/providers/send_next_media_to.dart'; import 'package:twonly/src/providers/send_next_media_to.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -55,8 +55,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
Future updateAsync(int userId) async { Future updateAsync(int userId) async {
if (sendNextMediaToUserName != null) return; if (sendNextMediaToUserName != null) return;
Contact? contact = Contact? contact = await twonlyDatabase.contactsDao
await twonlyDatabase.getContactByUserId(userId).getSingleOrNull(); .getContactByUserId(userId)
.getSingleOrNull();
if (contact != null) { if (contact != null) {
sendNextMediaToUserName = getContactDisplayName(contact); sendNextMediaToUserName = getContactDisplayName(contact);
} }

View file

@ -10,8 +10,8 @@ import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/headline.dart'; import 'package:twonly/src/components/headline.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/verified_shield.dart'; import 'package:twonly/src/components/verified_shield.dart';
import 'package:twonly/src/database/contacts_db.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/providers/api/media.dart'; import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/providers/send_next_media_to.dart'; import 'package:twonly/src/providers/send_next_media_to.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -54,7 +54,7 @@ class _ShareImageView extends State<ShareImageView> {
} }
Stream<List<Contact>> allContacts = Stream<List<Contact>> allContacts =
twonlyDatabase.watchContactsForChatList(); twonlyDatabase.contactsDao.watchContactsForChatList();
contactSub = allContacts.listen((allContacts) { contactSub = allContacts.listen((allContacts) {
setState(() { setState(() {

View file

@ -9,10 +9,10 @@ import 'package:twonly/src/components/animate_icon.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/message_send_state_icon.dart'; import 'package:twonly/src/components/message_send_state_icon.dart';
import 'package:twonly/src/components/verified_shield.dart'; import 'package:twonly/src/components/verified_shield.dart';
import 'package:twonly/src/database/contacts_db.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/messages_db.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/providers/api/api.dart'; import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/api/media.dart'; import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/providers/send_next_media_to.dart'; import 'package:twonly/src/providers/send_next_media_to.dart';
@ -158,8 +158,9 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
} }
Future initStreams() async { Future initStreams() async {
await twonlyDatabase.removeOldMessages(); await twonlyDatabase.messagesDao.removeOldMessages();
Stream<Contact> contact = twonlyDatabase.watchContact(widget.userid); Stream<Contact> contact =
twonlyDatabase.contactsDao.watchContact(widget.userid);
userSub = contact.listen((contact) { userSub = contact.listen((contact) {
setState(() { setState(() {
user = contact; user = contact;
@ -167,7 +168,7 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
}); });
Stream<List<Message>> msgStream = Stream<List<Message>> msgStream =
twonlyDatabase.watchAllMessagesFrom(widget.userid); twonlyDatabase.messagesDao.watchAllMessagesFrom(widget.userid);
messageSub = msgStream.listen((msgs) { messageSub = msgStream.listen((msgs) {
if (!context.mounted) return; if (!context.mounted) return;
var updated = false; var updated = false;
@ -181,7 +182,7 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
} }
} }
if (updated) { if (updated) {
twonlyDatabase.openedAllTextMessages(widget.userid); twonlyDatabase.messagesDao.openedAllTextMessages(widget.userid);
} else { } else {
// The stream should be get an update, so only update the UI when all are opened // The stream should be get an update, so only update the UI when all are opened
setState(() { setState(() {

View file

@ -9,10 +9,10 @@ import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/message_send_state_icon.dart'; import 'package:twonly/src/components/message_send_state_icon.dart';
import 'package:twonly/src/components/notification_badge.dart'; import 'package:twonly/src/components/notification_badge.dart';
import 'package:twonly/src/components/user_context_menu.dart'; import 'package:twonly/src/components/user_context_menu.dart';
import 'package:twonly/src/database/contacts_db.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/messages_db.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/providers/api/media.dart'; import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/providers/send_next_media_to.dart'; import 'package:twonly/src/providers/send_next_media_to.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -50,7 +50,7 @@ class _ChatListViewState extends State<ChatListView> {
// title: // title:
actions: [ actions: [
StreamBuilder( StreamBuilder(
stream: twonlyDatabase.watchContactsRequested(), stream: twonlyDatabase.contactsDao.watchContactsRequested(),
builder: (context, snapshot) { builder: (context, snapshot) {
var count = 0; var count = 0;
if (snapshot.hasData && snapshot.data != null) { if (snapshot.hasData && snapshot.data != null) {
@ -86,7 +86,7 @@ class _ChatListViewState extends State<ChatListView> {
], ],
), ),
body: StreamBuilder( body: StreamBuilder(
stream: twonlyDatabase.watchContactsForChatList(), stream: twonlyDatabase.contactsDao.watchContactsForChatList(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == null) { if (!snapshot.hasData || snapshot.data == null) {
return Container(); return Container();
@ -190,7 +190,8 @@ class _UserListItem extends State<UserListItem> {
child: ListTile( child: ListTile(
title: Text(getContactDisplayName(widget.user)), title: Text(getContactDisplayName(widget.user)),
subtitle: StreamBuilder( subtitle: StreamBuilder(
stream: twonlyDatabase.watchLastMessage(widget.user.userId), stream:
twonlyDatabase.messagesDao.watchLastMessage(widget.user.userId),
builder: (context, lastMessageSnapshot) { builder: (context, lastMessageSnapshot) {
if (!lastMessageSnapshot.hasData) { if (!lastMessageSnapshot.hasData) {
return Container(); return Container();
@ -200,7 +201,8 @@ class _UserListItem extends State<UserListItem> {
} }
final lastMessage = lastMessageSnapshot.data!.first; final lastMessage = lastMessageSnapshot.data!.first;
return StreamBuilder( return StreamBuilder(
stream: twonlyDatabase.watchMessageNotOpened(widget.user.userId), stream: twonlyDatabase.messagesDao
.watchMessageNotOpened(widget.user.userId),
builder: (context, notOpenedMessagesSnapshot) { builder: (context, notOpenedMessagesSnapshot) {
if (!lastMessageSnapshot.hasData) { if (!lastMessageSnapshot.hasData) {
return Container(); return Container();

View file

@ -9,9 +9,9 @@ import 'package:provider/provider.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/components/animate_icon.dart'; import 'package:twonly/src/components/animate_icon.dart';
import 'package:twonly/src/components/media_view_sizing.dart'; import 'package:twonly/src/components/media_view_sizing.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/messages_db.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/providers/api/api.dart'; import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/api/media.dart'; import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/providers/send_next_media_to.dart'; import 'package:twonly/src/providers/send_next_media_to.dart';
@ -57,7 +57,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
Future asyncLoadNextMedia(bool firstRun) async { Future asyncLoadNextMedia(bool firstRun) async {
Stream<List<Message>> messages = Stream<List<Message>> messages =
twonlyDatabase.watchMediaMessageNotOpened(widget.userId); twonlyDatabase.messagesDao.watchMediaMessageNotOpened(widget.userId);
_subscription = messages.listen((messages) { _subscription = messages.listen((messages) {
for (Message msg in messages) { for (Message msg in messages) {

View file

@ -2,13 +2,13 @@ import 'dart:async';
import 'package:drift/drift.dart' hide Column; import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/components/alert_dialog.dart'; import 'package:twonly/src/components/alert_dialog.dart';
import 'package:twonly/src/database/contacts_db.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/components/headline.dart'; import 'package:twonly/src/components/headline.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/providers/api/api.dart'; import 'package:twonly/src/providers/api/api.dart';
// ignore: library_prefixes // ignore: library_prefixes
import 'package:twonly/src/utils/signal.dart' as SignalHelper; import 'package:twonly/src/utils/signal.dart' as SignalHelper;
@ -49,7 +49,8 @@ class _SearchUsernameView extends State<SearchUsernameView> {
return; return;
} }
int added = await twonlyDatabase.insertContact(ContactsCompanion( int added =
await twonlyDatabase.contactsDao.insertContact(ContactsCompanion(
username: Value(searchUserName.text), username: Value(searchUserName.text),
userId: Value(res.value.userdata.userId.toInt()), userId: Value(res.value.userdata.userId.toInt()),
requested: Value(false), requested: Value(false),
@ -97,7 +98,8 @@ class _SearchUsernameView extends State<SearchUsernameView> {
); );
} }
Stream<List<Contact>> contacts = twonlyDatabase.watchNotAcceptedContacts(); Stream<List<Contact>> contacts =
twonlyDatabase.contactsDao.watchNotAcceptedContacts();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@ -195,8 +197,8 @@ class _ContactsListViewState extends State<ContactsListView> {
color: const Color.fromARGB(164, 244, 67, 54)), color: const Color.fromARGB(164, 244, 67, 54)),
onPressed: () async { onPressed: () async {
final update = ContactsCompanion(blocked: Value(true)); final update = ContactsCompanion(blocked: Value(true));
await twonlyDatabase.updateContact( await twonlyDatabase.contactsDao
contact.userId, update); .updateContact(contact.userId, update);
}, },
), ),
), ),
@ -205,7 +207,7 @@ class _ContactsListViewState extends State<ContactsListView> {
child: IconButton( child: IconButton(
icon: Icon(Icons.close, color: Colors.red), icon: Icon(Icons.close, color: Colors.red),
onPressed: () async { onPressed: () async {
await twonlyDatabase await twonlyDatabase.contactsDao
.deleteContactByUserId(contact.userId); .deleteContactByUserId(contact.userId);
encryptAndSendMessage( encryptAndSendMessage(
null, null,
@ -223,7 +225,8 @@ class _ContactsListViewState extends State<ContactsListView> {
icon: Icon(Icons.check, color: Colors.green), icon: Icon(Icons.check, color: Colors.green),
onPressed: () async { onPressed: () async {
final update = ContactsCompanion(accepted: Value(true)); final update = ContactsCompanion(accepted: Value(true));
await twonlyDatabase.updateContact(contact.userId, update); await twonlyDatabase.contactsDao
.updateContact(contact.userId, update);
encryptAndSendMessage( encryptAndSendMessage(
null, null,
contact.userId, contact.userId,

View file

@ -6,8 +6,8 @@ import 'package:qr_flutter/qr_flutter.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/components/format_long_string.dart'; import 'package:twonly/src/components/format_long_string.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/database/contacts_db.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/signal.dart'; import 'package:twonly/src/utils/signal.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -37,7 +37,7 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Stream<Contact?> contact = twonlyDatabase Stream<Contact?> contact = twonlyDatabase.contactsDao
.getContactByUserId(widget.contact.userId) .getContactByUserId(widget.contact.userId)
.watchSingleOrNull(); .watchSingleOrNull();
@ -145,7 +145,8 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
onPressed: () { onPressed: () {
final update = final update =
ContactsCompanion(verified: Value(false)); ContactsCompanion(verified: Value(false));
twonlyDatabase.updateContact(contact.userId, update); twonlyDatabase.contactsDao
.updateContact(contact.userId, update);
}, },
label: Text( label: Text(
context.lang.contactVerifyNumberClearVerification), context.lang.contactVerifyNumberClearVerification),
@ -155,7 +156,8 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
icon: FaIcon(FontAwesomeIcons.shieldHeart), icon: FaIcon(FontAwesomeIcons.shieldHeart),
onPressed: () { onPressed: () {
final update = ContactsCompanion(verified: Value(true)); final update = ContactsCompanion(verified: Value(true));
twonlyDatabase.updateContact(contact.userId, update); twonlyDatabase.contactsDao
.updateContact(contact.userId, update);
}, },
label: Text(context.lang.contactVerifyNumberMarkAsVerified), label: Text(context.lang.contactVerifyNumberMarkAsVerified),
); );

View file

@ -7,8 +7,8 @@ import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/verified_shield.dart'; import 'package:twonly/src/components/verified_shield.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/database/contacts_db.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/contact/contact_verify_view.dart'; import 'package:twonly/src/views/contact/contact_verify_view.dart';
@ -24,8 +24,9 @@ class ContactView extends StatefulWidget {
class _ContactViewState extends State<ContactView> { class _ContactViewState extends State<ContactView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Stream<Contact?> contact = Stream<Contact?> contact = twonlyDatabase.contactsDao
twonlyDatabase.getContactByUserId(widget.userId).watchSingleOrNull(); .getContactByUserId(widget.userId)
.watchSingleOrNull();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@ -79,7 +80,8 @@ class _ContactViewState extends State<ContactView> {
if (context.mounted && nickName != null && nickName != "") { if (context.mounted && nickName != null && nickName != "") {
final update = ContactsCompanion(nickName: Value(nickName)); final update = ContactsCompanion(nickName: Value(nickName));
twonlyDatabase.updateContact(contact.userId, update); twonlyDatabase.contactsDao
.updateContact(contact.userId, update);
} }
}, },
), ),
@ -109,8 +111,8 @@ class _ContactViewState extends State<ContactView> {
if (block) { if (block) {
final update = ContactsCompanion(blocked: Value(true)); final update = ContactsCompanion(blocked: Value(true));
if (context.mounted) { if (context.mounted) {
await twonlyDatabase.updateContact( await twonlyDatabase.contactsDao
contact.userId, update); .updateContact(contact.userId, update);
} }
if (context.mounted) { if (context.mounted) {
Navigator.popUntil(context, (route) => route.isFirst); Navigator.popUntil(context, (route) => route.isFirst);

View file

@ -5,7 +5,7 @@ import 'package:twonly/globals.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:twonly/src/components/alert_dialog.dart'; import 'package:twonly/src/components/alert_dialog.dart';
import 'package:twonly/src/model/json/user_data.dart'; import 'package:twonly/src/json_models/userdata.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/signal.dart'; import 'package:twonly/src/utils/signal.dart';
@ -43,7 +43,7 @@ class _RegisterViewState extends State<RegisterView> {
username: username, username: username,
displayName: username, displayName: username,
); );
storage.write(key: "user_data", value: jsonEncode(userData)); storage.write(key: "userData", value: jsonEncode(userData));
} }
setState(() { setState(() {

View file

@ -27,7 +27,7 @@ class _PrivacyViewState extends State<PrivacyView> {
ListTile( ListTile(
title: Text(context.lang.settingsPrivacyBlockUsers), title: Text(context.lang.settingsPrivacyBlockUsers),
subtitle: StreamBuilder( subtitle: StreamBuilder(
stream: twonlyDatabase.watchContactsBlocked(), stream: twonlyDatabase.contactsDao.watchContactsBlocked(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data != null) { if (snapshot.hasData && snapshot.data != null) {
return Text( return Text(

View file

@ -2,8 +2,8 @@ import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/database/contacts_db.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
class PrivacyViewBlockUsers extends StatefulWidget { class PrivacyViewBlockUsers extends StatefulWidget {
@ -21,7 +21,7 @@ class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
allUsers = twonlyDatabase.watchAllContacts(); allUsers = twonlyDatabase.contactsDao.watchAllContacts();
loadAsync(); loadAsync();
} }
@ -105,7 +105,7 @@ class UserList extends StatelessWidget {
Future block(BuildContext context, int userId, bool? value) async { Future block(BuildContext context, int userId, bool? value) async {
if (value != null) { if (value != null) {
final update = ContactsCompanion(blocked: Value(!value)); final update = ContactsCompanion(blocked: Value(!value));
await twonlyDatabase.updateContact(userId, update); await twonlyDatabase.contactsDao.updateContact(userId, update);
} }
} }

View file

@ -1,7 +1,7 @@
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/components/better_list_title.dart'; import 'package:twonly/src/components/better_list_title.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/model/json/user_data.dart'; import 'package:twonly/src/json_models/userdata.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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';

View file

@ -265,14 +265,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.7.1" version: "2.7.1"
cv:
dependency: "direct main"
description:
name: cv
sha256: b7ad2d39ebd8d0a610c69becbd9b0131c1b1544d42186fa025710f8ebff038a0
url: "https://pub.dev"
source: hosted
version: "1.1.3"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
@ -410,7 +402,7 @@ packages:
source: hosted source: hosted
version: "3.10.2" version: "3.10.2"
fixnum: fixnum:
dependency: "direct main" dependency: transitive
description: description:
name: fixnum name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
@ -422,14 +414,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_foreground_task:
dependency: "direct main"
description:
name: flutter_foreground_task
sha256: "206017ee1bf864f34b8d7bce664a172717caa21af8da23f55866470dfe316644"
url: "https://pub.dev"
source: hosted
version: "8.17.0"
flutter_image_compress: flutter_image_compress:
dependency: "direct main" dependency: "direct main"
description: description:
@ -669,14 +653,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.3"
google_fonts:
dependency: "direct main"
description:
name: google_fonts
sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82
url: "https://pub.dev"
source: hosted
version: "6.2.1"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
@ -1189,14 +1165,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0" version: "4.1.0"
reorderables:
dependency: "direct main"
description:
name: reorderables
sha256: "004a886e4878df1ee27321831c838bc1c976311f4ca6a74ce7d561e506540a77"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
restart_app: restart_app:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1221,62 +1189,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a"
url: "https://pub.dev"
source: hosted
version: "2.5.1"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f"
url: "https://pub.dev"
source: hosted
version: "2.4.4"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -1330,22 +1242,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.0"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
url: "https://pub.dev"
source: hosted
version: "2.5.4+6"
sqflite_sqlcipher:
dependency: "direct main"
description:
name: sqflite_sqlcipher
sha256: "16033fde6c7d7bd657b71a2bc42332ab02bc8001c3212f502d2e02714e735ec9"
url: "https://pub.dev"
source: hosted
version: "3.1.0+1"
sqlite3: sqlite3:
dependency: transitive dependency: transitive
description: description:
@ -1402,14 +1298,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
url: "https://pub.dev"
source: hosted
version: "3.3.0+3"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:

View file

@ -15,16 +15,13 @@ dependencies:
# path: ../CamerAwesome # path: ../CamerAwesome
collection: ^1.18.0 collection: ^1.18.0
connectivity_plus: ^6.1.2 connectivity_plus: ^6.1.2
cv: ^1.1.3
drift: ^2.25.1 drift: ^2.25.1
drift_flutter: ^0.2.4 drift_flutter: ^0.2.4
exif: ^3.3.0 exif: ^3.3.0
firebase_core: ^3.11.0 firebase_core: ^3.11.0
firebase_messaging: ^15.2.2 firebase_messaging: ^15.2.2
fixnum: ^1.1.1
flutter: flutter:
sdk: flutter sdk: flutter
flutter_foreground_task: ^8.17.0
flutter_image_compress: ^2.4.0 flutter_image_compress: ^2.4.0
flutter_local_notifications: ^18.0.1 flutter_local_notifications: ^18.0.1
flutter_localizations: flutter_localizations:
@ -32,7 +29,6 @@ dependencies:
flutter_secure_storage: ^10.0.0-beta.4 flutter_secure_storage: ^10.0.0-beta.4
font_awesome_flutter: ^10.8.0 font_awesome_flutter: ^10.8.0
gal: ^2.3.1 gal: ^2.3.1
google_fonts: ^6.2.1
hand_signature: ^3.0.3 hand_signature: ^3.0.3
hive: ^2.2.3 hive: ^2.2.3
image: ^4.3.0 image: ^4.3.0
@ -54,10 +50,8 @@ dependencies:
cryptography_flutter_plus: ^2.3.2 cryptography_flutter_plus: ^2.3.2
provider: ^6.1.2 provider: ^6.1.2
qr_flutter: ^4.1.0 qr_flutter: ^4.1.0
reorderables: ^0.6.0
restart_app: ^1.3.2 restart_app: ^1.3.2
screenshot: ^3.0.0 screenshot: ^3.0.0
sqflite_sqlcipher: ^3.1.0+1
url_launcher: ^6.3.1 url_launcher: ^6.3.1
web_socket_channel: ^3.0.1 web_socket_channel: ^3.0.1