From 7658425a7df60c2ac50f19f0dcd6675b923e3e97 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 14 Apr 2021 14:09:46 +0200 Subject: [PATCH] refactor: MVC chat list view --- lib/config/routes.dart | 2 +- lib/controllers/chat_list_controller.dart | 236 ++++++++++++ lib/views/chat_list.dart | 444 ---------------------- lib/views/chat_list_view.dart | 242 ++++++++++++ 4 files changed, 479 insertions(+), 445 deletions(-) create mode 100644 lib/controllers/chat_list_controller.dart delete mode 100644 lib/views/chat_list.dart create mode 100644 lib/views/chat_list_view.dart diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 13afa0d9..91b7acbe 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -9,7 +9,7 @@ import 'package:fluffychat/views/widgets/matrix.dart'; import 'package:fluffychat/views/chat.dart'; import 'package:fluffychat/controllers/chat_details_controller.dart'; import 'package:fluffychat/controllers/chat_encryption_settings_controller.dart'; -import 'package:fluffychat/views/chat_list.dart'; +import 'package:fluffychat/controllers/chat_list_controller.dart'; import 'package:fluffychat/views/chat_permissions_settings.dart'; import 'package:fluffychat/views/empty_page.dart'; import 'package:fluffychat/views/loading_view.dart'; diff --git a/lib/controllers/chat_list_controller.dart b/lib/controllers/chat_list_controller.dart new file mode 100644 index 00000000..5e1035ac --- /dev/null +++ b/lib/controllers/chat_list_controller.dart @@ -0,0 +1,236 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:adaptive_page_layout/adaptive_page_layout.dart'; +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/views/chat_list_view.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:future_loading_dialog/future_loading_dialog.dart'; +import 'package:receive_sharing_intent/receive_sharing_intent.dart'; +import '../views/widgets/matrix.dart'; +import '../utils/matrix_file_extension.dart'; +import '../utils/url_launcher.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +enum SelectMode { normal, share, select } +enum PopupMenuAction { settings, invite, newGroup, setStatus, archive } + +class ChatList extends StatefulWidget { + final String activeChat; + + const ChatList({this.activeChat, Key key}) : super(key: key); + + @override + ChatListController createState() => ChatListController(); +} + +class ChatListController extends State { + StreamSubscription _intentDataStreamSubscription; + + StreamSubscription _intentFileStreamSubscription; + + final selectedRoomIds = {}; + + void _processIncomingSharedFiles(List files) { + if (files?.isEmpty ?? true) return; + AdaptivePageLayout.of(context).popUntilIsFirst(); + final file = File(files.first.path); + + Matrix.of(context).shareContent = { + 'msgtype': 'chat.fluffy.shared_file', + 'file': MatrixFile( + bytes: file.readAsBytesSync(), + name: file.path, + ).detectFileType, + }; + } + + void _processIncomingSharedText(String text) { + if (text == null) return; + AdaptivePageLayout.of(context).popUntilIsFirst(); + if (text.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) || + (text.toLowerCase().startsWith(AppConfig.schemePrefix) && + !RegExp(r'\s').hasMatch(text))) { + UrlLauncher(context, text).openMatrixToUrl(); + return; + } + Matrix.of(context).shareContent = { + 'msgtype': 'm.text', + 'body': text, + }; + } + + void _initReceiveSharingIntent() { + if (!PlatformInfos.isMobile) return; + + // For sharing images coming from outside the app while the app is in the memory + _intentFileStreamSubscription = ReceiveSharingIntent.getMediaStream() + .listen(_processIncomingSharedFiles, onError: print); + + // For sharing images coming from outside the app while the app is closed + ReceiveSharingIntent.getInitialMedia().then(_processIncomingSharedFiles); + + // For sharing or opening urls/text coming from outside the app while the app is in the memory + _intentDataStreamSubscription = ReceiveSharingIntent.getTextStream() + .listen(_processIncomingSharedText, onError: print); + + // For sharing or opening urls/text coming from outside the app while the app is closed + ReceiveSharingIntent.getInitialText().then(_processIncomingSharedText); + } + + @override + void initState() { + _initReceiveSharingIntent(); + super.initState(); + } + + @override + void dispose() { + _intentDataStreamSubscription?.cancel(); + _intentFileStreamSubscription?.cancel(); + super.dispose(); + } + + void toggleSelection(String roomId) { + setState(() => selectedRoomIds.contains(roomId) + ? selectedRoomIds.remove(roomId) + : selectedRoomIds.add(roomId)); + } + + Future toggleUnread() { + final room = Matrix.of(context).client.getRoomById(selectedRoomIds.single); + return showFutureLoadingDialog( + context: context, + future: () => room.setUnread(!room.isUnread), + ); + } + + Future toggleFavouriteRoom() { + final room = Matrix.of(context).client.getRoomById(selectedRoomIds.single); + return showFutureLoadingDialog( + context: context, + future: () => room.setFavourite(!room.isFavourite), + ); + } + + Future toggleMuted() { + final room = Matrix.of(context).client.getRoomById(selectedRoomIds.single); + return showFutureLoadingDialog( + context: context, + future: () => room.setPushRuleState( + room.pushRuleState == PushRuleState.notify + ? PushRuleState.mentions_only + : PushRuleState.notify), + ); + } + + Future archiveAction() async { + final confirmed = await showOkCancelAlertDialog( + context: context, + title: L10n.of(context).areYouSure, + okLabel: L10n.of(context).yes, + cancelLabel: L10n.of(context).cancel, + useRootNavigator: false, + ) == + OkCancelResult.ok; + if (!confirmed) return; + await showFutureLoadingDialog( + context: context, + future: () => _archiveSelectedRooms(), + ); + setState(() => null); + } + + void setStatus() async { + final input = await showTextInputDialog( + context: context, + title: L10n.of(context).setStatus, + okLabel: L10n.of(context).ok, + cancelLabel: L10n.of(context).cancel, + useRootNavigator: false, + textFields: [ + DialogTextField( + hintText: L10n.of(context).statusExampleMessage, + ), + ]); + if (input == null) return; + await showFutureLoadingDialog( + context: context, + future: () => Matrix.of(context).client.sendPresence( + Matrix.of(context).client.userID, + PresenceType.online, + statusMsg: input.single, + ), + ); + } + + void onPopupMenuSelect(action) { + switch (action) { + case PopupMenuAction.setStatus: + setStatus(); + break; + case PopupMenuAction.settings: + AdaptivePageLayout.of(context).pushNamed('/settings'); + break; + case PopupMenuAction.invite: + FluffyShare.share( + L10n.of(context).inviteText(Matrix.of(context).client.userID, + 'https://matrix.to/#/${Matrix.of(context).client.userID}'), + context); + break; + case PopupMenuAction.newGroup: + AdaptivePageLayout.of(context).pushNamed('/newgroup'); + break; + case PopupMenuAction.archive: + AdaptivePageLayout.of(context).pushNamed('/archive'); + break; + } + } + + Future _archiveSelectedRooms() async { + final client = Matrix.of(context).client; + while (selectedRoomIds.isNotEmpty) { + final roomId = selectedRoomIds.first; + await client.getRoomById(roomId).leave(); + toggleSelection(roomId); + } + } + + Future waitForFirstSync() async { + final client = Matrix.of(context).client; + if (client.prevBatch?.isEmpty ?? true) { + await client.onFirstSync.stream.first; + } + return true; + } + + void cancelAction() { + final selectMode = Matrix.of(context).shareContent != null + ? SelectMode.share + : selectedRoomIds.isEmpty + ? SelectMode.normal + : SelectMode.select; + if (selectMode == SelectMode.share) { + setState(() => Matrix.of(context).shareContent = null); + } else { + setState(() => selectedRoomIds.clear()); + } + } + + @override + Widget build(BuildContext context) => ChatListView(this); +} + +enum ChatListPopupMenuItemActions { + createGroup, + discover, + setStatus, + inviteContact, + settings, +} diff --git a/lib/views/chat_list.dart b/lib/views/chat_list.dart deleted file mode 100644 index c10bc746..00000000 --- a/lib/views/chat_list.dart +++ /dev/null @@ -1,444 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:adaptive_page_layout/adaptive_page_layout.dart'; -import 'package:famedlysdk/famedlysdk.dart'; -import 'package:fluffychat/views/widgets/connection_status_header.dart'; -import 'package:fluffychat/views/widgets/list_items/chat_list_item.dart'; -import 'package:fluffychat/utils/fluffy_share.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:future_loading_dialog/future_loading_dialog.dart'; -import 'package:receive_sharing_intent/receive_sharing_intent.dart'; -import '../views/widgets/matrix.dart'; -import '../utils/matrix_file_extension.dart'; -import '../utils/url_launcher.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -enum SelectMode { normal, share, select } -enum PopupMenuAction { settings, invite, newGroup, setStatus, archive } - -class ChatList extends StatefulWidget { - final String activeChat; - - const ChatList({this.activeChat, Key key}) : super(key: key); - - @override - _ChatListState createState() => _ChatListState(); -} - -class _ChatListState extends State { - StreamSubscription _intentDataStreamSubscription; - - StreamSubscription _intentFileStreamSubscription; - - AppBar appBar; - final _selectedRoomIds = {}; - - void _processIncomingSharedFiles(List files) { - if (files?.isEmpty ?? true) return; - AdaptivePageLayout.of(context).popUntilIsFirst(); - final file = File(files.first.path); - - Matrix.of(context).shareContent = { - 'msgtype': 'chat.fluffy.shared_file', - 'file': MatrixFile( - bytes: file.readAsBytesSync(), - name: file.path, - ).detectFileType, - }; - } - - void _processIncomingSharedText(String text) { - if (text == null) return; - AdaptivePageLayout.of(context).popUntilIsFirst(); - if (text.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) || - (text.toLowerCase().startsWith(AppConfig.schemePrefix) && - !RegExp(r'\s').hasMatch(text))) { - UrlLauncher(context, text).openMatrixToUrl(); - return; - } - Matrix.of(context).shareContent = { - 'msgtype': 'm.text', - 'body': text, - }; - } - - void _initReceiveSharingIntent() { - if (!PlatformInfos.isMobile) return; - - // For sharing images coming from outside the app while the app is in the memory - _intentFileStreamSubscription = ReceiveSharingIntent.getMediaStream() - .listen(_processIncomingSharedFiles, onError: print); - - // For sharing images coming from outside the app while the app is closed - ReceiveSharingIntent.getInitialMedia().then(_processIncomingSharedFiles); - - // For sharing or opening urls/text coming from outside the app while the app is in the memory - _intentDataStreamSubscription = ReceiveSharingIntent.getTextStream() - .listen(_processIncomingSharedText, onError: print); - - // For sharing or opening urls/text coming from outside the app while the app is closed - ReceiveSharingIntent.getInitialText().then(_processIncomingSharedText); - } - - @override - void initState() { - _initReceiveSharingIntent(); - super.initState(); - } - - @override - void dispose() { - _intentDataStreamSubscription?.cancel(); - _intentFileStreamSubscription?.cancel(); - super.dispose(); - } - - void _toggleSelection(String roomId) { - setState(() => _selectedRoomIds.contains(roomId) - ? _selectedRoomIds.remove(roomId) - : _selectedRoomIds.add(roomId)); - } - - Future _toggleUnread(BuildContext context) { - final room = Matrix.of(context).client.getRoomById(_selectedRoomIds.single); - return showFutureLoadingDialog( - context: context, - future: () => room.setUnread(!room.isUnread), - ); - } - - Future _toggleFavouriteRoom(BuildContext context) { - final room = Matrix.of(context).client.getRoomById(_selectedRoomIds.single); - return showFutureLoadingDialog( - context: context, - future: () => room.setFavourite(!room.isFavourite), - ); - } - - Future _toggleMuted(BuildContext context) { - final room = Matrix.of(context).client.getRoomById(_selectedRoomIds.single); - return showFutureLoadingDialog( - context: context, - future: () => room.setPushRuleState( - room.pushRuleState == PushRuleState.notify - ? PushRuleState.mentions_only - : PushRuleState.notify), - ); - } - - Future _archiveAction(BuildContext context) async { - final confirmed = await showOkCancelAlertDialog( - context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).cancel, - useRootNavigator: false, - ) == - OkCancelResult.ok; - if (!confirmed) return; - await showFutureLoadingDialog( - context: context, - future: () => _archiveSelectedRooms(context), - ); - setState(() => null); - } - - void _setStatus(BuildContext context) async { - final input = await showTextInputDialog( - context: context, - title: L10n.of(context).setStatus, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - useRootNavigator: false, - textFields: [ - DialogTextField( - hintText: L10n.of(context).statusExampleMessage, - ), - ]); - if (input == null) return; - await showFutureLoadingDialog( - context: context, - future: () => Matrix.of(context).client.sendPresence( - Matrix.of(context).client.userID, - PresenceType.online, - statusMsg: input.single, - ), - ); - } - - Future _archiveSelectedRooms(BuildContext context) async { - final client = Matrix.of(context).client; - while (_selectedRoomIds.isNotEmpty) { - final roomId = _selectedRoomIds.first; - await client.getRoomById(roomId).leave(); - _toggleSelection(roomId); - } - } - - Future waitForFirstSync(BuildContext context) async { - final client = Matrix.of(context).client; - if (client.prevBatch?.isEmpty ?? true) { - await client.onFirstSync.stream.first; - } - return true; - } - - @override - Widget build(BuildContext context) { - return StreamBuilder( - stream: Matrix.of(context).onShareContentChanged.stream, - builder: (_, __) { - final selectMode = Matrix.of(context).shareContent != null - ? SelectMode.share - : _selectedRoomIds.isEmpty - ? SelectMode.normal - : SelectMode.select; - return Scaffold( - appBar: appBar ?? - AppBar( - elevation: AdaptivePageLayout.of(context).columnMode(context) - ? 1 - : null, - leading: selectMode == SelectMode.normal - ? null - : IconButton( - tooltip: L10n.of(context).cancel, - icon: Icon(Icons.close_outlined), - onPressed: () => selectMode == SelectMode.share - ? setState( - () => Matrix.of(context).shareContent = null) - : setState(() => _selectedRoomIds.clear()), - ), - centerTitle: false, - actions: selectMode == SelectMode.share - ? null - : selectMode == SelectMode.select - ? [ - if (_selectedRoomIds.length == 1) - IconButton( - tooltip: L10n.of(context).toggleUnread, - icon: Icon(Matrix.of(context) - .client - .getRoomById(_selectedRoomIds.single) - .isUnread - ? Icons.mark_chat_read_outlined - : Icons.mark_chat_unread_outlined), - onPressed: () => _toggleUnread(context), - ), - if (_selectedRoomIds.length == 1) - IconButton( - tooltip: L10n.of(context).toggleFavorite, - icon: Icon(Icons.push_pin_outlined), - onPressed: () => - _toggleFavouriteRoom(context), - ), - if (_selectedRoomIds.length == 1) - IconButton( - icon: Icon(Matrix.of(context) - .client - .getRoomById( - _selectedRoomIds.single) - .pushRuleState == - PushRuleState.notify - ? Icons.notifications_off_outlined - : Icons.notifications_outlined), - tooltip: L10n.of(context).toggleMuted, - onPressed: () => _toggleMuted(context), - ), - IconButton( - icon: Icon(Icons.archive_outlined), - tooltip: L10n.of(context).archive, - onPressed: () => _archiveAction(context), - ), - ] - : [ - IconButton( - icon: Icon(Icons.search_outlined), - tooltip: L10n.of(context).search, - onPressed: () => AdaptivePageLayout.of(context) - .pushNamed('/search'), - ), - PopupMenuButton( - onSelected: (action) { - switch (action) { - case PopupMenuAction.setStatus: - _setStatus(context); - break; - case PopupMenuAction.settings: - AdaptivePageLayout.of(context) - .pushNamed('/settings'); - break; - case PopupMenuAction.invite: - FluffyShare.share( - L10n.of(context).inviteText( - Matrix.of(context).client.userID, - 'https://matrix.to/#/${Matrix.of(context).client.userID}'), - context); - break; - case PopupMenuAction.newGroup: - AdaptivePageLayout.of(context) - .pushNamed('/newgroup'); - break; - case PopupMenuAction.archive: - AdaptivePageLayout.of(context) - .pushNamed('/archive'); - break; - } - }, - itemBuilder: (_) => [ - PopupMenuItem( - value: PopupMenuAction.setStatus, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.edit_outlined), - SizedBox(width: 12), - Text(L10n.of(context).setStatus), - ], - ), - ), - PopupMenuItem( - value: PopupMenuAction.newGroup, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.group_add_outlined), - SizedBox(width: 12), - Text(L10n.of(context).createNewGroup), - ], - ), - ), - PopupMenuItem( - value: PopupMenuAction.invite, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.share_outlined), - SizedBox(width: 12), - Text(L10n.of(context).inviteContact), - ], - ), - ), - PopupMenuItem( - value: PopupMenuAction.archive, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.archive_outlined), - SizedBox(width: 12), - Text(L10n.of(context).archive), - ], - ), - ), - PopupMenuItem( - value: PopupMenuAction.settings, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.settings_outlined), - SizedBox(width: 12), - Text(L10n.of(context).settings), - ], - ), - ), - ], - ), - ], - title: Text(selectMode == SelectMode.share - ? L10n.of(context).share - : selectMode == SelectMode.select - ? L10n.of(context).numberSelected( - _selectedRoomIds.length.toString()) - : AppConfig.applicationName), - ), - body: Column(children: [ - ConnectionStatusHeader(), - Expanded( - child: StreamBuilder( - stream: Matrix.of(context) - .client - .onSync - .stream - .where((s) => s.hasRoomUpdate), - builder: (context, snapshot) { - return FutureBuilder( - future: waitForFirstSync(context), - builder: (BuildContext context, snapshot) { - if (snapshot.hasData) { - final rooms = List.from( - Matrix.of(context).client.rooms); - rooms.removeWhere((room) => room.lastEvent == null); - if (rooms.isEmpty) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.maps_ugc_outlined, - size: 80, - color: Colors.grey, - ), - Center( - child: Text( - L10n.of(context).startYourFirstChat, - textAlign: TextAlign.start, - style: TextStyle( - color: Colors.grey, - fontSize: 16, - ), - ), - ), - ], - ); - } - final totalCount = rooms.length; - return ListView.builder( - itemCount: totalCount, - itemBuilder: (BuildContext context, int i) => - ChatListItem( - rooms[i], - selected: - _selectedRoomIds.contains(rooms[i].id), - onTap: selectMode == SelectMode.select - ? () => _toggleSelection(rooms[i].id) - : null, - onLongPress: () => - _toggleSelection(rooms[i].id), - activeChat: widget.activeChat == rooms[i].id, - ), - ); - } else { - return Center( - child: CircularProgressIndicator(), - ); - } - }, - ); - }), - ), - ]), - floatingActionButton: selectMode == SelectMode.normal - ? FloatingActionButton( - onPressed: () => AdaptivePageLayout.of(context) - .pushNamedAndRemoveUntilIsFirst('/newprivatechat'), - child: Icon(CupertinoIcons.chat_bubble), - ) - : null, - ); - }); - } -} - -enum ChatListPopupMenuItemActions { - createGroup, - discover, - setStatus, - inviteContact, - settings, -} diff --git a/lib/views/chat_list_view.dart b/lib/views/chat_list_view.dart new file mode 100644 index 00000000..ebe750fe --- /dev/null +++ b/lib/views/chat_list_view.dart @@ -0,0 +1,242 @@ +import 'package:adaptive_page_layout/adaptive_page_layout.dart'; +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:fluffychat/controllers/chat_list_controller.dart'; +import 'package:fluffychat/views/widgets/connection_status_header.dart'; +import 'package:fluffychat/views/widgets/list_items/chat_list_item.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:fluffychat/config/app_config.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'widgets/matrix.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +class ChatListView extends StatelessWidget { + final ChatListController controller; + + const ChatListView(this.controller, {Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: Matrix.of(context).onShareContentChanged.stream, + builder: (_, __) { + final selectMode = Matrix.of(context).shareContent != null + ? SelectMode.share + : controller.selectedRoomIds.isEmpty + ? SelectMode.normal + : SelectMode.select; + return Scaffold( + appBar: AppBar( + elevation: + AdaptivePageLayout.of(context).columnMode(context) ? 1 : null, + leading: selectMode == SelectMode.normal + ? null + : IconButton( + tooltip: L10n.of(context).cancel, + icon: Icon(Icons.close_outlined), + onPressed: controller.cancelAction, + ), + centerTitle: false, + actions: selectMode == SelectMode.share + ? null + : selectMode == SelectMode.select + ? [ + if (controller.selectedRoomIds.length == 1) + IconButton( + tooltip: L10n.of(context).toggleUnread, + icon: Icon(Matrix.of(context) + .client + .getRoomById( + controller.selectedRoomIds.single) + .isUnread + ? Icons.mark_chat_read_outlined + : Icons.mark_chat_unread_outlined), + onPressed: controller.toggleUnread, + ), + if (controller.selectedRoomIds.length == 1) + IconButton( + tooltip: L10n.of(context).toggleFavorite, + icon: Icon(Icons.push_pin_outlined), + onPressed: controller.toggleFavouriteRoom, + ), + if (controller.selectedRoomIds.length == 1) + IconButton( + icon: Icon(Matrix.of(context) + .client + .getRoomById( + controller.selectedRoomIds.single) + .pushRuleState == + PushRuleState.notify + ? Icons.notifications_off_outlined + : Icons.notifications_outlined), + tooltip: L10n.of(context).toggleMuted, + onPressed: controller.toggleMuted, + ), + IconButton( + icon: Icon(Icons.archive_outlined), + tooltip: L10n.of(context).archive, + onPressed: controller.archiveAction, + ), + ] + : [ + IconButton( + icon: Icon(Icons.search_outlined), + tooltip: L10n.of(context).search, + onPressed: () => AdaptivePageLayout.of(context) + .pushNamed('/search'), + ), + PopupMenuButton( + onSelected: controller.onPopupMenuSelect, + itemBuilder: (_) => [ + PopupMenuItem( + value: PopupMenuAction.setStatus, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.edit_outlined), + SizedBox(width: 12), + Text(L10n.of(context).setStatus), + ], + ), + ), + PopupMenuItem( + value: PopupMenuAction.newGroup, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.group_add_outlined), + SizedBox(width: 12), + Text(L10n.of(context).createNewGroup), + ], + ), + ), + PopupMenuItem( + value: PopupMenuAction.invite, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.share_outlined), + SizedBox(width: 12), + Text(L10n.of(context).inviteContact), + ], + ), + ), + PopupMenuItem( + value: PopupMenuAction.archive, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.archive_outlined), + SizedBox(width: 12), + Text(L10n.of(context).archive), + ], + ), + ), + PopupMenuItem( + value: PopupMenuAction.settings, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.settings_outlined), + SizedBox(width: 12), + Text(L10n.of(context).settings), + ], + ), + ), + ], + ), + ], + title: Text(selectMode == SelectMode.share + ? L10n.of(context).share + : selectMode == SelectMode.select + ? L10n.of(context).numberSelected( + controller.selectedRoomIds.length.toString()) + : AppConfig.applicationName), + ), + body: Column(children: [ + ConnectionStatusHeader(), + Expanded( + child: StreamBuilder( + stream: Matrix.of(context) + .client + .onSync + .stream + .where((s) => s.hasRoomUpdate), + builder: (context, snapshot) { + return FutureBuilder( + future: controller.waitForFirstSync(), + builder: (BuildContext context, snapshot) { + if (snapshot.hasData) { + final rooms = List.from( + Matrix.of(context).client.rooms); + rooms.removeWhere((room) => room.lastEvent == null); + if (rooms.isEmpty) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.maps_ugc_outlined, + size: 80, + color: Colors.grey, + ), + Center( + child: Text( + L10n.of(context).startYourFirstChat, + textAlign: TextAlign.start, + style: TextStyle( + color: Colors.grey, + fontSize: 16, + ), + ), + ), + ], + ); + } + final totalCount = rooms.length; + return ListView.builder( + itemCount: totalCount, + itemBuilder: (BuildContext context, int i) => + ChatListItem( + rooms[i], + selected: controller.selectedRoomIds + .contains(rooms[i].id), + onTap: selectMode == SelectMode.select + ? () => + controller.toggleSelection(rooms[i].id) + : null, + onLongPress: () => + controller.toggleSelection(rooms[i].id), + activeChat: + controller.widget.activeChat == rooms[i].id, + ), + ); + } else { + return Center( + child: CircularProgressIndicator(), + ); + } + }, + ); + }), + ), + ]), + floatingActionButton: selectMode == SelectMode.normal + ? FloatingActionButton( + onPressed: () => AdaptivePageLayout.of(context) + .pushNamedAndRemoveUntilIsFirst('/newprivatechat'), + child: Icon(CupertinoIcons.chat_bubble), + ) + : null, + ); + }); + } +} + +enum ChatListPopupMenuItemActions { + createGroup, + discover, + setStatus, + inviteContact, + settings, +}