import 'package:comunic/helpers/groups_helper.dart'; import 'package:comunic/helpers/users_helper.dart'; import 'package:comunic/lists/group_members_list.dart'; import 'package:comunic/lists/users_list.dart'; import 'package:comunic/models/group.dart'; import 'package:comunic/models/group_membership.dart'; import 'package:comunic/models/user.dart'; import 'package:comunic/ui/dialogs/multi_choices_dialog.dart'; import 'package:comunic/ui/dialogs/pick_user_dialog.dart'; import 'package:comunic/ui/widgets/account_image_widget.dart'; import 'package:comunic/ui/widgets/async_screen_widget.dart'; import 'package:comunic/utils/account_utils.dart'; import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/ui_utils.dart'; import 'package:flutter/material.dart'; /// Group members screen /// /// @author Pierre HUBERT class GroupMembersSection extends StatefulWidget { final int groupID; const GroupMembersSection({Key? key, required this.groupID}) : assert(groupID != null), super(key: key); @override _GroupMembersSectionState createState() => _GroupMembersSectionState(); } class _GroupMembersSectionState extends State { final _key = GlobalKey(); late Group _group; late GroupMembersList _members; late UsersList _users; Future _refresh() async { final group = await GroupsHelper().getSingle(widget.groupID, force: true); final members = await GroupsHelper.getMembersList(widget.groupID); final users = await UsersHelper().getListWithThrow(members.usersID); _group = group; _members = members; _users = users; } @override Widget build(BuildContext context) => AsyncScreenWidget( key: _key, onReload: _refresh, onBuild: _buildBodyContent, errorMessage: tr("Could not load the list of members of this group!")!, showOldDataWhileUpdating: true, ); Widget _buildBodyContent() { return Stack( children: [ ListView(children: _members.map(_buildGroupMemberTile).toList()), _group.isAtLeastModerator ? _buildInvitationFAB() : Container() ], ); } Widget _buildGroupMemberTile(GroupMembership membership) { final user = _users.getUser(membership.userID); return _GroupMembershipTile( group: _group, membership: membership, user: user, onUpdated: () => _key.currentState!.refresh(), ); } Widget _buildInvitationFAB() { return Positioned( right: 20, bottom: 20, child: FloatingActionButton( onPressed: _inviteMember, child: Icon(Icons.add), ), ); } void _inviteMember() async { try { final userID = await showPickUserDialog(context); if (userID == null) return; await GroupsHelper.sendInvitation(widget.groupID, userID); } catch (e, s) { print("Could not invite a user! $e\n$s"); showSimpleSnack(context, tr("Could not invite a user!")!); } _key.currentState!.refresh(); } } List> get _membershipLevels => [ MultiChoiceEntry( id: GroupMembershipLevel.ADMINISTRATOR, title: tr("Administrator")!, subtitle: tr("Can change members privileges and group settings")), MultiChoiceEntry( id: GroupMembershipLevel.MODERATOR, title: tr("Moderator")!, subtitle: tr( "Can always create posts, invite users and respond to membership request")), MultiChoiceEntry( id: GroupMembershipLevel.MEMBER, title: tr("Member")!, subtitle: tr("Can access to all group posts")), MultiChoiceEntry( id: GroupMembershipLevel.PENDING, title: tr("Requested")!, hidden: true, ), MultiChoiceEntry( id: GroupMembershipLevel.INVITED, title: tr("Invited")!, hidden: true, ), MultiChoiceEntry( id: GroupMembershipLevel.VISITOR, title: tr("Visitor")!, hidden: true, ), ]; class _GroupMembershipTile extends StatefulWidget { final Group group; final GroupMembership membership; final User user; final void Function() onUpdated; const _GroupMembershipTile({ Key? key, required this.group, required this.membership, required this.user, required this.onUpdated, }) : assert(group != null), assert(membership != null), assert(user != null), assert(onUpdated != null), super(key: key); @override __GroupMembershipTileState createState() => __GroupMembershipTileState(); } enum _MemberMenuOptions { CHANGE_LEVEL, DELETE } class __GroupMembershipTileState extends State<_GroupMembershipTile> { int get groupID => widget.membership.groupID; int get memberID => widget.membership.userID; bool get _isCurrentUser => widget.membership.userID == userID(); @override Widget build(BuildContext context) { return ListTile( leading: AccountImageWidget( user: widget.user, ), title: Text(widget.user.displayName), subtitle: Text(_membershipLevels .firstWhere((f) => f.id == widget.membership.level) .title), trailing: widget.group.isAtLeastModerator ? _buildTrailing() : null, ); } Widget? _buildTrailing() { switch (widget.membership.level) { case GroupMembershipLevel.ADMINISTRATOR: case GroupMembershipLevel.MODERATOR: case GroupMembershipLevel.MEMBER: return _buildMemberCase(); break; case GroupMembershipLevel.INVITED: return _buildInvitedCase(); break; case GroupMembershipLevel.PENDING: return _buildRequestedCase(); break; case GroupMembershipLevel.VISITOR: // This case should never happen break; } return null; } Widget _buildMemberCase() { return PopupMenuButton<_MemberMenuOptions>( itemBuilder: (c) => [ // Change membership level PopupMenuItem( child: Text(tr("Change level")!), enabled: !_isCurrentUser && widget.group.isAdmin, value: _MemberMenuOptions.CHANGE_LEVEL, ), // Remove membership PopupMenuItem( child: Text(tr("Remove")!), value: _MemberMenuOptions.DELETE, enabled: !_isCurrentUser, ), ], onSelected: _handleMenu, ); } void _handleMenu(_MemberMenuOptions value) { switch (value) { case _MemberMenuOptions.CHANGE_LEVEL: _changeLevel(); break; case _MemberMenuOptions.DELETE: _removeMembership(); break; } } void _changeLevel() async { try { final newLevel = await showMultiChoicesDialog( context: context, choices: _membershipLevels, defaultChoice: widget.membership.level, title: tr("New membership level"), ); if (newLevel == null) return; await GroupsHelper.setNewLevel(groupID, memberID, newLevel); } catch (e, s) { print("Could not change group membership level! $e\n$s"); showSimpleSnack(context, tr("Could not change group membership level!")!); } widget.onUpdated(); } void _removeMembership() async { try { if (!await showConfirmDialog( context: context, message: tr("Do you really want to remove this membership ?"))) return; await GroupsHelper.removeMemberFromGroup(groupID, memberID); } catch (e, s) { print("Could not remove membership! $e\n$s"); showSimpleSnack(context, tr("Could not remove this membership!")!); } widget.onUpdated(); } Widget _buildInvitedCase() { return MaterialButton( onPressed: _cancelMembership, child: Text(tr("Cancel")!.toUpperCase()), textColor: Colors.red, ); } void _cancelMembership() async { try { await GroupsHelper.cancelInvitation(groupID, memberID); } catch (e, s) { print("Could not cancel invitation! $e\n$s"); showSimpleSnack(context, tr("Could not cancel invitation!")!); } widget.onUpdated(); } Widget _buildRequestedCase() { return IntrinsicWidth( child: Row( children: [ MaterialButton( onPressed: () => _respondRequest(false), child: Text(tr("Reject")!.toUpperCase()), textColor: Colors.red, ), MaterialButton( onPressed: () => _respondRequest(true), child: Text(tr("Accept")!.toUpperCase()), textColor: Colors.green, ) ], )); } void _respondRequest(bool accept) async { try { await GroupsHelper.respondRequest(groupID, memberID, accept); } catch (e, s) { print("Could not respond to membership request! $e\n$s"); showSimpleSnack(context, tr("Could not respond to membership request!")!); } widget.onUpdated(); } }