import 'package:comunic/helpers/conversations_helper.dart'; import 'package:comunic/helpers/friends_helper.dart'; import 'package:comunic/helpers/groups_helper.dart'; import 'package:comunic/helpers/users_helper.dart'; import 'package:comunic/helpers/webapp_helper.dart'; import 'package:comunic/lists/groups_list.dart'; import 'package:comunic/lists/memberships_list.dart'; import 'package:comunic/lists/users_list.dart'; import 'package:comunic/models/friend.dart'; import 'package:comunic/models/group.dart'; import 'package:comunic/models/membership.dart'; import 'package:comunic/ui/routes/main_route/main_route.dart'; import 'package:comunic/ui/routes/main_route/page_info.dart'; import 'package:comunic/ui/widgets/account_image_widget.dart'; import 'package:comunic/ui/widgets/conversation_image_widget.dart'; import 'package:comunic/ui/widgets/group_icon_widget.dart'; import 'package:comunic/ui/widgets/group_membership_widget.dart'; import 'package:comunic/ui/widgets/safe_state.dart'; import 'package:comunic/utils/date_utils.dart'; import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/ui_utils.dart'; import 'package:flutter/material.dart'; /// Memberships panel /// /// @author Pierre Hubert class MembershipsPanel extends StatefulWidget { final PageInfo currentPage; const MembershipsPanel({ Key? key, required this.currentPage, }) : assert(currentPage != null), super(key: key); @override _MembershipsPanelState createState() => _MembershipsPanelState(); } const _MembershipIconsWidth = 30.0; class _MembershipsPanelState extends SafeState { final _refreshKey = GlobalKey(); MembershipList? _membershipList; UsersList? _usersList; late GroupsList _groupsList; Future _refresh() async { try { final memberships = await WebAppHelper.getMemberships(); final users = await UsersHelper().getListWithThrow(memberships.usersId); final groups = await GroupsHelper().getListOrThrow(memberships.groupsId); setState(() { _membershipList = memberships; _usersList = users; _groupsList = groups; }); } catch (e, s) { print("Could not load the list of memberships! $e\n$s"); } } @override void initState() { super.initState(); setInterval(20, (t) => _refresh()); _refresh(); } @override Widget build(BuildContext context) { if (_membershipList == null) return buildCenteredProgressBar(); return Material( color: Colors.transparent, child: RefreshIndicator( key: _refreshKey, child: _buildMembershipsList(), onRefresh: _refresh), ); } Widget _buildMembershipsList() { return ListTileTheme( iconColor: IconTheme.of(context).color, child: ListView.builder( itemBuilder: _buildMembershipTile, itemCount: _membershipList!.length, ), ); } Widget _buildMembershipTile(BuildContext context, int index) { final Membership membership = _membershipList![index]!; switch (membership.type) { case MembershipType.FRIEND: return _buildFriendMembership(membership); case MembershipType.GROUP: return _buildGroupMembership(membership); case MembershipType.CONVERSATION: return _buildConversationMembership(membership); } throw Exception("Unreachable statement!"); } // TODO : add private messages icon support Widget _buildFriendMembership(Membership membership) { final user = _usersList!.getUser(membership.friend!.id); final connected = membership.friend!.isConnected; Widget subtitle; final friend = membership.friend!; if (!friend.accepted) { subtitle = RichText( text: TextSpan(children: [ WidgetSpan( child: _RespondFriendshipRequestButton( friend: friend, accept: true, text: tr("Accept"), color: Colors.green, onTap: _respondFriendshipRequest, )), TextSpan(text: " "), WidgetSpan( child: _RespondFriendshipRequestButton( friend: friend, accept: false, text: tr("Reject"), color: Colors.red, onTap: _respondFriendshipRequest, )), ])); } else subtitle = Text( connected ? tr("Online")! : diffTimeFromNowToStr(membership.lastActive!)!, style: TextStyle(color: connected ? Colors.green : null), ); return Container( color: widget.currentPage.type == PageType.USER_PAGE && widget.currentPage.id == user.id ? Colors.grey.shade800 : null, child: ListTile( leading: AccountImageWidget(user: user, width: _MembershipIconsWidth), title: Text(user.displayName), subtitle: subtitle, onTap: () => MainController.of(context)!.openUserPage(user.id!), ), ); } Widget _buildGroupMembership(Membership membership) { final group = _groupsList.getGroup(membership.groupID)!; return Container( color: widget.currentPage.type == PageType.GROUP_PAGE && widget.currentPage.id == group.id ? Colors.grey.shade800 : null, child: Column( children: [_buildMainGroupInformationTile(membership, group)] ..addAll(_membershipList! .getGroupConversations(group.id) .map((e) => Padding( padding: const EdgeInsets.only(left: 30.0), child: _buildConversationMembership(e!, true), )) .toList()), ), ); } Widget _buildMainGroupInformationTile(Membership membership, Group group) { Widget subtitle; if (!group.isAtLeastMember) { subtitle = GroupMembershipWidget( group: group, onUpdated: () => _refreshKey.currentState!.show(), onError: _onGroupMembershipUpdateError, ); } else { subtitle = Text(diffTimeFromNowToStr(membership.lastActive!)!); } // Main group information return ListTile( leading: GroupIcon( group: group, width: _MembershipIconsWidth, ), title: Text(group.displayName), subtitle: subtitle, onTap: () => MainController.of(context)!.openGroup(group.id), ); } Widget _buildConversationMembership(Membership membership, [bool allowGroup = false]) { final conversation = membership.conversation!; if (conversation.isGroupConversation && !allowGroup) return Container(); Color? color; if (conversation.isHavingCall) color = Color(0xFF815d1d); else if (widget.currentPage.type == PageType.CONVERSATION_PAGE && widget.currentPage.id == conversation.id) color = Colors.grey.shade800; else if (!conversation.sawLastMessage) color = Color(0xFF1c443a); return Container( color: color, child: ListTile( dense: true, leading: ConversationImageWidget( conversation: conversation, users: _usersList!, noUserImage: conversation.isGroupConversation, ), title: Row( children: [ Icon( Icons.message, size: 12, ), SizedBox(width: 5), Expanded( child: Text(ConversationsHelper.getConversationName( conversation, _usersList)!), ), ], ), subtitle: Text(diffTimeFromNowToStr(membership.lastActive!)! + (conversation.isHavingCall ? "\n" + tr("Ongoing call")! : "")), onTap: () => MainController.of(context)! .openConversation(conversation, fullScreen: true), trailing: conversation.isHavingCall ? FloatingActionButton( heroTag: null, child: Icon(Icons.call), onPressed: () => MainController.of(context)!.startCall(conversation.id!), ) : null, ), ); } /// Respond to a friendship request Future _respondFriendshipRequest(Friend f, bool accept) async { try { if (!accept && !await showConfirmDialog( context: context, message: tr("Do you really want to reject this friendship request?"))) return; await FriendsHelper().respondRequest(f.id, accept); setState(() { if (accept) f.accepted = true; else _membershipList!.removeFriend(f.id); }); _refreshKey.currentState!.show(); } catch (e, s) { print("Could not respond to friendship request! $e\n$s"); showSimpleSnack(context, tr("Could not respond to friendship request!")!); } } /// Handles the case of failure in group membership update void _onGroupMembershipUpdateError() { showSimpleSnack(context, tr("Could not update group membership!")!); _refreshKey.currentState!.show(); } } class _RespondFriendshipRequestButton extends StatelessWidget { final Friend friend; final bool accept; final String? text; final Color color; final void Function(Friend, bool) onTap; const _RespondFriendshipRequestButton({ Key? key, required this.friend, required this.accept, required this.text, required this.color, required this.onTap, }) : super(key: key); @override Widget build(BuildContext context) { return InkWell( onTap: () => onTap(friend, accept), child: Text(text!, style: TextStyle(color: color)), ); } }