2026-04-16 07:35:56 +02:00
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
|
|
import 'package:drift/drift.dart';
|
|
|
|
|
import 'package:drift/native.dart';
|
|
|
|
|
import 'package:path/path.dart' as p;
|
|
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
|
|
|
|
|
|
part 'database.g.dart';
|
|
|
|
|
|
|
|
|
|
// ── Tables ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
class Accounts extends Table {
|
|
|
|
|
TextColumn get id => text()();
|
|
|
|
|
TextColumn get displayName => text()();
|
|
|
|
|
TextColumn get email => text()();
|
|
|
|
|
TextColumn get imapHost => text()();
|
|
|
|
|
IntColumn get imapPort => integer()();
|
|
|
|
|
BoolColumn get imapSsl => boolean()();
|
|
|
|
|
TextColumn get smtpHost => text()();
|
|
|
|
|
IntColumn get smtpPort => integer()();
|
|
|
|
|
BoolColumn get smtpSsl => boolean()();
|
2026-04-18 15:13:47 +02:00
|
|
|
// Added in schema v2:
|
|
|
|
|
TextColumn get accountType =>
|
|
|
|
|
text().withDefault(const Constant('imap'))();
|
|
|
|
|
TextColumn get jmapUrl => text().nullable()();
|
2026-04-18 15:37:11 +02:00
|
|
|
// Added in schema v3:
|
|
|
|
|
TextColumn get username => text().withDefault(const Constant(''))();
|
2026-04-16 07:35:56 +02:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Set<Column> get primaryKey => {id};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 08:21:14 +02:00
|
|
|
@DataClassName('MailboxRow')
|
2026-04-16 07:35:56 +02:00
|
|
|
class Mailboxes extends Table {
|
|
|
|
|
TextColumn get id => text()();
|
|
|
|
|
TextColumn get accountId =>
|
|
|
|
|
text().references(Accounts, #id, onDelete: KeyAction.cascade)();
|
|
|
|
|
TextColumn get path => text()();
|
|
|
|
|
TextColumn get name => text()();
|
|
|
|
|
IntColumn get unreadCount => integer().withDefault(const Constant(0))();
|
|
|
|
|
IntColumn get totalCount => integer().withDefault(const Constant(0))();
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Set<Column> get primaryKey => {id};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Emails extends Table {
|
|
|
|
|
TextColumn get id => text()();
|
|
|
|
|
TextColumn get accountId =>
|
|
|
|
|
text().references(Accounts, #id, onDelete: KeyAction.cascade)();
|
|
|
|
|
TextColumn get mailboxPath => text()();
|
|
|
|
|
IntColumn get uid => integer()();
|
|
|
|
|
TextColumn get subject => text().nullable()();
|
|
|
|
|
DateTimeColumn get sentAt => dateTime().nullable()();
|
|
|
|
|
DateTimeColumn get receivedAt => dateTime()();
|
|
|
|
|
// JSON-encoded List<{name,email}>
|
|
|
|
|
TextColumn get fromJson => text().withDefault(const Constant('[]'))();
|
2026-04-16 15:14:18 +02:00
|
|
|
TextColumn get toAddresses => text().withDefault(const Constant('[]'))();
|
2026-04-16 07:35:56 +02:00
|
|
|
TextColumn get ccJson => text().withDefault(const Constant('[]'))();
|
|
|
|
|
TextColumn get preview => text().nullable()();
|
|
|
|
|
BoolColumn get isSeen => boolean().withDefault(const Constant(false))();
|
|
|
|
|
BoolColumn get isFlagged => boolean().withDefault(const Constant(false))();
|
2026-04-18 15:13:47 +02:00
|
|
|
BoolColumn get hasAttachment =>
|
|
|
|
|
boolean().withDefault(const Constant(false))();
|
2026-04-16 07:35:56 +02:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Set<Column> get primaryKey => {id};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class EmailBodies extends Table {
|
|
|
|
|
TextColumn get emailId =>
|
|
|
|
|
text().references(Emails, #id, onDelete: KeyAction.cascade)();
|
|
|
|
|
TextColumn get textBody => text().nullable()();
|
|
|
|
|
TextColumn get htmlBody => text().nullable()();
|
|
|
|
|
// JSON-encoded List<{filename,contentType,size}>
|
|
|
|
|
TextColumn get attachmentsJson =>
|
|
|
|
|
text().withDefault(const Constant('[]'))();
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Set<Column> get primaryKey => {emailId};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 19:06:02 +02:00
|
|
|
/// Auto-saved compose drafts — persisted across app restarts.
|
|
|
|
|
class Drafts extends Table {
|
|
|
|
|
IntColumn get id => integer().autoIncrement()();
|
|
|
|
|
TextColumn get accountId => text().nullable()();
|
|
|
|
|
/// Set for replies/reply-alls; null for new messages.
|
|
|
|
|
TextColumn get replyToEmailId => text().nullable()();
|
|
|
|
|
TextColumn get toText => text().withDefault(const Constant(''))();
|
|
|
|
|
TextColumn get ccText => text().withDefault(const Constant(''))();
|
|
|
|
|
TextColumn get subjectText => text().withDefault(const Constant(''))();
|
|
|
|
|
TextColumn get bodyText => text().withDefault(const Constant(''))();
|
|
|
|
|
DateTimeColumn get updatedAt => dateTime()();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 07:35:56 +02:00
|
|
|
// ── Database ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-04-18 19:06:02 +02:00
|
|
|
@DriftDatabase(tables: [Accounts, Mailboxes, Emails, EmailBodies, Drafts])
|
2026-04-16 07:35:56 +02:00
|
|
|
class AppDatabase extends _$AppDatabase {
|
2026-04-17 10:05:31 +02:00
|
|
|
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
|
2026-04-16 07:35:56 +02:00
|
|
|
|
|
|
|
|
@override
|
2026-04-18 19:06:02 +02:00
|
|
|
int get schemaVersion => 4;
|
2026-04-18 15:13:47 +02:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
MigrationStrategy get migration => MigrationStrategy(
|
|
|
|
|
onUpgrade: (m, from, to) async {
|
|
|
|
|
if (from < 2) {
|
|
|
|
|
await m.addColumn(accounts, accounts.accountType);
|
|
|
|
|
await m.addColumn(accounts, accounts.jmapUrl);
|
|
|
|
|
}
|
2026-04-18 15:37:11 +02:00
|
|
|
if (from < 3) {
|
|
|
|
|
await m.addColumn(accounts, accounts.username);
|
|
|
|
|
}
|
2026-04-18 19:06:02 +02:00
|
|
|
if (from < 4) {
|
|
|
|
|
await m.createTable(drafts);
|
|
|
|
|
}
|
2026-04-18 15:13:47 +02:00
|
|
|
},
|
|
|
|
|
);
|
2026-04-16 07:35:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LazyDatabase _openConnection() {
|
|
|
|
|
return LazyDatabase(() async {
|
2026-04-18 13:25:16 +02:00
|
|
|
final dir = await getApplicationSupportDirectory();
|
2026-04-16 07:35:56 +02:00
|
|
|
final file = File(p.join(dir.path, 'sharedinbox.db'));
|
|
|
|
|
return NativeDatabase.createInBackground(file);
|
|
|
|
|
});
|
|
|
|
|
}
|