feat: 新增文件传输助手功能及相关组件
新增文件传输助手功能,包含设备发现、配对、传输等核心模块。主要变更包括: 1. 新增局域网、蓝牙、NFC等多种设备发现方式 2. 实现基于WebRTC、TCP、USB等多种传输协议 3. 添加相关权限管理及状态监控 4. 完善UI界面及交互流程 5. 更新依赖库及版本号至4.19.0 同时优化部分现有功能: 1. 聊天会话增加隐藏功能 2. 完善本地通知权限处理 3. 修复部分已知问题
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — Drift 数据库定义
|
||||
/// 创建时间: 2026-04-20
|
||||
/// 更新时间: 2026-04-28
|
||||
/// 更新时间: 2026-05-08
|
||||
/// 作用: 本地 SQLite 数据库表结构定义 (Drift)
|
||||
/// 上次更新: 新增 HanziCache 汉字查询缓存表 (v4)
|
||||
/// 上次更新: 新增传输表TransferDevices/TransferRecords/PairingRecords/TransferMessages (v12)
|
||||
/// ============================================================
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
@@ -203,6 +203,211 @@ class LearningRecords extends Table {
|
||||
DateTimeColumn get completedAt => dateTime()();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 聊天会话表
|
||||
// ============================================================
|
||||
|
||||
class ChatConversations extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get emoji => text().withDefault(const Constant('💬'))();
|
||||
TextColumn get name => text()();
|
||||
TextColumn get description => text().nullable()();
|
||||
TextColumn get bgImagePath => text().nullable()();
|
||||
TextColumn get categoriesJson => text().withDefault(const Constant('[]'))();
|
||||
TextColumn get settingsJson => text().withDefault(const Constant('{}'))();
|
||||
BoolColumn get isPinned => boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get isHidden => boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get isMuted => boolean().withDefault(const Constant(false))();
|
||||
TextColumn get lastMessageText => text().withDefault(const Constant(''))();
|
||||
DateTimeColumn get lastMessageAt => dateTime().nullable()();
|
||||
IntColumn get unreadCount => integer().withDefault(const Constant(0))();
|
||||
TextColumn get syncMode => text().withDefault(const Constant('local'))();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
DateTimeColumn get updatedAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 聊天消息记录表
|
||||
// ============================================================
|
||||
|
||||
class ChatMsgRecords extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get conversationId => text()();
|
||||
TextColumn get type => text()();
|
||||
TextColumn get role => text()();
|
||||
TextColumn get content => text()();
|
||||
TextColumn get author => text().nullable()();
|
||||
TextColumn get source => text().nullable()();
|
||||
TextColumn get category => text().nullable()();
|
||||
BoolColumn get isRead => boolean().withDefault(const Constant(false))();
|
||||
IntColumn get readCount => integer().withDefault(const Constant(0))();
|
||||
TextColumn get metaJson => text().withDefault(const Constant('{}'))();
|
||||
TextColumn get extJson => text().withDefault(const Constant('{}'))();
|
||||
TextColumn get replyToId => text().nullable()();
|
||||
TextColumn get richContent => text().withDefault(const Constant(''))();
|
||||
TextColumn get ipText => text().withDefault(const Constant(''))();
|
||||
TextColumn get ipDetailJson => text().withDefault(const Constant(''))();
|
||||
BoolColumn get isDeleted => boolean().withDefault(const Constant(false))();
|
||||
DateTimeColumn get deletedAt => dateTime().nullable()();
|
||||
DateTimeColumn get timestamp => dateTime()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
DateTimeColumn get updatedAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 聊天附件表
|
||||
// ============================================================
|
||||
|
||||
class ChatAttachments extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get messageId => text()();
|
||||
TextColumn get conversationId => text()();
|
||||
TextColumn get fileName => text()();
|
||||
TextColumn get filePath => text()();
|
||||
TextColumn get fileType => text()();
|
||||
IntColumn get fileSize => integer()();
|
||||
TextColumn get thumbnailPath => text().nullable()();
|
||||
IntColumn get width => integer().nullable()();
|
||||
IntColumn get height => integer().nullable()();
|
||||
IntColumn get durationMs => integer().nullable()();
|
||||
TextColumn get cloudUrl => text().nullable()();
|
||||
DateTimeColumn get cloudSyncedAt => dateTime().nullable()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// IP地址缓存表
|
||||
// ============================================================
|
||||
|
||||
class IpLocationCaches extends Table {
|
||||
TextColumn get ip => text()();
|
||||
TextColumn get city => text()();
|
||||
TextColumn get province => text().nullable()();
|
||||
TextColumn get fullText => text()();
|
||||
DateTimeColumn get queriedAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {ip};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 传输设备表 — 文件传输助手
|
||||
// ============================================================
|
||||
|
||||
class TransferDeviceRecords extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get alias => text()();
|
||||
TextColumn get deviceModel => text().nullable()();
|
||||
TextColumn get deviceType => text().withDefault(const Constant('mobile'))();
|
||||
TextColumn get ip => text().nullable()();
|
||||
IntColumn get port => integer().withDefault(const Constant(53317))();
|
||||
TextColumn get pairingMethod => text().withDefault(const Constant('lan'))();
|
||||
TextColumn get preferredTransport =>
|
||||
text().withDefault(const Constant('localsend_http'))();
|
||||
BoolColumn get isOnline => boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get isVerified => boolean().withDefault(const Constant(false))();
|
||||
TextColumn get publicKey => text().nullable()();
|
||||
TextColumn get fingerprint => text().nullable()();
|
||||
DateTimeColumn get lastSeen => dateTime()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
DateTimeColumn get updatedAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 传输记录表 — 文件传输助手
|
||||
// ============================================================
|
||||
|
||||
class TransferRecords extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get sessionId => text()();
|
||||
TextColumn get peerId => text()();
|
||||
TextColumn get peerAlias => text().withDefault(const Constant(''))();
|
||||
TextColumn get transport =>
|
||||
text().withDefault(const Constant('localsend_http'))();
|
||||
TextColumn get direction => text().withDefault(const Constant('send'))();
|
||||
TextColumn get status => text().withDefault(const Constant('waiting'))();
|
||||
TextColumn get fileName => text()();
|
||||
IntColumn get fileSize => integer().withDefault(const Constant(0))();
|
||||
IntColumn get transferredBytes => integer().withDefault(const Constant(0))();
|
||||
RealColumn get speed => real().withDefault(const Constant(0.0))();
|
||||
TextColumn get mimeType => text().nullable()();
|
||||
TextColumn get filePath => text().nullable()();
|
||||
TextColumn get thumbnailPath => text().nullable()();
|
||||
TextColumn get fileSha256 => text().nullable()();
|
||||
BoolColumn get hashVerified => boolean().nullable()();
|
||||
TextColumn get errorMessage => text().nullable()();
|
||||
DateTimeColumn get startTime => dateTime()();
|
||||
DateTimeColumn get endTime => dateTime().nullable()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
DateTimeColumn get updatedAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 配对记录表 — 文件传输助手
|
||||
// ============================================================
|
||||
|
||||
class PairingRecords extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get deviceId => text()();
|
||||
TextColumn get alias => text()();
|
||||
TextColumn get pairingMethod => text().withDefault(const Constant('lan'))();
|
||||
BoolColumn get isTrusted => boolean().withDefault(const Constant(false))();
|
||||
TextColumn get ip => text().nullable()();
|
||||
IntColumn get port => integer().withDefault(const Constant(53317))();
|
||||
TextColumn get fingerprint => text().nullable()();
|
||||
TextColumn get publicKey => text().nullable()();
|
||||
DateTimeColumn get pairedAt => dateTime()();
|
||||
DateTimeColumn get lastConnectedAt => dateTime().nullable()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
DateTimeColumn get updatedAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 传输消息表 — 文件传输助手
|
||||
// ============================================================
|
||||
|
||||
class TransferMsgRecords extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get sessionId => text()();
|
||||
TextColumn get type => text().withDefault(const Constant('text'))();
|
||||
TextColumn get content => text()();
|
||||
BoolColumn get isRemote => boolean().withDefault(const Constant(false))();
|
||||
TextColumn get peerDeviceId => text().nullable()();
|
||||
TextColumn get transferTaskId => text().nullable()();
|
||||
TextColumn get fileName => text().nullable()();
|
||||
IntColumn get fileSize => integer().nullable()();
|
||||
TextColumn get mimeType => text().nullable()();
|
||||
TextColumn get thumbnailPath => text().nullable()();
|
||||
TextColumn get filePath => text().nullable()();
|
||||
RealColumn get progress => real().nullable()();
|
||||
TextColumn get transferStatus => text().nullable()();
|
||||
TextColumn get deviceAlias => text().nullable()();
|
||||
TextColumn get deviceEmoji => text().nullable()();
|
||||
DateTimeColumn get timestamp => dateTime()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 数据库实例
|
||||
// ============================================================
|
||||
@@ -220,6 +425,14 @@ class LearningRecords extends Table {
|
||||
ShareHistories,
|
||||
LearningPlans,
|
||||
LearningRecords,
|
||||
ChatConversations,
|
||||
ChatMsgRecords,
|
||||
ChatAttachments,
|
||||
IpLocationCaches,
|
||||
TransferDeviceRecords,
|
||||
TransferRecords,
|
||||
PairingRecords,
|
||||
TransferMsgRecords,
|
||||
],
|
||||
)
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
@@ -229,7 +442,7 @@ class AppDatabase extends _$AppDatabase {
|
||||
static AppDatabase get instance => _instance;
|
||||
|
||||
@override
|
||||
int get schemaVersion => 8;
|
||||
int get schemaVersion => 12;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
@@ -263,6 +476,49 @@ class AppDatabase extends _$AppDatabase {
|
||||
'ALTER TABLE sentences ADD COLUMN isLiked INTEGER NOT NULL DEFAULT 0',
|
||||
);
|
||||
}
|
||||
if (from < 9) {
|
||||
await m.createTable(chatConversations);
|
||||
await m.createTable(chatMsgRecords);
|
||||
await m.createTable(chatAttachments);
|
||||
await m.createTable(ipLocationCaches);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_chat_msg_records_conv ON chat_msg_records (conversation_id, timestamp DESC)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_chat_msg_records_conv_deleted ON chat_msg_records (conversation_id, is_deleted)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_chat_attachments_msg ON chat_attachments (message_id)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_chat_attachments_conv ON chat_attachments (conversation_id)',
|
||||
);
|
||||
}
|
||||
if (from < 10) {
|
||||
await customStatement(
|
||||
'ALTER TABLE chat_msg_records ADD COLUMN reply_to_id TEXT DEFAULT NULL',
|
||||
);
|
||||
await customStatement(
|
||||
'ALTER TABLE chat_msg_records ADD COLUMN rich_content TEXT NOT NULL DEFAULT \'\'',
|
||||
);
|
||||
await customStatement(
|
||||
'ALTER TABLE chat_msg_records ADD COLUMN ip_text TEXT NOT NULL DEFAULT \'\'',
|
||||
);
|
||||
await customStatement(
|
||||
'ALTER TABLE chat_msg_records ADD COLUMN ip_detail_json TEXT NOT NULL DEFAULT \'\'',
|
||||
);
|
||||
}
|
||||
if (from < 11) {
|
||||
await customStatement(
|
||||
'ALTER TABLE chat_conversations ADD COLUMN is_hidden INTEGER NOT NULL DEFAULT 0',
|
||||
);
|
||||
}
|
||||
if (from < 12) {
|
||||
await m.createTable(transferDeviceRecords);
|
||||
await m.createTable(transferRecords);
|
||||
await m.createTable(pairingRecords);
|
||||
await m.createTable(transferMsgRecords);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -922,6 +1178,276 @@ class AppDatabase extends _$AppDatabase {
|
||||
learningRecords,
|
||||
)..where((t) => t.planId.equals(planId))).go();
|
||||
}
|
||||
|
||||
// ---- 聊天会话 CRUD ----
|
||||
|
||||
Future<void> insertChatConversation(ChatConversationsCompanion conv) {
|
||||
return into(
|
||||
chatConversations,
|
||||
).insert(conv, mode: InsertMode.insertOrIgnore);
|
||||
}
|
||||
|
||||
Future<List<ChatConversation>> getAllChatConversations() {
|
||||
return (select(chatConversations)..orderBy([
|
||||
(t) => OrderingTerm.desc(t.isPinned),
|
||||
(t) => OrderingTerm.desc(t.lastMessageAt),
|
||||
]))
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<ChatConversation?> getChatConversation(String id) {
|
||||
return (select(
|
||||
chatConversations,
|
||||
)..where((t) => t.id.equals(id))).getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<void> updateChatConversation(ChatConversationsCompanion conv) {
|
||||
return (update(
|
||||
chatConversations,
|
||||
)..where((t) => t.id.equals(conv.id.value))).write(conv);
|
||||
}
|
||||
|
||||
Future<void> deleteChatConversation(String id) {
|
||||
return (delete(chatConversations)..where((t) => t.id.equals(id))).go();
|
||||
}
|
||||
|
||||
Future<int> getChatConversationCount() async {
|
||||
final rows = await (selectOnly(
|
||||
chatConversations,
|
||||
)..addColumns([chatConversations.id.count()])).getSingle();
|
||||
return rows.read(chatConversations.id.count()) ?? 0;
|
||||
}
|
||||
|
||||
// ---- 聊天消息记录 CRUD ----
|
||||
|
||||
Future<void> insertChatMsgRecord(ChatMsgRecordsCompanion msg) {
|
||||
return into(chatMsgRecords).insert(msg);
|
||||
}
|
||||
|
||||
Future<void> insertChatMsgRecordBatch(
|
||||
List<ChatMsgRecordsCompanion> msgs,
|
||||
) async {
|
||||
await batch((b) {
|
||||
for (final msg in msgs) {
|
||||
b.insert(chatMsgRecords, msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<ChatMsgRecord>> getChatMsgRecords(
|
||||
String conversationId, {
|
||||
int limit = 50,
|
||||
int offset = 0,
|
||||
}) {
|
||||
return (select(chatMsgRecords)
|
||||
..where(
|
||||
(t) =>
|
||||
t.conversationId.equals(conversationId) &
|
||||
t.isDeleted.equals(false),
|
||||
)
|
||||
..orderBy([(t) => OrderingTerm.desc(t.timestamp)])
|
||||
..limit(limit, offset: offset))
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<ChatMsgRecord?> getChatMsgRecord(String id) {
|
||||
return (select(
|
||||
chatMsgRecords,
|
||||
)..where((t) => t.id.equals(id))).getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<void> updateChatMsgRecord(ChatMsgRecordsCompanion msg) {
|
||||
return (update(
|
||||
chatMsgRecords,
|
||||
)..where((t) => t.id.equals(msg.id.value))).write(msg);
|
||||
}
|
||||
|
||||
Future<void> softDeleteChatMsgRecord(String id) {
|
||||
return (update(chatMsgRecords)..where((t) => t.id.equals(id))).write(
|
||||
ChatMsgRecordsCompanion(
|
||||
isDeleted: const Value(true),
|
||||
deletedAt: Value(DateTime.now()),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> restoreChatMsgRecord(String id) {
|
||||
return (update(chatMsgRecords)..where((t) => t.id.equals(id))).write(
|
||||
ChatMsgRecordsCompanion(
|
||||
isDeleted: const Value(false),
|
||||
deletedAt: const Value(null),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> permanentlyDeleteMsgRecord(String id) {
|
||||
return (delete(chatMsgRecords)..where((t) => t.id.equals(id))).go();
|
||||
}
|
||||
|
||||
Future<List<ChatMsgRecord>> getDeletedChatMsgRecords(
|
||||
String conversationId, {
|
||||
int limit = 50,
|
||||
}) {
|
||||
return (select(chatMsgRecords)
|
||||
..where(
|
||||
(t) =>
|
||||
t.conversationId.equals(conversationId) &
|
||||
t.isDeleted.equals(true),
|
||||
)
|
||||
..orderBy([(t) => OrderingTerm.desc(t.deletedAt)])
|
||||
..limit(limit))
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<int> getChatMsgRecordCount(String conversationId) async {
|
||||
final rows =
|
||||
await (selectOnly(chatMsgRecords)
|
||||
..addColumns([chatMsgRecords.id.count()])
|
||||
..where(
|
||||
chatMsgRecords.conversationId.equals(conversationId) &
|
||||
chatMsgRecords.isDeleted.equals(false),
|
||||
))
|
||||
.getSingle();
|
||||
return rows.read(chatMsgRecords.id.count()) ?? 0;
|
||||
}
|
||||
|
||||
Future<int> getDeletedChatMsgRecordCount(String conversationId) async {
|
||||
final rows =
|
||||
await (selectOnly(chatMsgRecords)
|
||||
..addColumns([chatMsgRecords.id.count()])
|
||||
..where(
|
||||
chatMsgRecords.conversationId.equals(conversationId) &
|
||||
chatMsgRecords.isDeleted.equals(true),
|
||||
))
|
||||
.getSingle();
|
||||
return rows.read(chatMsgRecords.id.count()) ?? 0;
|
||||
}
|
||||
|
||||
Future<void> incrementMsgReadCount(String id) async {
|
||||
final msg = await getChatMsgRecord(id);
|
||||
if (msg == null) return;
|
||||
await (update(chatMsgRecords)..where((t) => t.id.equals(id))).write(
|
||||
ChatMsgRecordsCompanion(
|
||||
readCount: Value(msg.readCount + 1),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> markChatMsgRecordRead(String id) {
|
||||
return (update(chatMsgRecords)..where((t) => t.id.equals(id))).write(
|
||||
ChatMsgRecordsCompanion(
|
||||
isRead: const Value(true),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> markAllChatMsgRecordsRead(String conversationId) {
|
||||
return (update(chatMsgRecords)..where(
|
||||
(t) =>
|
||||
t.conversationId.equals(conversationId) & t.isRead.equals(false),
|
||||
))
|
||||
.write(const ChatMsgRecordsCompanion(isRead: Value(true)));
|
||||
}
|
||||
|
||||
Future<void> cleanExpiredDeletedMsgRecords({int days = 30}) {
|
||||
final cutoff = DateTime.now().subtract(Duration(days: days));
|
||||
return (delete(chatMsgRecords)..where(
|
||||
(t) =>
|
||||
t.isDeleted.equals(true) & t.deletedAt.isSmallerThanValue(cutoff),
|
||||
))
|
||||
.go();
|
||||
}
|
||||
|
||||
// ---- 聊天附件 CRUD ----
|
||||
|
||||
Future<void> insertChatAttachment(ChatAttachmentsCompanion att) {
|
||||
return into(chatAttachments).insert(att);
|
||||
}
|
||||
|
||||
Future<void> insertChatAttachmentBatch(
|
||||
List<ChatAttachmentsCompanion> atts,
|
||||
) async {
|
||||
await batch((b) {
|
||||
for (final att in atts) {
|
||||
b.insert(chatAttachments, att);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<ChatAttachment>> getChatAttachments(String messageId) {
|
||||
return (select(
|
||||
chatAttachments,
|
||||
)..where((t) => t.messageId.equals(messageId))).get();
|
||||
}
|
||||
|
||||
Future<List<ChatAttachment>> getChatAttachmentsByConversation(
|
||||
String conversationId, {
|
||||
int limit = 100,
|
||||
}) {
|
||||
return (select(chatAttachments)
|
||||
..where((t) => t.conversationId.equals(conversationId))
|
||||
..orderBy([(t) => OrderingTerm.desc(t.createdAt)])
|
||||
..limit(limit))
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<void> updateChatAttachment(ChatAttachmentsCompanion att) {
|
||||
return (update(
|
||||
chatAttachments,
|
||||
)..where((t) => t.id.equals(att.id.value))).write(att);
|
||||
}
|
||||
|
||||
Future<void> deleteChatAttachmentsByMessage(String messageId) {
|
||||
return (delete(
|
||||
chatAttachments,
|
||||
)..where((t) => t.messageId.equals(messageId))).go();
|
||||
}
|
||||
|
||||
Future<void> deleteChatAttachmentsByConversation(String conversationId) {
|
||||
return (delete(
|
||||
chatAttachments,
|
||||
)..where((t) => t.conversationId.equals(conversationId))).go();
|
||||
}
|
||||
|
||||
Future<int> getChatAttachmentCount(String conversationId) async {
|
||||
final rows =
|
||||
await (selectOnly(chatAttachments)
|
||||
..addColumns([chatAttachments.id.count()])
|
||||
..where(chatAttachments.conversationId.equals(conversationId)))
|
||||
.getSingle();
|
||||
return rows.read(chatAttachments.id.count()) ?? 0;
|
||||
}
|
||||
|
||||
Future<int> getUnsyncedChatAttachmentCount() async {
|
||||
final rows =
|
||||
await (selectOnly(chatAttachments)
|
||||
..addColumns([chatAttachments.id.count()])
|
||||
..where(chatAttachments.cloudUrl.isNull()))
|
||||
.getSingle();
|
||||
return rows.read(chatAttachments.id.count()) ?? 0;
|
||||
}
|
||||
|
||||
// ---- IP地址缓存 CRUD ----
|
||||
|
||||
Future<IpLocationCache?> getIpLocation(String ip) {
|
||||
return (select(
|
||||
ipLocationCaches,
|
||||
)..where((t) => t.ip.equals(ip))).getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<void> saveIpLocation(IpLocationCachesCompanion entry) {
|
||||
return into(
|
||||
ipLocationCaches,
|
||||
).insert(entry, mode: InsertMode.insertOrReplace);
|
||||
}
|
||||
|
||||
Future<void> clearIpLocationCache() {
|
||||
return delete(ipLocationCaches).go();
|
||||
}
|
||||
}
|
||||
|
||||
class HistorySentenceWithTime {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user