From 7b56f1d5cddb7108414129bed6e741e9c90eda4a Mon Sep 17 00:00:00 2001 From: Krille Fear Date: Sun, 14 Nov 2021 18:47:18 +0100 Subject: [PATCH] feat: Redesign multiaccounts and spaces --- lib/pages/chat_list/chat_list.dart | 12 +- lib/pages/chat_list/chat_list_view.dart | 206 +----------------- .../chat_list/client_chooser_button.dart | 104 +++++++++ lib/pages/chat_list/spaces_bottom_bar.dart | 56 +++++ lib/pages/search/search.dart | 2 +- lib/utils/url_launcher.dart | 2 +- 6 files changed, 174 insertions(+), 208 deletions(-) create mode 100644 lib/pages/chat_list/client_chooser_button.dart create mode 100644 lib/pages/chat_list/spaces_bottom_bar.dart diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 60be9cfe0..82fa2d67b 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -63,12 +63,10 @@ class ChatListController extends State { } void setActiveSpaceId(BuildContext context, String spaceId) { - Scaffold.of(context).openEndDrawer(); setState(() => _activeSpaceId = spaceId); } void editSpace(BuildContext context, String spaceId) async { - Scaffold.of(context).openEndDrawer(); await Matrix.of(context).client.getRoomById(spaceId).postLoad(); VRouter.of(context).toSegments(['spaces', spaceId]); } @@ -412,6 +410,7 @@ class ChatListController extends State { } void setActiveClient(Client client) { + if (client == null) return; VRouter.of(context).to('/rooms'); setState(() { _activeSpaceId = null; @@ -435,7 +434,7 @@ class ChatListController extends State { }); } - void editBundlesForAccount(String userId) async { + void editBundlesForAccount(String userId, String activeBundle) async { final client = Matrix.of(context) .widget .clients[Matrix.of(context).getClientIndexByMatrixId(userId)]; @@ -447,7 +446,7 @@ class ChatListController extends State { key: EditBundleAction.addToBundle, label: L10n.of(context).addToBundle, ), - if (Matrix.of(context).activeBundle != null) + if (activeBundle != client.userID) AlertDialogAction( key: EditBundleAction.removeFromBundle, label: L10n.of(context).removeFromBundle, @@ -463,7 +462,7 @@ class ChatListController extends State { textFields: [ DialogTextField(hintText: L10n.of(context).bundleName) ]); - if (bundle.isEmpty && bundle.single.isEmpty) return; + if (bundle == null || bundle.isEmpty || bundle.single.isEmpty) return; await showFutureLoadingDialog( context: context, future: () => client.setAccountBundle(bundle.single), @@ -472,8 +471,7 @@ class ChatListController extends State { case EditBundleAction.removeFromBundle: await showFutureLoadingDialog( context: context, - future: () => - client.removeFromAccountBundle(Matrix.of(context).activeBundle), + future: () => client.removeFromAccountBundle(activeBundle), ); } } diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index 3899ff4fb..52f07e07e 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -1,22 +1,18 @@ -import 'dart:math'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:async/async.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; -import 'package:fluffychat/widgets/avatar.dart'; +import 'package:fluffychat/pages/chat_list/client_chooser_button.dart'; +import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart'; import 'package:fluffychat/widgets/connection_status_header.dart'; -import '../../utils/account_bundles.dart'; import '../../utils/stream_extension.dart'; import '../../widgets/matrix.dart'; @@ -25,59 +21,6 @@ class ChatListView extends StatelessWidget { const ChatListView(this.controller, {Key key}) : super(key: key); - List getBottomBarItems(BuildContext context) { - final displayClients = Matrix.of(context).currentBundle; - if (displayClients.isEmpty) { - displayClients.addAll(Matrix.of(context).widget.clients); - controller.resetActiveBundle(); - } - final items = displayClients.map((client) { - return BottomNavigationBarItem( - label: client.userID, - icon: FutureBuilder( - future: client.ownProfile, - builder: (context, snapshot) { - return InkWell( - borderRadius: BorderRadius.circular(32), - onTap: () => controller.setActiveClient(client), - onLongPress: () => - controller.editBundlesForAccount(client.userID), - child: Avatar( - snapshot.data?.avatarUrl, - snapshot.data?.displayName ?? client.userID.localpart, - size: 32, - ), - ); - }), - ); - }).toList(); - - if (controller.displayBundles && false) { - items.insert( - 0, - BottomNavigationBarItem( - label: 'Bundles', - icon: PopupMenuButton( - icon: Icon( - Icons.menu, - color: Theme.of(context).textTheme.bodyText1.color, - ), - onSelected: controller.setActiveBundle, - itemBuilder: (context) => Matrix.of(context) - .accountBundles - .keys - .map( - (bundle) => PopupMenuItem( - value: bundle, - child: Text(bundle), - ), - ) - .toList(), - ))); - } - return items; - } - @override Widget build(BuildContext context) { return StreamBuilder( @@ -99,13 +42,9 @@ class ChatListView extends StatelessWidget { : Theme.of(context).colorScheme.primary, ), leading: selectMode == SelectMode.normal - ? controller.spaces.isEmpty - ? null - : Builder( - builder: (context) => IconButton( - icon: const Icon(Icons.group_work_outlined), - onPressed: Scaffold.of(context).openDrawer, - )) + ? Matrix.of(context).isMultiAccount + ? ClientChooserButton(controller) + : null : IconButton( tooltip: L10n.of(context).cancel, icon: const Icon(Icons.close_outlined), @@ -113,7 +52,6 @@ class ChatListView extends StatelessWidget { color: Theme.of(context).colorScheme.primary, ), centerTitle: false, - titleSpacing: controller.spaces.isEmpty ? null : 0, actions: selectMode == SelectMode.share ? null : selectMode == SelectMode.select @@ -275,139 +213,9 @@ class ChatListView extends StatelessWidget { child: const Icon(CupertinoIcons.chat_bubble), ) : null, - bottomNavigationBar: Matrix.of(context).isMultiAccount - ? StreamBuilder( - stream: StreamGroup.merge(Matrix.of(context) - .widget - .clients - .map((client) => client.onSync.stream.where((s) => - s.accountData != null && - s.accountData - .any((e) => e.type == accountBundlesType)))), - builder: (context, _) => Material( - color: Theme.of(context) - .bottomNavigationBarTheme - .backgroundColor, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Divider(height: 1), - Builder(builder: (context) { - final items = getBottomBarItems(context); - if (items.length == 1) { - return Padding( - padding: const EdgeInsets.all(7.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - items.single.icon, - Text(items.single.label), - ], - ), - ); - } - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SizedBox( - width: max( - FluffyThemes.isColumnMode(context) - ? FluffyThemes.columnWidth - : MediaQuery.of(context).size.width, - Matrix.of(context).widget.clients.length * - 84.0, - ), - child: BottomNavigationBar( - elevation: 0, - onTap: (i) => controller.setActiveClient( - Matrix.of(context).currentBundle[i]), - currentIndex: Matrix.of(context) - .currentBundle - .indexWhere( - (client) => - client == - Matrix.of(context).client, - ), - showUnselectedLabels: false, - showSelectedLabels: true, - type: BottomNavigationBarType.shifting, - selectedItemColor: - Theme.of(context).colorScheme.secondary, - items: items, - ), - ), - ); - }), - if (controller.displayBundles) - Padding( - padding: const EdgeInsets.symmetric( - vertical: 4.0, - horizontal: 12, - ), - child: SizedBox( - width: double.infinity, - child: CupertinoSlidingSegmentedControl( - groupValue: controller.secureActiveBundle, - onValueChanged: controller.setActiveBundle, - children: Map.fromEntries(Matrix.of(context) - .accountBundles - .keys - .map((bundle) => - MapEntry(bundle, Text(bundle)))), - ), - ), - ), - ], - ), - ), - ) - : null, - drawer: controller.spaces.isEmpty + bottomNavigationBar: controller.spaces.isEmpty ? null - : Drawer( - child: SafeArea( - child: ListView.builder( - itemCount: controller.spaces.length + 1, - itemBuilder: (context, i) { - if (i == 0) { - return ListTile( - selected: controller.activeSpaceId == null, - selectedTileColor: - Theme.of(context).secondaryHeaderColor, - leading: CircleAvatar( - foregroundColor: Colors.white, - backgroundColor: - Theme.of(context).primaryColor, - radius: Avatar.defaultSize / 2, - child: const Icon(Icons.home_outlined), - ), - title: Text(L10n.of(context).allChats), - onTap: () => - controller.setActiveSpaceId(context, null), - ); - } - final space = controller.spaces[i - 1]; - return ListTile( - selected: controller.activeSpaceId == space.id, - selectedTileColor: - Theme.of(context).secondaryHeaderColor, - leading: Avatar(space.avatar, space.displayname), - title: Text(space.displayname, maxLines: 1), - subtitle: Text(L10n.of(context).countParticipants( - (space.summary.mJoinedMemberCount + - space.summary.mInvitedMemberCount) - .toString())), - onTap: () => controller.setActiveSpaceId( - context, space.id), - trailing: IconButton( - icon: const Icon(Icons.edit_outlined), - onPressed: () => - controller.editSpace(context, space.id), - ), - ); - }, - ), - ), - ), + : SpacesBottomBar(controller), ), ); }); diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart new file mode 100644 index 000000000..190989ce7 --- /dev/null +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; + +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/widgets/avatar.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'chat_list.dart'; + +class ClientChooserButton extends StatelessWidget { + final ChatListController controller; + const ClientChooserButton(this.controller, {Key key}) : super(key: key); + + List> _bundleMenuItems(BuildContext context) { + final matrix = Matrix.of(context); + final bundles = matrix.accountBundles.keys.toList() + ..sort((a, b) => a.isValidMatrixId == b.isValidMatrixId + ? 0 + : a.isValidMatrixId && !b.isValidMatrixId + ? -1 + : 1); + return >[ + for (final bundle in bundles) ...[ + if (matrix.accountBundles[bundle].length != 1 || + matrix.accountBundles[bundle].single.userID != bundle) + PopupMenuItem( + value: null, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + bundle, + style: TextStyle( + color: Theme.of(context).textTheme.subtitle1.color, + fontSize: 14, + ), + ), + const Divider(height: 1), + ], + ), + ), + ...matrix.accountBundles[bundle] + .map( + (client) => PopupMenuItem( + value: client, + child: FutureBuilder( + future: client.ownProfile, + builder: (context, snapshot) => Row( + children: [ + Avatar( + snapshot.data?.avatarUrl, + snapshot.data?.displayName ?? client.userID.localpart, + size: 28, + fontSize: 12, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + snapshot.data?.displayName ?? client.userID.localpart, + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 12), + IconButton( + icon: const Icon(Icons.edit_outlined), + onPressed: () => controller.editBundlesForAccount( + client.userID, bundle), + ), + ], + ), + ), + ), + ) + .toList(), + ], + ]; + } + + @override + Widget build(BuildContext context) { + final matrix = Matrix.of(context); + return Center( + child: FutureBuilder( + future: matrix.client.ownProfile, + builder: (context, snapshot) => PopupMenuButton( + child: Avatar( + snapshot.data?.avatarUrl, + snapshot.data?.displayName ?? matrix.client.userID.localpart, + size: 28, + fontSize: 12, + ), + onSelected: (Object object) { + if (object is Client) { + controller.setActiveClient(object); + } else if (object is String) { + controller.setActiveBundle(object); + } + }, + itemBuilder: _bundleMenuItems, + ), + ), + ); + } +} diff --git a/lib/pages/chat_list/spaces_bottom_bar.dart b/lib/pages/chat_list/spaces_bottom_bar.dart new file mode 100644 index 000000000..0bddde3c4 --- /dev/null +++ b/lib/pages/chat_list/spaces_bottom_bar.dart @@ -0,0 +1,56 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +import 'package:fluffychat/pages/chat_list/chat_list.dart'; +import 'package:fluffychat/widgets/avatar.dart'; + +class SpacesBottomBar extends StatelessWidget { + final ChatListController controller; + const SpacesBottomBar(this.controller, {Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final currentIndex = controller.activeSpaceId == null + ? 0 + : controller.spaces + .indexWhere((space) => controller.activeSpaceId == space.id) + + 1; + return BottomNavigationBar( + currentIndex: currentIndex, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + landscapeLayout: BottomNavigationBarLandscapeLayout.spread, + onTap: (i) => controller.setActiveSpaceId( + context, + i == 0 ? null : controller.spaces[i - 1].id, + ), + items: [ + BottomNavigationBarItem( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + icon: const Icon(CupertinoIcons.chat_bubble_2), + label: L10n.of(context).allChats, + ), + ...controller.spaces + .map((space) => BottomNavigationBarItem( + icon: InkWell( + borderRadius: BorderRadius.circular(28), + onTap: () => controller.setActiveSpaceId( + context, + space.id, + ), + onLongPress: () => controller.editSpace(context, space.id), + child: Avatar( + space.avatar, + space.displayname, + size: 24, + fontSize: 12, + ), + ), + label: space.displayname, + )) + .toList(), + ], + ); + } +} diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index 96cedb521..9ac13bd7d 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; import 'package:flutter/material.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart'; @@ -9,6 +8,7 @@ import 'package:matrix/matrix.dart'; import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; import 'search_view.dart'; class Search extends StatefulWidget { diff --git a/lib/utils/url_launcher.dart b/lib/utils/url_launcher.dart index 6b54096ce..8b04a67c5 100644 --- a/lib/utils/url_launcher.dart +++ b/lib/utils/url_launcher.dart @@ -1,4 +1,3 @@ -import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; import 'package:flutter/material.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart'; @@ -12,6 +11,7 @@ import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/profile_bottom_sheet.dart'; +import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; import 'platform_infos.dart'; class UrlLauncher {