import 'package:comunic/helpers/database/conversation_messages_database_helper.dart'; import 'package:comunic/helpers/database/conversations_database_helper.dart'; import 'package:comunic/helpers/users_helper.dart'; import 'package:comunic/helpers/websocket_helper.dart'; import 'package:comunic/lists/conversation_messages_list.dart'; import 'package:comunic/lists/conversations_list.dart'; import 'package:comunic/lists/users_list.dart'; import 'package:comunic/models/api_request.dart'; import 'package:comunic/models/api_response.dart'; import 'package:comunic/models/conversation.dart'; import 'package:comunic/models/conversation_message.dart'; import 'package:comunic/models/displayed_content.dart'; import 'package:comunic/models/new_conversation_message.dart'; import 'package:comunic/utils/account_utils.dart'; import 'package:meta/meta.dart'; /// Conversation helper /// /// @author Pierre HUBERT enum SendMessageResult { SUCCESS, MESSAGE_REJECTED, FAILED } class ConversationsHelper { static final _registeredConversations = Map(); final ConversationsDatabaseHelper _conversationsDatabaseHelper = ConversationsDatabaseHelper(); final ConversationMessagesDatabaseHelper _conversationMessagesDatabaseHelper = ConversationMessagesDatabaseHelper(); /// Create a new conversation /// /// Return the ID of the newly created conversation or -1 in case of failure Future createConversation(Conversation settings) async { final response = await APIRequest(uri: "conversations/create", needLogin: true, args: { "name": settings.hasName ? settings.name : "false", "follow": settings.following ? "true" : "false", "users": settings.members.join(",") }).exec(); if (response.code != 200) return -1; return response.getObject()["conversationID"]; } /// Update an existing conversation /// /// Returns a boolean depending of the success of the operation Future updateConversation(Conversation settings) async { final request = APIRequest(uri: "conversations/updateSettings", needLogin: true, args: { "conversationID": settings.id.toString(), "following": settings.following ? "true" : "false" }); // Update all conversation settings, if possible if (settings.isOwner) { request.addString("name", settings.hasName ? settings.name : "false"); request.addString("members", settings.members.join(",")); } final response = await request.exec(); if (response.code != 200) return false; //Delete old conversation entry from the database await _conversationsDatabaseHelper.delete(settings.id); // Success return true; } /// Delete a conversation specified by its [id] Future deleteConversation(int id) async { final response = await APIRequest( uri: "conversations/delete", needLogin: true, args: { "conversationID": id.toString(), }, ).exec(); return response.code == 200; } /// Download the list of conversations from the server Future downloadList() async { final response = await APIRequest(uri: "conversations/getList", needLogin: true).exec(); if (response.code != 200) return null; try { ConversationsList list = ConversationsList(); response.getArray().forEach((f) => list.add(_apiToConversation(f))); // Update the database await _conversationsDatabaseHelper.clearTable(); await _conversationsDatabaseHelper.insertAll(list); return list; } on Exception catch (e) { print(e.toString()); return null; } } /// Get the local list of conversations Future getCachedList() async { final list = await _conversationsDatabaseHelper.getAll(); list.sort(); return list; } /// Get information about a single conversation specified by its [id] Future _downloadSingle(int id) async { try { final response = await APIRequest( uri: "conversations/getInfoOne", needLogin: true, args: {"conversationID": id.toString()}).exec(); if (response.code != 200) return null; final conversation = _apiToConversation(response.getObject()); _conversationsDatabaseHelper.insertOrUpdate(conversation); return conversation; } on Exception catch (e) { print(e.toString()); print("Could not get information about a single conversation !"); return null; } } /// Get information about a single conversation. If [force] is set to false, /// cached version of the conversation will be used, else it will always get /// the information from the server Future getSingle(int id, {bool force = false}) async { if (force || !await _conversationsDatabaseHelper.has(id)) return await _downloadSingle(id); else return _conversationsDatabaseHelper.get(id); } /// Get information about a conversation. The method throws an [Exception] in /// case of failure /// /// Return value of this method is never null. Future getSingleOrThrow(int id, {bool force = false}) async { final conv = await this.getSingle(id, force: force); if (conv == null) throw Exception("Could not get information about the conversation!"); return conv; } /// Get the name of a [conversation]. This requires information /// about the users of this conversation static String getConversationName( Conversation conversation, UsersList users) { if (conversation.hasName) return conversation.name; String name = ""; int count = 0; for (int i = 0; i < 3 && i < conversation.members.length; i++) if (conversation.members[i] != userID()) { name += (count > 0 ? ", " : "") + users.getUser(conversation.members[i]).fullName; count++; } if (conversation.members.length > 3) name += ", ..."; return name; } /// Search and return a private conversation with a given [userID]. If such /// conversation does not exists, it is created if [allowCreate] is set to /// true Future getPrivate(int userID, {bool allowCreate = true}) async { final response = await APIRequest( uri: "conversations/getPrivate", needLogin: true, args: { "otherUser": userID.toString(), "allowCreate": allowCreate.toString() }, ).exec(); if (response.code != 200) return null; // Get and return conversation ID try { return int.parse(response.getObject()["conversationsID"][0].toString()); } catch (e) { e.toString(); return null; } } /// Asynchronously get the name of the conversation /// /// Unlike the synchronous method, this method does not need information /// about the members of the conversation /// /// Returns null in case of failure static Future getConversationNameAsync( Conversation conversation) async { if (conversation.hasName) return conversation.name; //Get information about the members of the conversation final members = await UsersHelper().getUsersInfo(conversation.members); if (members == null) return null; return ConversationsHelper.getConversationName(conversation, members); } /// Turn an API entry into a [Conversation] object Conversation _apiToConversation(Map map) { return Conversation( id: map["ID"], ownerID: map["ID_owner"], lastActive: map["last_active"], name: map["name"] == false ? null : map["name"], following: map["following"] == 1, sawLastMessage: map["saw_last_message"] == 1, members: List.from(map["members"]), callCapabilities: map["can_have_video_call"] ? CallCapabilities.VIDEO : (map["can_have_call"] ? CallCapabilities.AUDIO : CallCapabilities.NONE), isHavingCall: map["has_call_now"]); } /// Parse a list of messages given by the server Future _parseConversationMessageFromServer( int conversationID, APIResponse response) async { if (response.code != 200) return null; // Parse the response of the server ConversationMessagesList list = ConversationMessagesList(); response.getArray().forEach((f) { list.add( apiToConversationMessage(f), ); }); // Save messages in the cache _conversationMessagesDatabaseHelper.insertOrUpdateAll(list); return list; } /// Refresh the list of messages of a conversation /// /// Set [lastMessageID] to 0 to specify that we do not have any message of the /// conversation yet or another value else Future _downloadNewMessagesSingle( int conversationID, {int lastMessageID = 0}) async { // Execute the request on the server final response = await APIRequest( uri: "conversations/refresh_single", needLogin: true, args: { "conversationID": conversationID.toString(), "last_message_id": lastMessageID.toString() }).exec(); return await _parseConversationMessageFromServer(conversationID, response); } /// Get older messages for a given conversation from an online source Future getOlderMessages({ @required int conversationID, @required int oldestMessagesID, int limit = 15, }) async { // Perform the request online final response = await APIRequest( uri: "conversations/get_older_messages", needLogin: true, args: { "conversationID": conversationID.toString(), "oldest_message_id": oldestMessagesID.toString(), "limit": limit.toString() }).exec(); return await _parseConversationMessageFromServer(conversationID, response); } /// Get new messages for a given conversation /// /// If [lastMessageID] is set to 0 then we retrieve the last messages of /// the conversation. /// Otherwise [lastMessageID] contains the ID of the last known message Future getNewMessages( {@required int conversationID, int lastMessageID = 0, bool online = true}) async { if (online) return await _downloadNewMessagesSingle(conversationID, lastMessageID: lastMessageID); else return await _conversationMessagesDatabaseHelper .getAllMessagesConversations(conversationID, lastMessageID: lastMessageID); } /// Get a single conversation message from the local database /// /// Returns the message if found or null in case of failure Future getSingleMessageFromCache(int messageID) async { return await _conversationMessagesDatabaseHelper.get(messageID); } /// Send a new message to the server Future sendMessage(NewConversationMessage message) async { final request = APIRequest( uri: "conversations/sendMessage", needLogin: true, args: { "conversationID": message.conversationID.toString(), "message": message.hasMessage ? message.message : "" }, ); //Check for image if (message.hasImage) request.addFile("image", message.image); //Send the message APIResponse response; if (!message.hasImage) response = await request.exec(); else response = await request.execWithFiles(); if (response.code == 401) return SendMessageResult.MESSAGE_REJECTED; else if (response.code != 200) return SendMessageResult.FAILED; return SendMessageResult.SUCCESS; } /// Save / Update a message into the database Future saveMessage(ConversationMessage msg) async { await _conversationMessagesDatabaseHelper.insertOrUpdate(msg); } /// Remove a message from the database Future removeMessage(int msgID) async { await _conversationMessagesDatabaseHelper.delete(msgID); } /// Update a message content Future updateMessage(int id, String newContent) async { final response = await APIRequest( uri: "conversations/updateMessage", needLogin: true, args: {"messageID": id.toString(), "content": newContent}).exec(); if (response.code != 200) return false; return true; } /// Delete permanently a message specified by its [id] Future deleteMessage(int id) async { // Delete the message online final response = await APIRequest( uri: "conversations/deleteMessage", needLogin: true, args: {"messageID": id.toString()}).exec(); if (response.code != 200) return false; return true; } /// Register a conversation : ask the server to notify about updates to the /// conversation through WebSocket Future registerConversationEvents(int id) async { if (_registeredConversations.containsKey(id)) _registeredConversations[id]++; else { _registeredConversations[id] = 1; await ws("\$main/register_conv", {"convID": id}); } } /// Un-register to conversation update events Future unregisterConversationEvents(int id) async { if (!_registeredConversations.containsKey(id)) return; _registeredConversations[id]--; if (_registeredConversations[id] <= 0) { _registeredConversations.remove(id); await ws("\$main/unregister_conv", {"convID": id}); } } /// Turn an API response into a ConversationMessage object static ConversationMessage apiToConversationMessage( Map map, ) { return ConversationMessage( id: map["ID"], conversationID: map["convID"], userID: map["ID_user"], timeInsert: map["time_insert"], message: DisplayedString(map["message"]), imageURL: map["image_path"], ); } }