import 'dart:typed_data'; import 'package:comunic/helpers/conversations_helper.dart'; import 'package:comunic/lists/group_members_list.dart'; import 'package:comunic/lists/groups_list.dart'; import 'package:comunic/models/advanced_group_info.dart'; import 'package:comunic/models/api_request.dart'; import 'package:comunic/models/conversation.dart'; import 'package:comunic/models/group.dart'; import 'package:comunic/models/group_membership.dart'; import 'package:comunic/models/new_group_conversation.dart'; import 'package:comunic/utils/api_utils.dart'; import 'package:comunic/utils/map_utils.dart'; /// Groups helper /// /// @author Pierre HUBERT const APIGroupsMembershipLevelsMap = { "administrator": GroupMembershipLevel.ADMINISTRATOR, "moderator": GroupMembershipLevel.MODERATOR, "member": GroupMembershipLevel.MEMBER, "invited": GroupMembershipLevel.INVITED, "pending": GroupMembershipLevel.PENDING, "visitor": GroupMembershipLevel.VISITOR }; const _APIGroupsVisibilityLevelsMap = { "open": GroupVisibilityLevel.OPEN, "private": GroupVisibilityLevel.PRIVATE, "secrete": GroupVisibilityLevel.SECRETE }; const _APIGroupsRegistrationLevelsMap = { "open": GroupRegistrationLevel.OPEN, "moderated": GroupRegistrationLevel.MODERATED, "closed": GroupRegistrationLevel.CLOSED }; const _APIGroupsPostsCreationLevelsMap = { "moderators": GroupPostCreationLevel.MODERATORS, "members": GroupPostCreationLevel.MEMBERS }; final _groupsListCache = GroupsList(); /// Callback for getting advanced user information enum GetAdvancedInfoStatus { SUCCESS, ACCESS_DENIED } class GetAdvancedInfoResult { final GetAdvancedInfoStatus status; final AdvancedGroupInfo? info; GetAdvancedInfoResult(this.status, this.info); } /// Groups helper class GroupsHelper { /// Download a list of groups information from the server Future _downloadList(Set groups) async { final response = await APIRequest( uri: "groups/get_multiple_info", needLogin: true, args: {"list": groups.join(",")}, ).exec(); if (response.code != 200) return null; final list = GroupsList(); response .getObject() .forEach((k, d) => list[int.parse(k)] = getGroupFromAPI(d)); return list; } /// Get a list of groups from the server. In case of error, this method throws /// an exception Future getListOrThrow(Set groups, {bool force = false}) async { final list = await getList(groups, force: force); if (list == null) throw Exception("Could not get the list of groups!"); return list; } /// Get a list of groups from the server Future getList(Set groups, {bool force = false}) async { final list = GroupsList(); // Check which groups information to download final toDownload = Set(); groups.forEach((groupID) { if (!force && _groupsListCache.containsKey(groupID)) list[groupID] = _groupsListCache[groupID]; else toDownload.add(groupID); }); // Download required groups information if (toDownload.length > 0) { final downloaded = await _downloadList(toDownload); if (downloaded == null) return null; list.addAll(downloaded); _groupsListCache.addAll(downloaded); } return list; } /// Get information about a single group /// /// Throws in case of failure Future getSingle(int groupID, {bool force = false}) async { return (await getListOrThrow(Set()..add(groupID), force: force)) .values .first; } /// Get the list of groups of a user Future> getListUser() async => (await APIRequest(uri: "groups/get_my_list", needLogin: true).exec()) .assertOk() .getArray()! .map((f) => cast(f)) .toSet(); /// Create a new group /// /// Throws in case of failure static Future create(String name) async { final result = await APIRequest.withLogin("groups/create") .addString("name", name) .execWithThrow(); return result.getObject()["id"]; } /// Perform a simple membership request static Future _simpleMembershipRequest(int groupID, String uri, {Map? args}) async => (await (APIRequest.withLogin(uri) ..addInt("id", groupID) ..addArgs(args == null ? Map() : args)) .exec()) .isOK; /// Remove group membership Future removeMembership(int groupID) async => _simpleMembershipRequest(groupID, "groups/remove_membership"); /// Cancel membership request Future cancelRequest(int groupID) async => _simpleMembershipRequest(groupID, "groups/cancel_request"); /// Send a new membership request static Future sendRequest(int groupID) async => _simpleMembershipRequest(groupID, "groups/send_request"); /// Respond to a group membership invitation static Future respondInvitation(int groupID, bool accept) async => _simpleMembershipRequest(groupID, "groups/respond_invitation", args: { "accept": accept ? "true" : "false", }); /// Update group following status Future setFollowing(int groupID, bool follow) async => (await (APIRequest(uri: "groups/set_following", needLogin: true) ..addInt("groupID", groupID) ..addBool("follow", follow)) .exec()) .isOK; /// Get advanced information about the user Future getAdvancedInfo(int? groupID) async { // Get advanced information about the user final result = await (APIRequest(uri: "groups/get_advanced_info", needLogin: true) ..addInt("id", groupID)) .exec(); switch (result.code) { case 401: return GetAdvancedInfoResult(GetAdvancedInfoStatus.ACCESS_DENIED, null); case 200: return GetAdvancedInfoResult(GetAdvancedInfoStatus.SUCCESS, _getAdvancedGroupInfoFromAPI(result.getObject())); default: throw Exception("Could not get advanced group information!"); } } /// Get group settings /// /// This function is currently a kind of alias, but it might /// change in the future /// /// Throws in case of error Future getSettings(int groupID) async { final groupInfo = await getAdvancedInfo(groupID); if (groupInfo.status != GetAdvancedInfoStatus.SUCCESS) throw Exception("Could not get group information!"); return groupInfo.info; } /// Check the availability of a virtual directory /// /// Throws in case of error static Future checkVirtualDirectoryAvailability( int groupID, String dir) async => await APIRequest(uri: "groups/checkVirtualDirectory", needLogin: true) .addInt("groupID", groupID) .addString("directory", dir) .execWithThrow(); /// Update (set) new group settings /// /// Throws in case of error static Future setSettings(AdvancedGroupInfo settings) async { await APIRequest(uri: "groups/set_settings", needLogin: true) .addInt("id", settings.id) .addString("name", settings.name) .addString("virtual_directory", settings.virtualDirectory) .addString("visibility", invertMap(_APIGroupsVisibilityLevelsMap)[settings.visibilityLevel]) .addString( "registration_level", invertMap( _APIGroupsRegistrationLevelsMap)[settings.registrationLevel]) .addString( "posts_level", invertMap( _APIGroupsPostsCreationLevelsMap)[settings.postCreationLevel]) .addBool("is_members_list_public", settings.isMembersListPublic!) .addString("description", settings.description) .addString("url", settings.url) .execWithThrow(); } /// Upload a new logo /// /// Throws in case of failure static Future uploadNewLogo(int groupID, Uint8List? bytes) async => await APIRequest(uri: "groups/upload_logo", needLogin: true) .addInt("id", groupID) .addBytesFile("logo", BytesFile("logo.png", bytes)) .execWithFilesAndThrow(); /// Delete group logo /// /// Throws in case of error static Future deleteLogo(int groupID) async => await APIRequest(uri: "groups/delete_logo", needLogin: true) .addInt("id", groupID) .execWithThrow(); /// Delete a group /// /// Throws in case of error static Future deleteGroup(int groupID, String password) async => await APIRequest(uri: "groups/delete", needLogin: true) .addInt("groupID", groupID) .addString("password", password) .execWithThrow(); /// Get the list of members of the group /// /// Throws in case of failure static Future getMembersList(int groupID) async => GroupMembersList() ..addAll((await APIRequest(uri: "groups/get_members", needLogin: true) .addInt("id", groupID) .execWithThrow()) .getArray()! .map((f) => _apiToGroupMembership(f)) .toList()); /// Invite a user to join a group /// /// Throws an exception in case of failure static Future sendInvitation(int groupID, int userID) async => APIRequest.withLogin("groups/invite") .addInt("group_id", groupID) .addInt("userID", userID) .execWithThrow(); /// Cancel a group membership invitation /// /// Throws an exception in case of failure static Future cancelInvitation(int groupID, int? userID) async => await APIRequest.withLogin("groups/cancel_invitation") .addInt("groupID", groupID) .addInt("userID", userID) .execWithThrow(); /// Respond to a group membership request /// /// Throws an exception in case of failure static Future respondRequest( int groupID, int? userID, bool accept) async => await APIRequest.withLogin("groups/respond_request") .addInt("groupID", groupID) .addInt("userID", userID) .addBool("accept", accept) .execWithThrow(); /// Remove a member from a group /// /// Throws an exception in case of failure static Future removeMemberFromGroup(int groupID, int userID) async => APIRequest.withLogin("groups/delete_member") .addInt("groupID", groupID) .addInt("userID", userID) .execWithThrow(); /// Change the membership level of a member of a group /// /// Throws an exception in case of failure static Future setNewLevel( int groupID, int userID, GroupMembershipLevel level) async => await APIRequest.withLogin("groups/update_membership_level") .addInt("groupID", groupID) .addInt("userID", userID) .addString("level", invertMap(APIGroupsMembershipLevelsMap)[level]) .execWithThrow(); /// Create a new group conversation /// /// Throws in case of failure static Future createGroupConversation( NewGroupConversation conv) async => await APIRequest.withLogin("groups/create_conversation") .addInt("group_id", conv.groupID) .addString( "min_membership_level", APIGroupsMembershipLevelsMap.entries .firstWhere((e) => e.value == conv.minMembershipLevel) .key) .addString("name", conv.name) .execWithThrow(); /// Set new conversation visibility level /// /// Throws in case of failure static Future setConversationVisibility( int? convID, GroupMembershipLevel? newLevel) async => await APIRequest.withLogin("groups/set_conversation_visibility") .addInt("conv_id", convID) .addString( "min_membership_level", APIGroupsMembershipLevelsMap.entries .firstWhere((e) => e.value == newLevel) .key) .execWithThrow(); /// Delete a group's conversation /// /// Throws in case of failure static Future deleteConversation(int? convID) async => await APIRequest.withLogin("groups/delete_conversation") .addInt("conv_id", convID) .execWithThrow(); /// Turn an API entry into a group object static Group getGroupFromAPI(Map map) { return Group( id: map["id"], name: map["name"], iconURL: map["icon_url"], numberMembers: map["number_members"], membershipLevel: APIGroupsMembershipLevelsMap[map["membership"]]!, visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]]!, registrationLevel: _APIGroupsRegistrationLevelsMap[map["registration_level"]]!, postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]]!, virtualDirectory: nullToEmpty(map["virtual_directory"]), following: map["following"]); } /// Get advanced group information AdvancedGroupInfo _getAdvancedGroupInfoFromAPI(Map map) => AdvancedGroupInfo( id: map["id"], name: map["name"], iconURL: map["icon_url"], numberMembers: map["number_members"], membershipLevel: APIGroupsMembershipLevelsMap[map["membership"]]!, visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]]!, registrationLevel: _APIGroupsRegistrationLevelsMap[map["registration_level"]]!, postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]]!, isMembersListPublic: map["is_members_list_public"], virtualDirectory: nullToEmpty(map["virtual_directory"]), following: map["following"], timeCreate: map["time_create"], description: nullToEmpty(map["description"]), url: nullToEmpty(map["url"]), likes: map["number_likes"], userLike: map["is_liking"], conversations: map["conversations"] .map((s) => ConversationsHelper.apiToConversation(s)) .cast() .toList(), isForezGroup: map["is_forez_group"], ); /// Create [GroupMembership] object from API entry static GroupMembership _apiToGroupMembership(Map row) => GroupMembership( userID: row["user_id"], groupID: row["group_id"], timeCreate: row["time_create"], level: APIGroupsMembershipLevelsMap[row["level"]]!, ); }