diff --git a/lib/helpers/conversations_helper.dart b/lib/helpers/conversations_helper.dart index 9a286b9..f042ba6 100644 --- a/lib/helpers/conversations_helper.dart +++ b/lib/helpers/conversations_helper.dart @@ -1,3 +1,4 @@ +import 'package:comunic/helpers/groups_helper.dart'; import 'package:comunic/helpers/serialization/conversation_message_serialization_helper.dart'; import 'package:comunic/helpers/serialization/conversations_serialization_helper.dart'; import 'package:comunic/helpers/users_helper.dart'; @@ -238,6 +239,8 @@ class ConversationsHelper { color: map["color"] == null ? null : HexColor(map["color"]), logoURL: map["logo"], groupID: map["group_id"], + groupMinMembershipLevel: + APIGroupsMembershipLevelsMap[map["group_min_membership_level"]], members: map["members"] .cast>() .map(apiToConversationMember) diff --git a/lib/helpers/groups_helper.dart b/lib/helpers/groups_helper.dart index 0bbd954..dd7b645 100644 --- a/lib/helpers/groups_helper.dart +++ b/lib/helpers/groups_helper.dart @@ -6,6 +6,7 @@ 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/models/new_group_conversation.dart'; import 'package:comunic/utils/api_utils.dart'; import 'package:comunic/utils/map_utils.dart'; @@ -13,7 +14,7 @@ import 'package:comunic/utils/map_utils.dart'; /// /// @author Pierre HUBERT -const _APIGroupsMembershipLevelsMap = { +const APIGroupsMembershipLevelsMap = { "administrator": GroupMembershipLevel.ADMINISTRATOR, "moderator": GroupMembershipLevel.MODERATOR, "member": GroupMembershipLevel.MEMBER, @@ -326,7 +327,22 @@ class GroupsHelper { await APIRequest.withLogin("groups/update_membership_level") .addInt("groupID", groupID) .addInt("userID", userID) - .addString("level", invertMap(_APIGroupsMembershipLevelsMap)[level]) + .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(); /// Turn an API entry into a group object @@ -336,7 +352,7 @@ class GroupsHelper { name: map["name"], iconURL: map["icon_url"], numberMembers: map["number_members"], - membershipLevel: _APIGroupsMembershipLevelsMap[map["membership"]], + membershipLevel: APIGroupsMembershipLevelsMap[map["membership"]], visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]], registrationLevel: _APIGroupsRegistrationLevelsMap[map["registration_level"]], @@ -352,7 +368,7 @@ class GroupsHelper { name: map["name"], iconURL: map["icon_url"], numberMembers: map["number_members"], - membershipLevel: _APIGroupsMembershipLevelsMap[map["membership"]], + membershipLevel: APIGroupsMembershipLevelsMap[map["membership"]], visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]], registrationLevel: _APIGroupsRegistrationLevelsMap[map["registration_level"]], @@ -373,6 +389,6 @@ class GroupsHelper { userID: row["user_id"], groupID: row["group_id"], timeCreate: row["time_create"], - level: _APIGroupsMembershipLevelsMap[row["level"]], + level: APIGroupsMembershipLevelsMap[row["level"]], ); } diff --git a/lib/helpers/server_config_helper.dart b/lib/helpers/server_config_helper.dart index e1e7cc8..4f7a994 100644 --- a/lib/helpers/server_config_helper.dart +++ b/lib/helpers/server_config_helper.dart @@ -52,6 +52,8 @@ class ServerConfigurationHelper { minLikesLifetime: dataConservationPolicy["min_likes_lifetime"], ), conversationsPolicy: ConversationsPolicy( + maxConversationNameLen: + conversationsPolicy["max_conversation_name_len"], minMessageLen: conversationsPolicy["min_message_len"], maxMessageLen: conversationsPolicy["max_message_len"], allowedFilesType: diff --git a/lib/models/advanced_group_info.dart b/lib/models/advanced_group_info.dart index 1da015c..5b9e897 100644 --- a/lib/models/advanced_group_info.dart +++ b/lib/models/advanced_group_info.dart @@ -1,4 +1,5 @@ import 'package:comunic/enums/likes_type.dart'; +import 'package:comunic/models/conversation.dart'; import 'package:comunic/models/like_element.dart'; import 'package:flutter/material.dart'; @@ -15,6 +16,7 @@ class AdvancedGroupInfo extends Group implements LikeElement { String url; int likes; bool userLike; + List conversations; AdvancedGroupInfo({ @required int id, diff --git a/lib/models/conversation.dart b/lib/models/conversation.dart index e5b847a..d33f569 100644 --- a/lib/models/conversation.dart +++ b/lib/models/conversation.dart @@ -4,6 +4,8 @@ import 'package:comunic/utils/account_utils.dart'; import 'package:flutter/material.dart'; import 'package:meta/meta.dart'; +import 'group.dart'; + /// Conversation model /// /// @author Pierre HUBERT @@ -17,6 +19,7 @@ class Conversation extends SerializableElement { final Color color; final String logoURL; final int groupID; + final GroupMembershipLevel groupMinMembershipLevel; final List members; final bool canEveryoneAddMembers; final CallCapabilities callCapabilities; @@ -29,6 +32,7 @@ class Conversation extends SerializableElement { @required this.color, @required this.logoURL, @required this.groupID, + @required this.groupMinMembershipLevel, @required this.members, @required this.canEveryoneAddMembers, this.callCapabilities = CallCapabilities.NONE, @@ -37,6 +41,7 @@ class Conversation extends SerializableElement { assert(lastActivity != null), assert(members != null), assert(canEveryoneAddMembers != null), + assert((groupID == null) == (groupMinMembershipLevel == null)), assert(callCapabilities != null), assert(isHavingCall != null); @@ -78,6 +83,9 @@ class Conversation extends SerializableElement { color = map["color"] == null ? null : Color(map["color"]), logoURL = map["logoURL"], groupID = map["groupID"], + groupMinMembershipLevel = GroupMembershipLevel.values.firstWhere( + (element) => element.toString() == map["groupMinMembershipLevel"], + orElse: () => null), lastActivity = map["lastActivity"], members = map["members"] .map((el) => ConversationMember.fromJSON(el)) @@ -96,6 +104,7 @@ class Conversation extends SerializableElement { "color": color?.value, "logoURL": logoURL, "groupID": groupID, + "groupMinMembershipLevel": groupMinMembershipLevel?.toString(), "lastActivity": lastActivity, "members": members.map((e) => e.toJson()).toList(), "canEveryoneAddMembers": canEveryoneAddMembers, diff --git a/lib/models/new_group_conversation.dart b/lib/models/new_group_conversation.dart new file mode 100644 index 0000000..dc017e3 --- /dev/null +++ b/lib/models/new_group_conversation.dart @@ -0,0 +1,21 @@ +import 'package:comunic/models/group.dart'; +import 'package:flutter/foundation.dart'; + +/// This class contains information about a conversation linked to a group +/// to create +/// +/// @author Pierre Hubert + +class NewGroupConversation { + final int groupID; + final String name; + final GroupMembershipLevel minMembershipLevel; + + const NewGroupConversation({ + @required this.groupID, + @required this.name, + @required this.minMembershipLevel, + }) : assert(groupID != null), + assert(name != null), + assert(minMembershipLevel != null); +} diff --git a/lib/models/server_config.dart b/lib/models/server_config.dart index ea47a41..b19879b 100644 --- a/lib/models/server_config.dart +++ b/lib/models/server_config.dart @@ -58,6 +58,7 @@ class ServerDataConservationPolicy { } class ConversationsPolicy { + final int maxConversationNameLen; final int minMessageLen; final int maxMessageLen; final List allowedFilesType; @@ -72,6 +73,7 @@ class ConversationsPolicy { final int maxLogoHeight; const ConversationsPolicy({ + @required this.maxConversationNameLen, @required this.minMessageLen, @required this.maxMessageLen, @required this.allowedFilesType, @@ -84,7 +86,8 @@ class ConversationsPolicy { @required this.maxThumbnailHeight, @required this.maxLogoWidth, @required this.maxLogoHeight, - }) : assert(minMessageLen != null), + }) : assert(maxConversationNameLen != null), + assert(minMessageLen != null), assert(maxMessageLen != null), assert(allowedFilesType != null), assert(filesMaxSize != null), diff --git a/lib/ui/screens/group_settings_screen.dart b/lib/ui/screens/group_settings_screen.dart index fa3156f..f5ba96d 100644 --- a/lib/ui/screens/group_settings_screen.dart +++ b/lib/ui/screens/group_settings_screen.dart @@ -1,8 +1,10 @@ import 'dart:typed_data'; import 'package:comunic/helpers/groups_helper.dart'; +import 'package:comunic/helpers/server_config_helper.dart'; import 'package:comunic/models/advanced_group_info.dart'; import 'package:comunic/models/group.dart'; +import 'package:comunic/models/new_group_conversation.dart'; import 'package:comunic/ui/dialogs/input_user_password_dialog.dart'; import 'package:comunic/ui/dialogs/multi_choices_dialog.dart'; import 'package:comunic/ui/dialogs/virtual_directory_dialog.dart'; @@ -17,6 +19,7 @@ import 'package:comunic/ui/widgets/settings/text_settings_edit_tile.dart'; import 'package:comunic/utils/files_utils.dart'; import 'package:comunic/utils/input_utils.dart'; import 'package:comunic/utils/intl_utils.dart'; +import 'package:comunic/utils/log_utils.dart'; import 'package:comunic/utils/ui_utils.dart'; import 'package:flutter/material.dart'; import 'package:identicon/identicon.dart'; @@ -85,6 +88,7 @@ class _GroupSettingsScreenState extends SafeState { HeadSpacerSection(), _buildGeneralSection(), _buildAccessRestrictions(), + _buildConversationsArea(), _buildGroupLogoArea(), _buildDangerZone(), ], @@ -256,6 +260,75 @@ class _GroupSettingsScreenState extends SafeState { ], ); + List> + get _conversationMinMembershipLevel => [ + MultiChoiceEntry( + id: GroupMembershipLevel.ADMINISTRATOR, + title: tr("Administrators only"), + subtitle: tr( + "Only the administrators of the group can access the conversation"), + ), + MultiChoiceEntry( + id: GroupMembershipLevel.MODERATOR, + title: tr("Moderators and administrators"), + subtitle: tr( + "Only moderators and administrators of the group can access the conversation"), + ), + MultiChoiceEntry( + id: GroupMembershipLevel.MEMBER, + title: tr("All members"), + subtitle: tr( + "All the members of the group can access the conversation"), + ), + ]; + + SettingsSection _buildConversationsArea() => SettingsSection( + title: tr("Group conversations"), + tiles: [ + SettingsTile( + title: tr("Create a new conversation"), + onPressed: _createNewGroupConversation, + ), + ], + ); + + void _createNewGroupConversation(BuildContext context) async { + try { + final name = await askUserString( + context: context, + title: tr("New conversation name"), + message: tr("Please give a name to the new conversation"), + defaultValue: "", + hint: tr("Name"), + minLength: 1, + maxLength: ServerConfigurationHelper + .config.conversationsPolicy.maxConversationNameLen, + ); + + if (name == null) return; + + final visibility = await showMultiChoicesDialog( + context: context, + choices: _conversationMinMembershipLevel, + defaultChoice: GroupMembershipLevel.MEMBER, + title: tr("Conversation visibility"), + ); + + if (visibility == null) return; + + await GroupsHelper.createGroupConversation(NewGroupConversation( + groupID: _groupSettings.id, + name: name, + minMembershipLevel: visibility, + )); + + _key.currentState.refresh(); + } catch (e, s) { + logError(e, s); + snack(context, tr("Failed to create a conversation!")); + } + } + Widget _buildGroupLogoArea() { return SettingsSection( title: tr("Group logo"),