mirror of
				https://gitlab.com/comunic/comunicmobile
				synced 2025-11-03 19:54:12 +00:00 
			
		
		
		
	Integrate members screen as a tab
This commit is contained in:
		
							
								
								
									
										319
									
								
								lib/ui/screens/group_sections/group_members_screen.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								lib/ui/screens/group_sections/group_members_screen.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,319 @@
 | 
			
		||||
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, this.groupID})
 | 
			
		||||
      : assert(groupID != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _GroupMembersSectionState createState() => _GroupMembersSectionState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _GroupMembersSectionState extends State<GroupMembersSection> {
 | 
			
		||||
  final _key = GlobalKey<AsyncScreenWidgetState>();
 | 
			
		||||
  Group _group;
 | 
			
		||||
  GroupMembersList _members;
 | 
			
		||||
  UsersList _users;
 | 
			
		||||
 | 
			
		||||
  Future<void> _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()),
 | 
			
		||||
        _buildInvitationFAB()
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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<MultiChoiceEntry<GroupMembershipLevel>> 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: _buildTrailing(),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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: <Widget>[
 | 
			
		||||
        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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user