import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; import 'package:chepuhagram/data/models/message_model.dart'; import 'dart:typed_data'; class LocalDbService { static final LocalDbService _instance = LocalDbService._internal(); static Database? _database; factory LocalDbService() => _instance; LocalDbService._internal(); Future get database async { if (_database != null) return _database!; _database = await _initDb(); return _database!; } Future _initDb() async { String path = join(await getDatabasesPath(), 'chat_app.db'); return await openDatabase( path, version: 7, onCreate: (db, version) async { await db.execute(''' CREATE TABLE messages( id INTEGER PRIMARY KEY, sender_id INTEGER, receiver_id INTEGER, content TEXT, timestamp TEXT, delivered_at TEXT, read_at TEXT, reply_to_id INTEGER, reply_to_text TEXT, edited_at TEXT, message_type TEXT DEFAULT 'text', file_id TEXT, encrypted_key TEXT ) '''); }, onUpgrade: (db, oldVersion, newVersion) async { if (oldVersion < 2) { await db.execute('ALTER TABLE messages ADD COLUMN delivered_at TEXT'); await db.execute('ALTER TABLE messages ADD COLUMN read_at TEXT'); } if (oldVersion < 3) { await db.execute( 'ALTER TABLE messages ADD COLUMN reply_to_id INTEGER', ); await db.execute( 'ALTER TABLE messages ADD COLUMN reply_to_text TEXT', ); } if (oldVersion < 4) { await db.execute('ALTER TABLE messages ADD COLUMN edited_at TEXT'); } if (oldVersion < 5) { try { await db.execute( 'ALTER TABLE messages ADD COLUMN message_type TEXT', ); } catch (e) { print('message_type column already exists: $e'); } try { await db.execute('ALTER TABLE messages ADD COLUMN file_id TEXT'); } catch (e) { print('file_id column already exists: $e'); } } if (oldVersion < 6) { try { await db.execute( 'ALTER TABLE messages ADD COLUMN encrypted_key TEXT', ); } catch (e) { print('encrypted_key column already exists: $e'); } } if (oldVersion < 7) { try { await db.execute( 'ALTER TABLE messages ADD COLUMN local_file_bytes BLOB', ); } catch (e) { print('local_file_bytes column already exists: $e'); } } }, ); } Future clearDatabase() async { final db = await database; await db.delete('messages'); } Future saveMessages(List messages) async { final db = await database; final List incomingIds = messages.map((msg) { return (msg is MessageModel) ? msg.id! : (msg['id'] as int); }).toList(); Batch batch = db.batch(); if (incomingIds.isNotEmpty) { batch.delete('messages', where: 'id NOT IN (${incomingIds.join(',')})'); } for (var msg in messages) { if (msg is MessageModel) { batch.insert('messages', { 'id': msg.id, 'sender_id': msg.senderId, 'receiver_id': msg.receiverId, 'content': msg.text, 'timestamp': msg.createdAt.toIso8601String(), 'delivered_at': null, 'read_at': null, 'reply_to_id': msg.replyToId, 'reply_to_text': msg.replyToText, 'edited_at': msg.editedAt?.toIso8601String(), 'message_type': msg.messageType == MessageType.image ? 'image' : 'text', 'file_id': msg.fileId, 'encrypted_key': msg.encryptedFileKey, 'local_file_bytes': msg.localFileBytes, }, conflictAlgorithm: ConflictAlgorithm.replace); } else { // Если это Map из API batch.insert('messages', { 'id': msg['id'], 'sender_id': msg['sender_id'], 'receiver_id': msg['receiver_id'], // Убедись, что ключ совпадает с API 'content': msg['content'], 'timestamp': msg['timestamp'], 'delivered_at': msg['delivered_at'], 'read_at': msg['read_at'], 'reply_to_id': msg['reply_to_id'], 'reply_to_text': msg['reply_to_text'], 'edited_at': msg['edited_at'], 'message_type': msg['message_type'] ?? 'text', 'file_id': msg['file_id'], 'encrypted_key': msg['encrypted_key'], }, conflictAlgorithm: ConflictAlgorithm.replace); } } await batch.commit(noResult: true); } // Получение сообщений конкретного чата Future>> getChatHistory( int contactId, int myId, ) async { final db = await database; return await db.query( 'messages', where: '(sender_id = ? AND receiver_id = ?) OR (sender_id = ? AND receiver_id = ?)', whereArgs: [contactId, myId, myId, contactId], orderBy: 'timestamp ASC', ); } Future deleteChatHistory(int contactId, int myId) async { final db = await database; return await db.delete( 'messages', where: '(sender_id = ? AND receiver_id = ?) OR (sender_id = ? AND receiver_id = ?)', whereArgs: [contactId, myId, myId, contactId], ); } Future?> getLastMessage(int contactId, int myId) async { final db = await database; final rows = await db.query( 'messages', columns: ['sender_id', 'receiver_id', 'content', 'timestamp'], where: '(sender_id = ? AND receiver_id = ?) OR (sender_id = ? AND receiver_id = ?)', whereArgs: [contactId, myId, myId, contactId], orderBy: 'timestamp DESC', limit: 1, ); if (rows.isEmpty) return null; return rows.first; } Future updateDeliveredAt(int messageId, DateTime deliveredAt) async { final db = await database; await db.update( 'messages', {'delivered_at': deliveredAt.toIso8601String()}, where: 'id = ?', whereArgs: [messageId], ); } Future updateReadAt(int messageId, DateTime readAt) async { final db = await database; await db.update( 'messages', {'read_at': readAt.toIso8601String()}, where: 'id = ?', whereArgs: [messageId], ); } Future updateMessageLocalFileBytes( int messageId, Uint8List localFileBytes, ) async { final db = await database; await db.update( 'messages', {'local_file_bytes': localFileBytes}, where: 'id = ?', whereArgs: [messageId], ); } Future updateMessageContent( int messageId, String content, DateTime? editedAt, ) async { final db = await database; await db.update( 'messages', {'content': content, 'edited_at': editedAt?.toIso8601String()}, where: 'id = ?', whereArgs: [messageId], ); } Future deleteMessage(int messageId) async { final db = await database; await db.delete('messages', where: 'id = ?', whereArgs: [messageId]); } }