|
@ -2,11 +2,12 @@ import 'dart:convert'; |
|
|
import 'dart:io'; |
|
|
import 'dart:io'; |
|
|
import 'dart:typed_data'; |
|
|
import 'dart:typed_data'; |
|
|
|
|
|
|
|
|
|
|
|
import 'package:Envelope/database/repositories/friends_repository.dart'; |
|
|
import 'package:mime/mime.dart'; |
|
|
import 'package:mime/mime.dart'; |
|
|
import 'package:pointycastle/export.dart'; |
|
|
import 'package:pointycastle/export.dart'; |
|
|
import 'package:sqflite/sqflite.dart'; |
|
|
|
|
|
import 'package:uuid/uuid.dart'; |
|
|
import 'package:uuid/uuid.dart'; |
|
|
|
|
|
|
|
|
|
|
|
import '/database/repositories/conversation_users_repository.dart'; |
|
|
import '/database/models/messages.dart'; |
|
|
import '/database/models/messages.dart'; |
|
|
import '/database/models/text_messages.dart'; |
|
|
import '/database/models/text_messages.dart'; |
|
|
import '/database/models/conversation_users.dart'; |
|
|
import '/database/models/conversation_users.dart'; |
|
@ -15,198 +16,12 @@ import '/database/models/my_profile.dart'; |
|
|
import '/utils/encryption/aes_helper.dart'; |
|
|
import '/utils/encryption/aes_helper.dart'; |
|
|
import '/utils/encryption/crypto_utils.dart'; |
|
|
import '/utils/encryption/crypto_utils.dart'; |
|
|
import '/utils/storage/database.dart'; |
|
|
import '/utils/storage/database.dart'; |
|
|
import '/utils/strings.dart'; |
|
|
|
|
|
import '/utils/storage/write_file.dart'; |
|
|
import '/utils/storage/write_file.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', |
|
|
|
|
|
adminAddMembers: true, |
|
|
|
|
|
adminEditInfo: true, |
|
|
|
|
|
adminSendMessages: false, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
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> 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'], |
|
|
|
|
|
adminAddMembers: maps[0]['admin_add_members'] == 1, |
|
|
|
|
|
adminEditInfo: maps[0]['admin_edit_info'] == 1, |
|
|
|
|
|
adminSendMessages: maps[0]['admin_send_messages'] == 1, |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 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', |
|
|
|
|
|
adminAddMembers: maps[i]['admin_add_members'] == 1, |
|
|
|
|
|
adminEditInfo: maps[i]['admin_edit_info'] == 1, |
|
|
|
|
|
adminSendMessages: maps[i]['admin_send_messages'] == 1, |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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'], |
|
|
|
|
|
adminAddMembers: maps[0]['admin_add_members'] == 1, |
|
|
|
|
|
adminEditInfo: maps[0]['admin_edit_info'] == 1, |
|
|
|
|
|
adminSendMessages: maps[0]['admin_send_messages'] == 1, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum ConversationStatus { |
|
|
|
|
|
complete, |
|
|
|
|
|
pending, |
|
|
|
|
|
error, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class Conversation { |
|
|
class Conversation { |
|
@ -282,11 +97,11 @@ class Conversation { |
|
|
return { |
|
|
return { |
|
|
'id': id, |
|
|
'id': id, |
|
|
'name': AesHelper.aesEncrypt(symKey, Uint8List.fromList(name.codeUnits)), |
|
|
'name': AesHelper.aesEncrypt(symKey, Uint8List.fromList(name.codeUnits)), |
|
|
'users': await getEncryptedConversationUsers(this, symKey), |
|
|
|
|
|
|
|
|
'users': await ConversationUsersRepository.getEncryptedConversationUsers(this, symKey), |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
List<ConversationUser> users = await getConversationUsers(this); |
|
|
|
|
|
|
|
|
List<ConversationUser> users = await ConversationUsersRepository.getConversationUsers(this); |
|
|
|
|
|
|
|
|
List<Object> userConversations = []; |
|
|
List<Object> userConversations = []; |
|
|
|
|
|
|
|
@ -296,7 +111,7 @@ class Conversation { |
|
|
String newId = id; |
|
|
String newId = id; |
|
|
|
|
|
|
|
|
if (profile.id != user.userId) { |
|
|
if (profile.id != user.userId) { |
|
|
Friend friend = await getFriendByFriendId(user.userId); |
|
|
|
|
|
|
|
|
Friend friend = await FriendsRepository.getFriendByFriendId(user.userId); |
|
|
pubKey = friend.publicKey; |
|
|
pubKey = friend.publicKey; |
|
|
newId = (const Uuid()).v4(); |
|
|
newId = (const Uuid()).v4(); |
|
|
} |
|
|
} |
|
@ -313,7 +128,7 @@ class Conversation { |
|
|
Map<String, dynamic> returnData = { |
|
|
Map<String, dynamic> returnData = { |
|
|
'id': id, |
|
|
'id': id, |
|
|
'name': AesHelper.aesEncrypt(symKey, Uint8List.fromList(name.codeUnits)), |
|
|
'name': AesHelper.aesEncrypt(symKey, Uint8List.fromList(name.codeUnits)), |
|
|
'users': await getEncryptedConversationUsers(this, symKey), |
|
|
|
|
|
|
|
|
'users': await ConversationUsersRepository.getEncryptedConversationUsers(this, symKey), |
|
|
'two_user': AesHelper.aesEncrypt(symKey, Uint8List.fromList((twoUser ? 'true' : 'false').codeUnits)), |
|
|
'two_user': AesHelper.aesEncrypt(symKey, Uint8List.fromList((twoUser ? 'true' : 'false').codeUnits)), |
|
|
'message_expiry': messageExpiryDefault, |
|
|
'message_expiry': messageExpiryDefault, |
|
|
'admin_add_members': AesHelper.aesEncrypt(symKey, Uint8List.fromList((adminAddMembers ? 'true' : 'false').codeUnits)), |
|
|
'admin_add_members': AesHelper.aesEncrypt(symKey, Uint8List.fromList((adminAddMembers ? 'true' : 'false').codeUnits)), |
|
@ -405,10 +220,3 @@ class Conversation { |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum ConversationStatus { |
|
|
|
|
|
complete, |
|
|
|
|
|
pending, |
|
|
|
|
|
error, |
|
|
|
|
|
} |
|
|
|