import 'dart:typed_data'; 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/group.dart'; import 'package:comunic/models/group_membership.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) : assert(status != null); } /// 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 Future _simpleMembershipRequest(int groupID, String uri, {Map args}) async => (await (APIRequest(uri: uri, needLogin: true) ..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 Future sendRequest(int groupID) async => _simpleMembershipRequest(groupID, "groups/send_request"); /// Respond to a group membership invitation 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]) .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(); /// Turn an API entry into a group object 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"]], 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"], ); /// 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"]], ); }