diff --git a/lib/config/themes.dart b/lib/config/themes.dart index 5fa138f8d..8a7f55483 100644 --- a/lib/config/themes.dart +++ b/lib/config/themes.dart @@ -1,13 +1,24 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:vrouter/vrouter.dart'; + import 'package:fluffychat/utils/platform_infos.dart'; +import '../widgets/matrix.dart'; import 'app_config.dart'; abstract class FluffyThemes { static const double columnWidth = 360.0; + + static bool isColumnModeByWidth(double width) => width > columnWidth * 2 + 64; + static bool isColumnMode(BuildContext context) => - MediaQuery.of(context).size.width > columnWidth * 2; + isColumnModeByWidth(MediaQuery.of(context).size.width); + + static bool getDisplayNavigationRail(BuildContext context) => + VRouter.of(context).path.startsWith('/rooms') && + (Matrix.of(context).client.rooms.any((room) => room.isSpace) || + AppConfig.separateChatTypes); static const fallbackTextStyle = TextStyle( fontFamily: 'Roboto', diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 0b9e082f4..edea41163 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -13,10 +13,12 @@ import 'package:uni_links/uni_links.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_view.dart'; import 'package:fluffychat/pages/chat_list/spaces_entry.dart'; import 'package:fluffychat/utils/famedlysdk_store.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions.dart/client_stories_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/space_navigator.dart'; import '../../../utils/account_bundles.dart'; @@ -30,7 +32,11 @@ import '../settings_account/settings_account.dart'; import 'package:fluffychat/utils/tor_stub.dart' if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart'; -enum SelectMode { normal, share, select } +enum SelectMode { + normal, + share, + select, +} enum PopupMenuAction { settings, @@ -41,6 +47,13 @@ enum PopupMenuAction { archive, } +enum ActiveFilter { + allChats, + groups, + messages, + spaces, +} + class ChatList extends StatefulWidget { const ChatList({Key? key}) : super(key: key); @@ -58,6 +71,92 @@ class ChatListController extends State SpacesEntry? _activeSpacesEntry; + bool get displayNavigationBar => + !FluffyThemes.isColumnMode(context) && + (spaces.isNotEmpty || AppConfig.separateChatTypes); + + int get selectedIndex { + switch (activeFilter) { + case ActiveFilter.allChats: + return 0; + case ActiveFilter.groups: + return 0; + case ActiveFilter.messages: + return 1; + case ActiveFilter.spaces: + return AppConfig.separateChatTypes ? 2 : 1; + } + } + + void onDestinationSelected(int? i) { + switch (i) { + case 0: + if (AppConfig.separateChatTypes) { + setState(() { + activeFilter = ActiveFilter.groups; + }); + } else { + setState(() { + activeFilter = ActiveFilter.allChats; + }); + } + break; + case 1: + if (AppConfig.separateChatTypes) { + setState(() { + activeFilter = ActiveFilter.messages; + }); + } else { + setState(() { + activeFilter = ActiveFilter.spaces; + }); + } + break; + case 2: + setState(() { + activeFilter = ActiveFilter.spaces; + }); + break; + } + } + + ActiveFilter activeFilter = AppConfig.separateChatTypes + ? ActiveFilter.messages + : ActiveFilter.allChats; + + List get filteredRooms { + final rooms = Matrix.of(context).client.rooms; + switch (activeFilter) { + case ActiveFilter.allChats: + return rooms + .where((room) => + !room.isSpace && room.spaceParents.isEmpty && !room.isStoryRoom) + .toList(); + case ActiveFilter.groups: + return rooms + .where((room) => + !room.isSpace && + room.spaceParents.isEmpty && + !room.isDirectChat && + !room.isStoryRoom) + .toList(); + case ActiveFilter.messages: + return rooms + .where((room) => + !room.isSpace && + room.spaceParents.isEmpty && + room.isDirectChat && + !room.isStoryRoom) + .toList(); + case ActiveFilter.spaces: + return rooms + .where((room) => + (room.isSpace || room.spaceParents.isNotEmpty) && + !room.isStoryRoom) + .toList(); + } + } + bool isSearchMode = false; Future? publicRoomsResponse; String? searchServer; @@ -154,6 +253,7 @@ class ChatListController extends State bool isTorBrowser = false; + @Deprecated('') SpacesEntry get activeSpacesEntry { final id = _activeSpacesEntry; return (id == null || !id.stillValid(context)) ? defaultSpacesEntry : id; diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 26fa86db7..8d7bf4502 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -17,6 +17,7 @@ import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/profile_bottom_sheet.dart'; import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; import '../../utils/stream_extension.dart'; +import '../../widgets/connection_status_header.dart'; import '../../widgets/matrix.dart'; import 'spaces_hierarchy_proposal.dart'; @@ -57,146 +58,148 @@ class _ChatListViewBodyState extends State { Widget child; if (widget.controller.waitForFirstSync && Matrix.of(context).client.prevBatch != null) { - final rooms = widget.controller.activeSpacesEntry.getRooms(context); + final rooms = widget.controller.filteredRooms; - final displayStoriesHeader = widget.controller.activeSpacesEntry - .shouldShowStoriesHeader(context) || - rooms.isEmpty; + final displayStoriesHeader = { + ActiveFilter.allChats, + ActiveFilter.messages, + }.contains(widget.controller.activeFilter); child = ListView.builder( key: ValueKey(Matrix.of(context).client.userID.toString() + widget.controller.activeSpaceId.toString() + - widget.controller.activeSpacesEntry.runtimeType.toString()), + widget.controller.activeFilter.toString()), controller: widget.controller.scrollController, // add +1 space below in order to properly scroll below the spaces bar - itemCount: rooms.length + (displayStoriesHeader ? 2 : 1), + itemCount: rooms.length + 2, itemBuilder: (BuildContext context, int i) { - if (displayStoriesHeader) { - if (i == 0) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - SpaceRoomListTopBar(widget.controller), - if (roomSearchResult != null) ...[ - SearchTitle( - title: L10n.of(context)!.publicRooms, - icon: const Icon(Icons.explore_outlined), - ), - AnimatedContainer( - height: roomSearchResult.chunk.isEmpty ? 0 : 106, - duration: const Duration(milliseconds: 250), - clipBehavior: Clip.hardEdge, - decoration: const BoxDecoration(), - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: roomSearchResult.chunk.length, - itemBuilder: (context, i) => _SearchItem( - title: roomSearchResult.chunk[i].name ?? - roomSearchResult - .chunk[i].canonicalAlias?.localpart ?? - L10n.of(context)!.group, - avatar: roomSearchResult.chunk[i].avatarUrl, - onPressed: () => showModalBottomSheet( - context: context, - builder: (c) => PublicRoomBottomSheet( - roomAlias: - roomSearchResult.chunk[i].canonicalAlias ?? - roomSearchResult.chunk[i].roomId, - outerContext: context, - chunk: roomSearchResult.chunk[i], - ), + if (i == 0) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SpaceRoomListTopBar(widget.controller), + if (roomSearchResult != null) ...[ + SearchTitle( + title: L10n.of(context)!.publicRooms, + icon: const Icon(Icons.explore_outlined), + ), + AnimatedContainer( + height: roomSearchResult.chunk.isEmpty ? 0 : 106, + duration: const Duration(milliseconds: 250), + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: roomSearchResult.chunk.length, + itemBuilder: (context, i) => _SearchItem( + title: roomSearchResult.chunk[i].name ?? + roomSearchResult + .chunk[i].canonicalAlias?.localpart ?? + L10n.of(context)!.group, + avatar: roomSearchResult.chunk[i].avatarUrl, + onPressed: () => showModalBottomSheet( + context: context, + builder: (c) => PublicRoomBottomSheet( + roomAlias: + roomSearchResult.chunk[i].canonicalAlias ?? + roomSearchResult.chunk[i].roomId, + outerContext: context, + chunk: roomSearchResult.chunk[i], ), ), ), ), - ], - if (userSearchResult != null) ...[ - SearchTitle( - title: L10n.of(context)!.users, - icon: const Icon(Icons.group_outlined), - ), - AnimatedContainer( - height: userSearchResult.results.isEmpty ? 0 : 106, - duration: const Duration(milliseconds: 250), - clipBehavior: Clip.hardEdge, - decoration: const BoxDecoration(), - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: userSearchResult.results.length, - itemBuilder: (context, i) => _SearchItem( - title: userSearchResult.results[i].displayName ?? - userSearchResult.results[i].userId.localpart ?? - L10n.of(context)!.unknownDevice, - avatar: userSearchResult.results[i].avatarUrl, - onPressed: () => showModalBottomSheet( - context: context, - builder: (c) => ProfileBottomSheet( - userId: userSearchResult.results[i].userId, - outerContext: context, - ), + ), + ], + if (userSearchResult != null) ...[ + SearchTitle( + title: L10n.of(context)!.users, + icon: const Icon(Icons.group_outlined), + ), + AnimatedContainer( + height: userSearchResult.results.isEmpty ? 0 : 106, + duration: const Duration(milliseconds: 250), + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: userSearchResult.results.length, + itemBuilder: (context, i) => _SearchItem( + title: userSearchResult.results[i].displayName ?? + userSearchResult.results[i].userId.localpart ?? + L10n.of(context)!.unknownDevice, + avatar: userSearchResult.results[i].avatarUrl, + onPressed: () => showModalBottomSheet( + context: context, + builder: (c) => ProfileBottomSheet( + userId: userSearchResult.results[i].userId, + outerContext: context, ), ), ), ), - ], - if (widget.controller.isSearchMode) - SearchTitle( - title: L10n.of(context)!.stories, - icon: const Icon(Icons.camera_alt_outlined), - ), + ), + ], + if (widget.controller.isSearchMode) + SearchTitle( + title: L10n.of(context)!.stories, + icon: const Icon(Icons.camera_alt_outlined), + ), + if (displayStoriesHeader) StoriesHeader( filter: widget.controller.searchController.text, ), - AnimatedContainer( - height: widget.controller.isTorBrowser ? 64 : 0, - duration: const Duration(milliseconds: 300), - clipBehavior: Clip.hardEdge, - curve: Curves.bounceInOut, - decoration: const BoxDecoration(), - child: Material( - color: Theme.of(context).colorScheme.surface, - child: ListTile( - leading: const Icon(Icons.vpn_key), - title: Text(L10n.of(context)!.dehydrateTor), - subtitle: Text(L10n.of(context)!.dehydrateTorLong), - trailing: const Icon(Icons.chevron_right_outlined), - onTap: widget.controller.dehydrate, - ), + const ConnectionStatusHeader(), + AnimatedContainer( + height: widget.controller.isTorBrowser ? 64 : 0, + duration: const Duration(milliseconds: 300), + clipBehavior: Clip.hardEdge, + curve: Curves.bounceInOut, + decoration: const BoxDecoration(), + child: Material( + color: Theme.of(context).colorScheme.surface, + child: ListTile( + leading: const Icon(Icons.vpn_key), + title: Text(L10n.of(context)!.dehydrateTor), + subtitle: Text(L10n.of(context)!.dehydrateTorLong), + trailing: const Icon(Icons.chevron_right_outlined), + onTap: widget.controller.dehydrate, ), ), - if (widget.controller.isSearchMode) - SearchTitle( - title: L10n.of(context)!.chats, - icon: const Icon(Icons.chat_outlined), - ), - if (rooms.isEmpty && !widget.controller.isSearchMode) - Column( - key: const ValueKey(null), - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Image.asset( - 'assets/private_chat_wallpaper.png', - width: 160, - height: 160, - ), - Center( - child: Text( - L10n.of(context)!.startYourFirstChat, - textAlign: TextAlign.start, - style: const TextStyle( - color: Colors.grey, - fontSize: 16, - ), + ), + if (widget.controller.isSearchMode) + SearchTitle( + title: L10n.of(context)!.chats, + icon: const Icon(Icons.chat_outlined), + ), + if (rooms.isEmpty && !widget.controller.isSearchMode) + Column( + key: const ValueKey(null), + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + 'assets/private_chat_wallpaper.png', + width: 160, + height: 160, + ), + Center( + child: Text( + L10n.of(context)!.startYourFirstChat, + textAlign: TextAlign.start, + style: const TextStyle( + color: Colors.grey, + fontSize: 16, ), ), - const SizedBox(height: 16), - ], - ), - ], - ); - } - i--; + ), + const SizedBox(height: 16), + ], + ), + ], + ); } + i--; + if (i >= rooms.length) { return SpacesHierarchyProposals( space: widget.controller.activeSpacesEntry.getSpace(context)?.id, diff --git a/lib/pages/chat_list/chat_list_drawer.dart b/lib/pages/chat_list/chat_list_drawer.dart deleted file mode 100644 index a1c79b4fe..000000000 --- a/lib/pages/chat_list/chat_list_drawer.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:vrouter/vrouter.dart'; - -import 'package:fluffychat/pages/chat_list/chat_list.dart'; -import 'package:fluffychat/pages/chat_list/spaces_drawer.dart'; -import 'package:fluffychat/utils/fluffy_share.dart'; -import 'package:fluffychat/widgets/avatar.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import '../../config/app_config.dart'; - -class ChatListDrawer extends StatelessWidget { - final ChatListController controller; - const ChatListDrawer(this.controller, {Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => Drawer( - child: SafeArea( - child: Column( - children: [ - ListTile( - leading: const CircleAvatar( - radius: Avatar.defaultSize / 2, - backgroundImage: AssetImage('assets/logo.png'), - ), - title: Text(AppConfig.applicationName), - trailing: Icon( - Icons.adaptive.share_outlined, - color: Theme.of(context).colorScheme.onBackground, - ), - onTap: () { - Scaffold.of(context).closeDrawer(); - FluffyShare.share( - L10n.of(context)!.inviteText( - Matrix.of(context).client.userID!, - 'https://matrix.to/#/${Matrix.of(context).client.userID}?client=im.fluffychat'), - context); - }, - ), - const Divider(thickness: 1), - Expanded( - child: SpacesDrawer( - controller: controller, - ), - ), - const Divider(thickness: 1), - ListTile( - leading: Icon( - Icons.group_add_outlined, - color: Theme.of(context).colorScheme.onBackground, - ), - title: Text(L10n.of(context)!.createNewGroup), - onTap: () { - Scaffold.of(context).closeDrawer(); - VRouter.of(context).to('/newgroup'); - }, - ), - ListTile( - leading: Icon( - Icons.group_work_outlined, - color: Theme.of(context).colorScheme.onBackground, - ), - title: Text(L10n.of(context)!.createNewSpace), - onTap: () { - Scaffold.of(context).closeDrawer(); - VRouter.of(context).to('/newspace'); - }, - ), - ListTile( - leading: Icon( - Icons.settings_outlined, - color: Theme.of(context).colorScheme.onBackground, - ), - title: Text(L10n.of(context)!.settings), - onTap: () { - Scaffold.of(context).closeDrawer(); - VRouter.of(context).to('/settings'); - }, - ), - ], - ), - ), - ); -} diff --git a/lib/pages/chat_list/chat_list_header.dart b/lib/pages/chat_list/chat_list_header.dart index 7f6219b77..b156094e5 100644 --- a/lib/pages/chat_list/chat_list_header.dart +++ b/lib/pages/chat_list/chat_list_header.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/client_chooser_button.dart'; -import 'package:fluffychat/widgets/matrix.dart'; class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { final ChatListController controller; @@ -53,68 +51,22 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { borderRadius: BorderRadius.circular(AppConfig.borderRadius), ), - hintText: controller.activeSpacesEntry.getName(context), - prefixIcon: Padding( - padding: const EdgeInsets.only( - left: 8.0, - right: 4, - ), - child: controller.isSearchMode - ? IconButton( - tooltip: L10n.of(context)!.cancel, - icon: const Icon(Icons.close_outlined), - onPressed: controller.cancelSearch, - color: - Theme.of(context).colorScheme.onBackground, - ) - : IconButton( - onPressed: Scaffold.of(context).openDrawer, - icon: Icon( - Icons.menu, - color: Theme.of(context) - .colorScheme - .onBackground, - ), - ), - ), - suffixIcon: Row( - mainAxisSize: MainAxisSize.min, - children: controller.isSearchMode - ? [ - if (controller.isSearching) - const CircularProgressIndicator.adaptive( - strokeWidth: 2, - ), - TextButton( - onPressed: controller.setServer, - style: TextButton.styleFrom( - textStyle: const TextStyle(fontSize: 12), - ), - child: Text( - controller.searchServer ?? - Matrix.of(context) - .client - .homeserver! - .host, - maxLines: 2, - ), - ), - ] - : [ - IconButton( - icon: Icon( - Icons.camera_alt_outlined, - color: Theme.of(context) - .colorScheme - .onBackground, - ), - tooltip: L10n.of(context)!.addToStory, - onPressed: () => - VRouter.of(context).to('/stories/create'), - ), - ClientChooserButton(controller), - const SizedBox(width: 12), - ], + hintText: L10n.of(context)!.search, + floatingLabelBehavior: FloatingLabelBehavior.never, + prefixIcon: controller.isSearchMode + ? IconButton( + tooltip: L10n.of(context)!.cancel, + icon: const Icon(Icons.close_outlined), + onPressed: controller.cancelSearch, + color: Theme.of(context).colorScheme.onBackground, + ) + : Icon( + Icons.search_outlined, + color: Theme.of(context).colorScheme.onBackground, + ), + suffixIcon: SizedBox( + width: 0, + child: ClientChooserButton(controller), ), ), ), diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index 6913fe3b1..666f2773d 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -5,9 +5,9 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:keyboard_shortcuts/keyboard_shortcuts.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_drawer.dart'; -import 'package:fluffychat/widgets/connection_status_header.dart'; import '../../widgets/matrix.dart'; import 'chat_list_body.dart'; import 'chat_list_header.dart'; @@ -18,6 +18,29 @@ class ChatListView extends StatelessWidget { const ChatListView(this.controller, {Key? key}) : super(key: key); + List getNavigationDestinations(BuildContext context) => + [ + if (AppConfig.separateChatTypes) ...[ + NavigationDestination( + icon: const Icon(Icons.group_outlined), + label: L10n.of(context)!.groups, + ), + NavigationDestination( + icon: const Icon(Icons.chat_bubble_outline), + label: L10n.of(context)!.messages, + ), + ] else + NavigationDestination( + icon: const Icon(Icons.chat_bubble), + label: L10n.of(context)!.allChats, + ), + if (controller.spaces.isNotEmpty) + const NavigationDestination( + icon: Icon(Icons.group_work_outlined), + label: 'Spaces', + ), + ]; + @override Widget build(BuildContext context) { return StreamBuilder( @@ -30,24 +53,56 @@ class ChatListView extends StatelessWidget { if (selMode != SelectMode.normal) controller.cancelAction(); if (selMode == SelectMode.select) redirector.stopRedirection(); }, - child: Scaffold( - appBar: ChatListHeader(controller: controller), - body: ChatListViewBody(controller), - drawer: ChatListDrawer(controller), - bottomNavigationBar: const ConnectionStatusHeader(), - floatingActionButton: selectMode == SelectMode.normal - ? KeyBoardShortcuts( - keysToPress: { - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.keyN - }, - onKeysPressed: () => - VRouter.of(context).to('/newprivatechat'), - helpLabel: L10n.of(context)!.newChat, - child: - StartChatFloatingActionButton(controller: controller), - ) - : null, + child: Row( + children: [ + if (FluffyThemes.isColumnMode(context) && + FluffyThemes.getDisplayNavigationRail(context)) ...[ + NavigationRail( + selectedIndex: controller.selectedIndex, + onDestinationSelected: controller.onDestinationSelected, + labelType: NavigationRailLabelType.all, + destinations: getNavigationDestinations(context) + .map( + (destination) => NavigationRailDestination( + icon: destination.icon, + label: Text(destination.label), + ), + ) + .toList(), + ), + Container( + color: Theme.of(context).dividerColor, + width: 1, + ), + ], + Expanded( + child: Scaffold( + appBar: ChatListHeader(controller: controller), + body: ChatListViewBody(controller), + bottomNavigationBar: controller.displayNavigationBar + ? NavigationBar( + selectedIndex: controller.selectedIndex, + onDestinationSelected: + controller.onDestinationSelected, + destinations: getNavigationDestinations(context), + ) + : null, + floatingActionButton: selectMode == SelectMode.normal + ? KeyBoardShortcuts( + keysToPress: { + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.keyN + }, + onKeysPressed: () => + VRouter.of(context).to('/newprivatechat'), + helpLabel: L10n.of(context)!.newChat, + child: StartChatFloatingActionButton( + controller: controller), + ) + : null, + ), + ), + ], ), ); }, diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 76889af9f..834320513 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -4,9 +4,11 @@ import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:keyboard_shortcuts/keyboard_shortcuts.dart'; import 'package:matrix/matrix.dart'; +import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import '../../utils/fluffy_share.dart'; import 'chat_list.dart'; class ClientChooserButton extends StatelessWidget { @@ -23,6 +25,60 @@ class ClientChooserButton extends StatelessWidget { ? -1 : 1); return >[ + PopupMenuItem( + value: SettingsAction.newStory, + child: Row( + children: [ + const Icon(Icons.camera_outlined), + const SizedBox(width: 18), + Text(L10n.of(context)!.yourStory), + ], + ), + ), + PopupMenuItem( + value: SettingsAction.newGroup, + child: Row( + children: [ + const Icon(Icons.group_add_outlined), + const SizedBox(width: 18), + Text(L10n.of(context)!.createNewGroup), + ], + ), + ), + PopupMenuItem( + value: SettingsAction.newSpace, + child: Row( + children: [ + const Icon(Icons.group_work_outlined), + const SizedBox(width: 18), + Text(L10n.of(context)!.createNewSpace), + ], + ), + ), + PopupMenuItem( + value: SettingsAction.invite, + child: Row( + children: [ + Icon(Icons.adaptive.share_outlined), + const SizedBox(width: 18), + Text(L10n.of(context)!.inviteContact), + ], + ), + ), + PopupMenuItem( + value: SettingsAction.settings, + child: Row( + children: [ + const Icon(Icons.settings_outlined), + const SizedBox(width: 18), + Text(L10n.of(context)!.settings), + ], + ), + ), + const PopupMenuItem( + value: null, + child: Divider(height: 1), + ), for (final bundle in bundles) ...[ if (matrix.accountBundles[bundle]!.length != 1 || matrix.accountBundles[bundle]!.single!.userID != bundle) @@ -80,7 +136,7 @@ class ClientChooserButton extends StatelessWidget { .toList(), ], PopupMenuItem( - value: AddAccountAction.addAccount, + value: SettingsAction.addAccount, child: Row( children: [ const Icon(Icons.person_add_outlined), @@ -98,42 +154,55 @@ class ClientChooserButton extends StatelessWidget { int clientCount = 0; matrix.accountBundles.forEach((key, value) => clientCount += value.length); - return Center( - child: FutureBuilder( - future: matrix.client.fetchOwnProfile(), - builder: (context, snapshot) => Stack( - alignment: Alignment.center, - children: [ - ...List.generate( - clientCount, - (index) => KeyBoardShortcuts( - keysToPress: _buildKeyboardShortcut(index + 1), - helpLabel: L10n.of(context)!.switchToAccount(index + 1), - onKeysPressed: () => _handleKeyboardShortcut(matrix, index), - child: Container(), + return FutureBuilder( + future: matrix.client.fetchOwnProfile(), + builder: (context, snapshot) => Stack( + alignment: Alignment.center, + children: [ + ...List.generate( + clientCount, + (index) => KeyBoardShortcuts( + keysToPress: _buildKeyboardShortcut(index + 1), + helpLabel: L10n.of(context)!.switchToAccount(index + 1), + onKeysPressed: () => _handleKeyboardShortcut( + matrix, + index, + context, + ), + child: Container(), + ), + ), + KeyBoardShortcuts( + keysToPress: { + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.tab + }, + helpLabel: L10n.of(context)!.nextAccount, + onKeysPressed: () => _nextAccount(matrix, context), + child: Container(), + ), + KeyBoardShortcuts( + keysToPress: { + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.shiftLeft, + LogicalKeyboardKey.tab + }, + helpLabel: L10n.of(context)!.previousAccount, + onKeysPressed: () => _previousAccount(matrix, context), + child: Container(), + ), + Theme( + data: Theme.of(context).copyWith( + useMaterial3: false, + popupMenuTheme: PopupMenuThemeData( + color: Theme.of(context).colorScheme.background, + textStyle: TextStyle( + color: Theme.of(context).colorScheme.onBackground), + elevation: Theme.of(context).appBarTheme.scrolledUnderElevation, ), ), - KeyBoardShortcuts( - keysToPress: { - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.tab - }, - helpLabel: L10n.of(context)!.nextAccount, - onKeysPressed: () => _nextAccount(matrix), - child: Container(), - ), - KeyBoardShortcuts( - keysToPress: { - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.shiftLeft, - LogicalKeyboardKey.tab - }, - helpLabel: L10n.of(context)!.previousAccount, - onKeysPressed: () => _previousAccount(matrix), - child: Container(), - ), - PopupMenuButton( - onSelected: _clientSelected, + child: PopupMenuButton( + onSelected: (o) => _clientSelected(o, context), itemBuilder: _bundleMenuItems, child: Material( color: Colors.transparent, @@ -147,8 +216,8 @@ class ClientChooserButton extends StatelessWidget { ), ), ), - ], - ), + ), + ], ), ); } @@ -164,17 +233,46 @@ class ClientChooserButton extends StatelessWidget { } } - void _clientSelected(Object object) { + void _clientSelected( + Object object, + BuildContext context, + ) { if (object is Client) { controller.setActiveClient(object); } else if (object is String) { controller.setActiveBundle(object); - } else if (object == AddAccountAction.addAccount) { - controller.addAccountAction(); + } else if (object is SettingsAction) { + switch (object) { + case SettingsAction.addAccount: + VRouter.of(context).to('/settings/account'); + break; + case SettingsAction.newStory: + VRouter.of(context).to('/stories/create'); + break; + case SettingsAction.newGroup: + VRouter.of(context).to('/newgroup'); + break; + case SettingsAction.newSpace: + VRouter.of(context).to('/newspace'); + break; + case SettingsAction.invite: + FluffyShare.share( + L10n.of(context)!.inviteText(Matrix.of(context).client.userID!, + 'https://matrix.to/#/${Matrix.of(context).client.userID}?client=im.fluffychat'), + context); + break; + case SettingsAction.settings: + VRouter.of(context).to('/settings'); + break; + } } } - void _handleKeyboardShortcut(MatrixState matrix, int index) { + void _handleKeyboardShortcut( + MatrixState matrix, + int index, + BuildContext context, + ) { final bundles = matrix.accountBundles.keys.toList() ..sort((a, b) => a!.isValidMatrixId == b!.isValidMatrixId ? 0 @@ -186,20 +284,20 @@ class ClientChooserButton extends StatelessWidget { int clientCount = 0; matrix.accountBundles .forEach((key, value) => clientCount += value.length); - _handleKeyboardShortcut(matrix, clientCount); + _handleKeyboardShortcut(matrix, clientCount, context); } for (final bundleName in bundles) { final bundle = matrix.accountBundles[bundleName]; if (bundle != null) { if (index < bundle.length) { - return _clientSelected(bundle[index]!); + return _clientSelected(bundle[index]!, context); } else { index -= bundle.length; } } } // if index too high, restarting from 0 - _handleKeyboardShortcut(matrix, 0); + _handleKeyboardShortcut(matrix, 0, context); } int? _shortcutIndexOfClient(MatrixState matrix, Client client) { @@ -223,17 +321,24 @@ class ClientChooserButton extends StatelessWidget { return null; } - void _nextAccount(MatrixState matrix) { + void _nextAccount(MatrixState matrix, BuildContext context) { final client = matrix.client; final lastIndex = _shortcutIndexOfClient(matrix, client); - _handleKeyboardShortcut(matrix, lastIndex! + 1); + _handleKeyboardShortcut(matrix, lastIndex! + 1, context); } - void _previousAccount(MatrixState matrix) { + void _previousAccount(MatrixState matrix, BuildContext context) { final client = matrix.client; final lastIndex = _shortcutIndexOfClient(matrix, client); - _handleKeyboardShortcut(matrix, lastIndex! - 1); + _handleKeyboardShortcut(matrix, lastIndex! - 1, context); } } -enum AddAccountAction { addAccount } +enum SettingsAction { + addAccount, + newStory, + newGroup, + newSpace, + invite, + settings, +} diff --git a/lib/utils/matrix_sdk_extensions.dart/client_stories_extension.dart b/lib/utils/matrix_sdk_extensions.dart/client_stories_extension.dart index 269de73c8..eff87f234 100644 --- a/lib/utils/matrix_sdk_extensions.dart/client_stories_extension.dart +++ b/lib/utils/matrix_sdk_extensions.dart/client_stories_extension.dart @@ -16,14 +16,8 @@ extension ClientStoriesExtension on Client { room.unsafeGetUserFromMemoryOrFallback(room.directChatMatrixID!)) .toList(); - List get storiesRooms => rooms - .where((room) => - room - .getState(EventTypes.RoomCreate) - ?.content - .tryGet('type') == - storiesRoomType) - .toList(); + List get storiesRooms => + rooms.where((room) => room.isStoryRoom).toList(); Future> getUndecidedContactsForStories(Room? storiesRoom) async { if (storiesRoom == null) return contacts; @@ -96,3 +90,9 @@ extension ClientStoriesExtension on Client { .toList()); } } + +extension StoryRoom on Room { + bool get isStoryRoom => + getState(EventTypes.RoomCreate)?.content.tryGet('type') == + ClientStoriesExtension.storiesRoomType; +} diff --git a/lib/widgets/fluffy_chat_app.dart b/lib/widgets/fluffy_chat_app.dart index e1c6c7be8..a6eab30e2 100644 --- a/lib/widgets/fluffy_chat_app.dart +++ b/lib/widgets/fluffy_chat_app.dart @@ -62,18 +62,14 @@ class FluffyChatAppState extends State { initial: AdaptiveThemeMode.system, builder: (theme, darkTheme) => LayoutBuilder( builder: (context, constraints) { - const maxColumns = 3; - var newColumns = - (constraints.maxWidth / FluffyThemes.columnWidth).floor(); - if (newColumns > maxColumns) newColumns = maxColumns; - columnMode ??= newColumns > 1; - _router ??= GlobalKey(); - if (columnMode != newColumns > 1) { - Logs().v('Set Column Mode = $columnMode'); + final isColumnMode = + FluffyThemes.isColumnModeByWidth(constraints.maxWidth); + if (isColumnMode != columnMode) { + Logs().v('Set Column Mode = $isColumnMode'); WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { _initialUrl = _router?.currentState?.url; - columnMode = newColumns > 1; + columnMode = isColumnMode; _router = GlobalKey(); }); }); diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index 9371be3d2..3017e851c 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import '../../config/themes.dart'; + class TwoColumnLayout extends StatelessWidget { final Widget mainView; final Widget sideView; @@ -18,7 +20,8 @@ class TwoColumnLayout extends StatelessWidget { Container( clipBehavior: Clip.antiAlias, decoration: const BoxDecoration(), - width: 360.0, + width: 360.0 + + (FluffyThemes.getDisplayNavigationRail(context) ? 64 : 0), child: mainView, ), Container(