- import 'dart:convert';
- import 'dart:io';
- import 'dart:typed_data';
-
- import 'package:Capsule/models/messages.dart';
- import 'package:Capsule/models/text_messages.dart';
- import 'package:mime/mime.dart';
- import 'package:pointycastle/export.dart';
- import 'package:sqflite/sqflite.dart';
- import 'package:uuid/uuid.dart';
-
- import '../utils/storage/write_file.dart';
- import '/models/conversation_users.dart';
- import '/models/friends.dart';
- import '/models/my_profile.dart';
- import '/utils/encryption/aes_helper.dart';
- import '/utils/encryption/crypto_utils.dart';
- import '/utils/storage/database.dart';
- import '/utils/strings.dart';
-
- Future<Conversation> createConversation(String title, List<Friend> friends, bool twoUser) async {
- final db = await getDatabaseConnection();
-
- MyProfile profile = await MyProfile.getProfile();
-
- var uuid = const Uuid();
- final String conversationId = uuid.v4();
-
- Uint8List symmetricKey = AesHelper.deriveKey(generateRandomString(32));
-
- Conversation conversation = Conversation(
- id: conversationId,
- userId: profile.id,
- symmetricKey: base64.encode(symmetricKey),
- admin: true,
- name: title,
- twoUser: twoUser,
- status: ConversationStatus.pending,
- isRead: true,
- messageExpiryDefault: 'no_expiry'
- );
-
- await db.insert(
- 'conversations',
- conversation.toMap(),
- conflictAlgorithm: ConflictAlgorithm.replace,
- );
-
- await db.insert(
- 'conversation_users',
- ConversationUser(
- id: uuid.v4(),
- userId: profile.id,
- conversationId: conversationId,
- username: profile.username,
- associationKey: uuid.v4(),
- publicKey: profile.publicKey!,
- admin: true,
- ).toMap(),
- conflictAlgorithm: ConflictAlgorithm.fail,
- );
-
- for (Friend friend in friends) {
- await db.insert(
- 'conversation_users',
- ConversationUser(
- id: uuid.v4(),
- userId: friend.friendId,
- conversationId: conversationId,
- username: friend.username,
- associationKey: uuid.v4(),
- publicKey: friend.publicKey,
- admin: twoUser ? true : false,
- ).toMap(),
- conflictAlgorithm: ConflictAlgorithm.replace,
- );
- }
-
- if (twoUser) {
- List<Map<String, dynamic>> maps = await db.query(
- 'conversation_users',
- where: 'conversation_id = ? AND user_id != ?',
- whereArgs: [ conversation.id, profile.id ],
- );
-
- if (maps.length != 1) {
- throw ArgumentError('Invalid user id');
- }
-
- conversation.name = maps[0]['username'];
-
- await db.insert(
- 'conversations',
- conversation.toMap(),
- conflictAlgorithm: ConflictAlgorithm.replace,
- );
- }
-
- return conversation;
- }
-
-
- Future<Conversation> addUsersToConversation(Conversation conversation, List<Friend> friends) async {
- final db = await getDatabaseConnection();
-
- var uuid = const Uuid();
-
- for (Friend friend in friends) {
- await db.insert(
- 'conversation_users',
- ConversationUser(
- id: uuid.v4(),
- userId: friend.friendId,
- conversationId: conversation.id,
- username: friend.username,
- associationKey: uuid.v4(),
- publicKey: friend.publicKey,
- admin: false,
- ).toMap(),
- conflictAlgorithm: ConflictAlgorithm.replace,
- );
- }
-
- return conversation;
- }
-
- Conversation findConversationByDetailId(List<Conversation> conversations, String id) {
- for (var conversation in conversations) {
- if (conversation.id == id) {
- return conversation;
- }
- }
- // Or return `null`.
- throw ArgumentError.value(id, 'id', 'No element with that id');
- }
-
- Future<Conversation> getConversationById(String id) async {
- final db = await getDatabaseConnection();
-
- final List<Map<String, dynamic>> maps = await db.query(
- 'conversations',
- where: 'id = ?',
- whereArgs: [id],
- );
-
- if (maps.length != 1) {
- throw ArgumentError('Invalid user id');
- }
-
- File? file;
- if (maps[0]['file'] != null && maps[0]['file'] != '') {
- file = File(maps[0]['file']);
- }
-
- return Conversation(
- id: maps[0]['id'],
- userId: maps[0]['user_id'],
- symmetricKey: maps[0]['symmetric_key'],
- admin: maps[0]['admin'] == 1,
- name: maps[0]['name'],
- twoUser: maps[0]['two_user'] == 1,
- status: ConversationStatus.values[maps[0]['status']],
- isRead: maps[0]['is_read'] == 1,
- icon: file,
- messageExpiryDefault: maps[0]['message_expiry'],
- );
- }
-
- // A method that retrieves all the dogs from the dogs table.
- Future<List<Conversation>> getConversations() async {
- final db = await getDatabaseConnection();
-
- final List<Map<String, dynamic>> maps = await db.query(
- 'conversations',
- orderBy: 'name',
- );
-
- return List.generate(maps.length, (i) {
-
- File? file;
- if (maps[i]['file'] != null && maps[i]['file'] != '') {
- file = File(maps[i]['file']);
- }
-
- return Conversation(
- id: maps[i]['id'],
- userId: maps[i]['user_id'],
- symmetricKey: maps[i]['symmetric_key'],
- admin: maps[i]['admin'] == 1,
- name: maps[i]['name'],
- twoUser: maps[i]['two_user'] == 1,
- status: ConversationStatus.values[maps[i]['status']],
- isRead: maps[i]['is_read'] == 1,
- icon: file,
- messageExpiryDefault: maps[i]['message_expiry'] ?? 'no_expiry',
- );
- });
- }
-
- Future<Conversation?> getTwoUserConversation(String userId) async {
- final db = await getDatabaseConnection();
-
- MyProfile profile = await MyProfile.getProfile();
-
- final List<Map<String, dynamic>> maps = await db.rawQuery(
- '''
- SELECT conversations.* FROM conversations
- LEFT JOIN conversation_users ON conversation_users.conversation_id = conversations.id
- WHERE conversation_users.user_id = ?
- AND conversation_users.user_id != ?
- AND conversations.two_user = 1
- ''',
- [ userId, profile.id ],
- );
-
- if (maps.length != 1) {
- return null;
- }
-
- return Conversation(
- id: maps[0]['id'],
- userId: maps[0]['user_id'],
- symmetricKey: maps[0]['symmetric_key'],
- admin: maps[0]['admin'] == 1,
- name: maps[0]['name'],
- twoUser: maps[0]['two_user'] == 1,
- status: ConversationStatus.values[maps[0]['status']],
- isRead: maps[0]['is_read'] == 1,
- messageExpiryDefault: maps[0]['message_expiry'],
- );
-
- }
-
- class Conversation {
- String id;
- String userId;
- String symmetricKey;
- bool admin;
- String name;
- bool twoUser;
- ConversationStatus status;
- bool isRead;
- String messageExpiryDefault = 'no_expiry';
- File? icon;
-
- Conversation({
- required this.id,
- required this.userId,
- required this.symmetricKey,
- required this.admin,
- required this.name,
- required this.twoUser,
- required this.status,
- required this.isRead,
- required this.messageExpiryDefault,
- this.icon,
- });
-
-
- factory Conversation.fromJson(Map<String, dynamic> json, RSAPrivateKey privKey) {
- var symmetricKeyDecrypted = CryptoUtils.rsaDecrypt(
- base64.decode(json['symmetric_key']),
- privKey,
- );
-
- var id = AesHelper.aesDecrypt(
- symmetricKeyDecrypted,
- base64.decode(json['conversation_detail_id']),
- );
-
- var admin = AesHelper.aesDecrypt(
- symmetricKeyDecrypted,
- base64.decode(json['admin']),
- );
-
- return Conversation(
- id: id,
- userId: json['user_id'],
- symmetricKey: base64.encode(symmetricKeyDecrypted),
- admin: admin == 'true',
- name: 'Unknown',
- twoUser: false,
- status: ConversationStatus.complete,
- isRead: true,
- messageExpiryDefault: 'no_expiry',
- );
- }
-
- Future<Map<String, dynamic>> payloadJson({ bool includeUsers = true }) async {
- MyProfile profile = await MyProfile.getProfile();
-
- var symKey = base64.decode(symmetricKey);
-
- if (!includeUsers) {
- return {
- 'id': id,
- 'name': AesHelper.aesEncrypt(symKey, Uint8List.fromList(name.codeUnits)),
- 'users': await getEncryptedConversationUsers(this, symKey),
- };
- }
-
- List<ConversationUser> users = await getConversationUsers(this);
-
- List<Object> userConversations = [];
-
- for (ConversationUser user in users) {
-
- RSAPublicKey pubKey = profile.publicKey!;
- String newId = id;
-
- if (profile.id != user.userId) {
- Friend friend = await getFriendByFriendId(user.userId);
- pubKey = friend.publicKey;
- newId = (const Uuid()).v4();
- }
-
- userConversations.add({
- 'id': newId,
- 'user_id': user.userId,
- 'conversation_detail_id': AesHelper.aesEncrypt(symKey, Uint8List.fromList(id.codeUnits)),
- 'admin': AesHelper.aesEncrypt(symKey, Uint8List.fromList((user.admin ? 'true' : 'false').codeUnits)),
- 'symmetric_key': base64.encode(CryptoUtils.rsaEncrypt(symKey, pubKey)),
- });
- }
-
- Map<String, dynamic> returnData = {
- 'id': id,
- 'name': AesHelper.aesEncrypt(symKey, Uint8List.fromList(name.codeUnits)),
- 'users': await getEncryptedConversationUsers(this, symKey),
- 'two_user': AesHelper.aesEncrypt(symKey, Uint8List.fromList((twoUser ? 'true' : 'false').codeUnits)),
- 'message_expiry': messageExpiryDefault,
- 'user_conversations': userConversations,
- };
-
- if (icon != null) {
- returnData['attachment'] = {
- 'data': AesHelper.aesEncrypt(symmetricKey, Uint8List.fromList(icon!.readAsBytesSync())),
- 'mimetype': lookupMimeType(icon!.path),
- 'extension': getExtension(icon!.path),
- };
- }
-
- return returnData;
- }
-
- Map<String, dynamic> payloadImageJson() {
- if (icon == null) {
- return {};
- }
-
- return {
- 'data': AesHelper.aesEncrypt(symmetricKey, Uint8List.fromList(icon!.readAsBytesSync())),
- 'mimetype': lookupMimeType(icon!.path),
- 'extension': getExtension(icon!.path),
- };
- }
-
- Map<String, dynamic> toMap() {
- return {
- 'id': id,
- 'user_id': userId,
- 'symmetric_key': symmetricKey,
- 'admin': admin ? 1 : 0,
- 'name': name,
- 'two_user': twoUser ? 1 : 0,
- 'status': status.index,
- 'is_read': isRead ? 1 : 0,
- 'file': icon != null ? icon!.path : null,
- 'message_expiry': messageExpiryDefault,
- };
- }
-
- @override
- String toString() {
- return '''
-
-
- id: $id
- userId: $userId
- name: $name
- admin: $admin''';
- }
-
- Future<Message?> getRecentMessage() async {
- final db = await getDatabaseConnection();
-
- final List<Map<String, dynamic>> maps = await db.rawQuery(
- '''
- SELECT * FROM messages WHERE association_key IN (
- SELECT association_key FROM conversation_users WHERE conversation_id = ?
- )
- ORDER BY created_at DESC
- LIMIT 1;
- ''',
- [ id ],
- );
-
- if (maps.isEmpty) {
- return null;
- }
-
- return TextMessage(
- id: maps[0]['id'],
- symmetricKey: maps[0]['symmetric_key'],
- userSymmetricKey: maps[0]['user_symmetric_key'],
- text: maps[0]['data'] ?? 'Image',
- senderId: maps[0]['sender_id'],
- senderUsername: maps[0]['sender_username'],
- associationKey: maps[0]['association_key'],
- createdAt: maps[0]['created_at'],
- failedToSend: maps[0]['failed_to_send'] == 1,
- );
- }
- }
-
-
- enum ConversationStatus {
- complete,
- pending,
- error,
- }
|