From d3f87aeaad9234d4012768ead37883e9aa3fd6d3 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Sun, 1 Aug 2021 07:45:34 +0200 Subject: [PATCH] feat: Light implementation of spaces drawer --- lib/main.dart | 3 + lib/pages/chat_list.dart | 11 + lib/pages/views/chat_list_view.dart | 589 +++++++++++++++------------- 3 files changed, 335 insertions(+), 268 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 0177df27..5d5bd61e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -44,6 +44,9 @@ void main() async { KeyVerificationMethod.emoji, }, importantStateEvents: { + EventTypes.spaceChild, + EventTypes.spaceParent, + EventTypes.RoomCreate, 'im.ponies.room_emotes', // we want emotes to work properly }, databaseBuilder: FlutterMatrixHiveStore.hiveDatabaseBuilder, diff --git a/lib/pages/chat_list.dart b/lib/pages/chat_list.dart index 6eae58cc..aa0eb155 100644 --- a/lib/pages/chat_list.dart +++ b/lib/pages/chat_list.dart @@ -40,6 +40,17 @@ class ChatListController extends State { StreamSubscription _intentUriStreamSubscription; + String _activeSpaceId; + String get activeSpaceId => _activeSpaceId; + + void setActiveSpaceId(BuildContext context, String spaceId) { + Scaffold.of(context).openEndDrawer(); + setState(() => _activeSpaceId = spaceId); + } + + List get spaces => + Matrix.of(context).client.rooms.where((r) => r.isSpace).toList(); + final selectedRoomIds = {}; Future crossSigningCachedFuture; bool crossSigningCached; diff --git a/lib/pages/views/chat_list_view.dart b/lib/pages/views/chat_list_view.dart index 8d35cb75..18281020 100644 --- a/lib/pages/views/chat_list_view.dart +++ b/lib/pages/views/chat_list_view.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/widgets/avatar.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/pages/chat_list.dart'; import 'package:fluffychat/widgets/connection_status_header.dart'; @@ -23,285 +24,337 @@ class ChatListView extends StatelessWidget { builder: (_, __) { final selectMode = controller.selectMode; return VWidgetGuard( - onSystemPop: (redirector) async { - final selMode = controller.selectMode; - if (selMode != SelectMode.normal) controller.cancelAction(); - if (selMode == SelectMode.select) redirector.stopRedirection(); - }, - child: Scaffold( - appBar: AppBar( - 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, - ), + onSystemPop: (redirector) async { + final selMode = controller.selectMode; + if (selMode != SelectMode.normal) controller.cancelAction(); + if (selMode == SelectMode.select) redirector.stopRedirection(); + }, + child: Scaffold( + appBar: AppBar( + leading: selectMode == SelectMode.normal + ? controller.spaces.isEmpty + ? null + : Builder( + builder: (context) => IconButton( + icon: Icon(Icons.group_work_outlined), + onPressed: Scaffold.of(context).openDrawer, + )) + : IconButton( + tooltip: L10n.of(context).cancel, + icon: Icon(Icons.close_outlined), + onPressed: controller.cancelAction, + ), + centerTitle: false, + titleSpacing: controller.spaces.isEmpty ? null : 0, + actions: selectMode == SelectMode.share + ? null + : selectMode == SelectMode.select + ? [ + if (controller.selectedRoomIds.length == 1) IconButton( - icon: Icon(Icons.archive_outlined), - tooltip: L10n.of(context).archive, - onPressed: controller.archiveAction, + 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( - icon: Icon(Icons.search_outlined), - tooltip: L10n.of(context).search, - onPressed: () => - VRouter.of(context).to('/search'), + tooltip: L10n.of(context).toggleFavorite, + icon: Icon(Icons.push_pin_outlined), + onPressed: controller.toggleFavouriteRoom, ), - 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), - ], - ), - ), - ], + 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, ), - ], - 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) - .rateLimit(Duration(seconds: 1)), - builder: (context, snapshot) { - return FutureBuilder( - future: controller.waitForFirstSync(), - builder: (BuildContext context, snapshot) { - if (Matrix.of(context).client.prevBatch != null) { - 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 + 1; - return ListView.builder( - itemCount: totalCount, - itemBuilder: (BuildContext context, int i) { - if (i == 0) { - return FutureBuilder( - future: controller - .crossSigningCachedFuture, - builder: (context, snapshot) { - final needsBootstrap = - Matrix.of(context) - .client - .encryption - ?.crossSigning - ?.enabled == - false || - snapshot.data == false; - final isUnknownSession = - Matrix.of(context) - .client - .isUnknownSession; - final displayHeader = - needsBootstrap || - isUnknownSession; - if (!displayHeader || - controller - .hideChatBackupBanner) { - return Container(); - } - return Material( - color: Theme.of(context) - .secondaryHeaderColor, - child: ListTile( - leading: CircleAvatar( - backgroundColor: Theme.of( - context) - .scaffoldBackgroundColor, - foregroundColor: - Theme.of(context) - .colorScheme - .secondaryVariant, - child: Icon(Icons.cloud), - ), - trailing: IconButton( - icon: Icon(Icons.close), - onPressed: controller - .hideChatBackupBannerAction, - ), - title: Text(L10n.of(context) - .chatBackup), - subtitle: Text(L10n.of(context) - .enableChatBackup), - onTap: controller - .firstRunBootstrapAction, - ), - ); - }); - } - i--; - return 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.activeChat == rooms[i].id, - ); - }, - ); - } else { - return Center( - child: Column( + 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: () => + VRouter.of(context).to('/search'), + ), + PopupMenuButton( + onSelected: controller.onPopupMenuSelect, + itemBuilder: (_) => [ + PopupMenuItem( + value: PopupMenuAction.setStatus, + child: Row( mainAxisSize: MainAxisSize.min, children: [ - Image.asset( - 'assets/private_chat_wallpaper.png', - width: 100, - ), - Text( - L10n.of(context) - .yourChatsAreBeingSynced, - textAlign: TextAlign.center, - ), + 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()) + : controller.activeSpaceId == null + ? AppConfig.applicationName + : Matrix.of(context) + .client + .getRoomById(controller.activeSpaceId) + .displayname), + ), + body: Column(children: [ + ConnectionStatusHeader(), + Expanded( + child: StreamBuilder( + stream: Matrix.of(context) + .client + .onSync + .stream + .where((s) => s.hasRoomUpdate) + .rateLimit(Duration(seconds: 1)), + builder: (context, snapshot) { + return FutureBuilder( + future: controller.waitForFirstSync(), + builder: (BuildContext context, snapshot) { + if (Matrix.of(context).client.prevBatch != null) { + final rooms = List.from( + Matrix.of(context).client.rooms) + .where((r) => !r.isSpace) + .toList(); + if (controller.activeSpaceId != null) { + rooms.removeWhere((room) => !room.spaceParents + .any((parent) => + parent.roomId == + controller.activeSpaceId)); + } + 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, + ), + ), + ), + ], ); } - }, - ); - }), - ), - ]), - floatingActionButton: selectMode == SelectMode.normal - ? FloatingActionButton( - heroTag: 'main_fab', - onPressed: () => - VRouter.of(context).to('/newprivatechat'), - child: Icon(CupertinoIcons.chat_bubble), - ) - : null, - )); + final totalCount = rooms.length + 1; + return ListView.builder( + itemCount: totalCount, + itemBuilder: (BuildContext context, int i) { + if (i == 0) { + return FutureBuilder( + future: + controller.crossSigningCachedFuture, + builder: (context, snapshot) { + final needsBootstrap = + Matrix.of(context) + .client + .encryption + ?.crossSigning + ?.enabled == + false || + snapshot.data == false; + final isUnknownSession = + Matrix.of(context) + .client + .isUnknownSession; + final displayHeader = + needsBootstrap || + isUnknownSession; + if (!displayHeader || + controller.hideChatBackupBanner) { + return Container(); + } + return Material( + color: Theme.of(context) + .secondaryHeaderColor, + child: ListTile( + leading: CircleAvatar( + backgroundColor: Theme.of( + context) + .scaffoldBackgroundColor, + foregroundColor: + Theme.of(context) + .colorScheme + .secondaryVariant, + child: Icon(Icons.cloud), + ), + trailing: IconButton( + icon: Icon(Icons.close), + onPressed: controller + .hideChatBackupBannerAction, + ), + title: Text( + L10n.of(context).chatBackup), + subtitle: Text(L10n.of(context) + .enableChatBackup), + onTap: controller + .firstRunBootstrapAction, + ), + ); + }); + } + i--; + return 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.activeChat == rooms[i].id, + ); + }, + ); + } else { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + 'assets/private_chat_wallpaper.png', + width: 100, + ), + Text( + L10n.of(context).yourChatsAreBeingSynced, + textAlign: TextAlign.center, + ), + ], + ), + ); + } + }, + ); + }), + ), + ]), + floatingActionButton: selectMode == SelectMode.normal + ? FloatingActionButton( + heroTag: 'main_fab', + onPressed: () => + VRouter.of(context).to('/newprivatechat'), + child: Icon(CupertinoIcons.chat_bubble), + ) + : null, + drawer: controller.spaces.isEmpty + ? null + : Drawer( + child: SafeArea( + child: ListView.builder( + itemCount: controller.spaces.length + 1, + itemBuilder: (context, i) { + if (i == 0) { + return ListTile( + leading: CircleAvatar( + foregroundColor: + Theme.of(context).primaryColor, + backgroundColor: + Theme.of(context).secondaryHeaderColor, + radius: Avatar.defaultSize / 2, + child: Icon(Icons.home_outlined), + ), + title: Text(L10n.of(context).chats), + onTap: () => + controller.setActiveSpaceId(context, null), + ); + } + final space = controller.spaces[i - 1]; + return ListTile( + leading: Avatar(space.avatar, space.displayname), + title: Text(space.displayname), + onTap: () => controller.setActiveSpaceId( + context, space.id), + ); + }, + ), + ), + ), + ), + ); }); } }