diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 89f88431..d883820d 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -174,13 +174,6 @@ "type": "text", "placeholders": {} }, - "numberRoomMembers": "{number} members", - "@numberRoomMembers": { - "type": "number", - "placeholders": { - "number": {} - } - }, "badServerLoginTypesException": "The homeserver supports the login types:\n{serverVersions}\nBut this app supports only:\n{supportedVersions}", "@badServerLoginTypesException": { "type": "text", @@ -217,7 +210,6 @@ "targetName": {} } }, - "suggestedRooms": "Discover groups in this space", "blockDevice": "Block Device", "@blockDevice": { "type": "text", diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 68158210..f616e585 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -8,7 +8,6 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; -import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/spaces_entry.dart'; import 'package:fluffychat/pages/chat_list/stories_header.dart'; import 'package:fluffychat/widgets/avatar.dart'; @@ -17,7 +16,6 @@ import 'package:fluffychat/widgets/profile_bottom_sheet.dart'; import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; import '../../utils/stream_extension.dart'; import '../../widgets/matrix.dart'; -import 'spaces_hierarchy_proposal.dart'; class ChatListViewBody extends StatefulWidget { final ChatListController controller; @@ -57,206 +55,201 @@ class _ChatListViewBodyState extends State { if (widget.controller.waitForFirstSync && Matrix.of(context).client.prevBatch != null) { final rooms = widget.controller.activeSpacesEntry.getRooms(context); - - final displayStoriesHeader = widget.controller.activeSpacesEntry - .shouldShowStoriesHeader(context) || - rooms.isEmpty; - child = ListView.builder( - key: ValueKey(Matrix.of(context).client.userID.toString() + - widget.controller.activeSpaceId.toString() + - widget.controller.activeSpacesEntry.runtimeType.toString()), - controller: widget.controller.scrollController, - // add +1 space below in order to properly scroll below the spaces bar - itemCount: rooms.length + (displayStoriesHeader ? 2 : 1), - itemBuilder: (BuildContext context, int i) { - if (displayStoriesHeader) { - if (i == 0) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - const ConnectionStatusHeader(), - if (roomSearchResult != null) ...[ - SearchTitle( - title: L10n.of(context)!.publicRooms, - icon: const Icon(Icons.explore_outlined), + if (rooms.isEmpty) { + child = Column( + key: const ValueKey(null), + mainAxisAlignment: MainAxisAlignment.center, + 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: 160), + ], + ); + } else { + final displayStoriesHeader = widget.controller.activeSpacesEntry + .shouldShowStoriesHeader(context); + child = ListView.builder( + key: ValueKey(Matrix.of(context).client.userID.toString() + + widget.controller.activeSpaceId.toString() + + widget.controller.activeSpacesEntry.runtimeType.toString()), + controller: widget.controller.scrollController, + // add +1 space below in order to properly scroll below the spaces bar + itemCount: rooms.length + (displayStoriesHeader ? 2 : 1), + itemBuilder: (BuildContext context, int i) { + if (displayStoriesHeader) { + if (i == 0) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + const ConnectionStatusHeader(), + if (roomSearchResult != null) ...[ + _SearchTitle( + title: L10n.of(context)!.publicRooms, + 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: 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: Icons.camera_alt_outlined, + ), + StoriesHeader( + filter: widget.controller.searchController.text, ), AnimatedContainer( - height: roomSearchResult.chunk.isEmpty ? 0 : 106, - duration: const Duration(milliseconds: 250), + height: !widget.controller.isSearchMode && + widget.controller.showChatBackupBanner + ? 54 + : 0, + duration: const Duration(milliseconds: 300), clipBehavior: Clip.hardEdge, + curve: Curves.bounceInOut, 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], + child: Material( + color: Theme.of(context).colorScheme.surface, + child: ListTile( + leading: CircleAvatar( + radius: Avatar.defaultSize / 2, + child: + const Icon(Icons.enhanced_encryption_outlined), + backgroundColor: + Theme.of(context).colorScheme.surfaceVariant, + foregroundColor: + Theme.of(context).colorScheme.onSurfaceVariant, + ), + title: Text( + (Matrix.of(context) + .client + .encryption + ?.keyManager + .enabled == + true) + ? L10n.of(context)!.unlockOldMessages + : L10n.of(context)!.enableAutoBackups, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, ), ), + trailing: const Icon(Icons.chevron_right_outlined), + onTap: widget.controller.firstRunBootstrapAction, ), ), ), - ], - 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), + height: widget.controller.isTorBrowser ? 64 : 0, + duration: const Duration(milliseconds: 300), clipBehavior: Clip.hardEdge, + curve: Curves.bounceInOut, 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, - ), - ), + 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: Icons.chat_outlined, + ), ], - if (widget.controller.isSearchMode) - SearchTitle( - title: L10n.of(context)!.stories, - icon: const Icon(Icons.camera_alt_outlined), - ), - StoriesHeader( - filter: widget.controller.searchController.text, - ), - AnimatedContainer( - height: !widget.controller.isSearchMode && - widget.controller.showChatBackupBanner - ? 54 - : 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: CircleAvatar( - radius: Avatar.defaultSize / 2, - child: const Icon(Icons.enhanced_encryption_outlined), - backgroundColor: - Theme.of(context).colorScheme.surfaceVariant, - foregroundColor: - Theme.of(context).colorScheme.onSurfaceVariant, - ), - title: Text( - (Matrix.of(context) - .client - .encryption - ?.keyManager - .enabled == - true) - ? L10n.of(context)!.unlockOldMessages - : L10n.of(context)!.enableAutoBackups, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - ), - ), - trailing: const Icon(Icons.chevron_right_outlined), - onTap: widget.controller.firstRunBootstrapAction, - ), - ), - ), - 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) - 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--; } - i--; - } - if (i >= rooms.length) { - return SpacesHierarchyProposals( - space: widget.controller.activeSpacesEntry.getSpace(context)?.id, - query: widget.controller.isSearchMode - ? widget.controller.searchController.text + if (i >= rooms.length) { + return const ListTile(); + } + if (!rooms[i].displayname.toLowerCase().contains( + widget.controller.searchController.text.toLowerCase())) { + return Container(); + } + return ChatListItem( + rooms[i], + selected: widget.controller.selectedRoomIds.contains(rooms[i].id), + onTap: widget.controller.selectMode == SelectMode.select + ? () => widget.controller.toggleSelection(rooms[i].id) : null, + onLongPress: () => widget.controller.toggleSelection(rooms[i].id), + activeChat: widget.controller.activeChat == rooms[i].id, ); - } - if (!rooms[i].displayname.toLowerCase().contains( - widget.controller.searchController.text.toLowerCase())) { - return Container(); - } - return ChatListItem( - rooms[i], - selected: widget.controller.selectedRoomIds.contains(rooms[i].id), - onTap: widget.controller.selectMode == SelectMode.select - ? () => widget.controller.toggleSelection(rooms[i].id) - : null, - onLongPress: () => widget.controller.toggleSelection(rooms[i].id), - activeChat: widget.controller.activeChat == rooms[i].id, - ); - }, - ); + }, + ); + } } else { const dummyChatCount = 5; final titleColor = @@ -368,11 +361,54 @@ class _ChatListViewBodyState extends State { } } +class _SearchTitle extends StatelessWidget { + final String title; + final IconData icon; + const _SearchTitle({ + required this.title, + required this.icon, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) => Container( + decoration: BoxDecoration( + border: Border.symmetric( + horizontal: BorderSide( + color: Theme.of(context).dividerColor, + width: 1, + )), + color: Theme.of(context).colorScheme.surface, + ), + child: Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Row( + children: [ + Icon(icon, size: 16), + const SizedBox(width: 16), + Text(title, + textAlign: TextAlign.left, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + fontSize: 12, + fontWeight: FontWeight.bold, + )), + ], + ), + ), + ), + ); +} + class _SearchItem extends StatelessWidget { final String title; final Uri? avatar; final void Function() onPressed; - const _SearchItem({ required this.title, this.avatar, diff --git a/lib/pages/chat_list/recommended_room_list_item.dart b/lib/pages/chat_list/recommended_room_list_item.dart deleted file mode 100644 index d315ad5d..00000000 --- a/lib/pages/chat_list/recommended_room_list_item.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/pages/chat_list/spaces_hierarchy_proposal.dart'; -import 'package:fluffychat/widgets/avatar.dart'; -import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; - -class RecommendedRoomListItem extends StatelessWidget { - final SpaceRoomsChunk room; - - const RecommendedRoomListItem({Key? key, required this.room}) - : super(key: key); - - @override - Widget build(BuildContext context) { - final leading = Avatar( - mxContent: room.avatarUrl, - name: room.name, - ); - final title = Row( - children: [ - Expanded( - child: Text( - room.name ?? '', - maxLines: 1, - overflow: TextOverflow.ellipsis, - softWrap: false, - style: TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).textTheme.bodyText1!.color, - ), - ), - ), - // number of joined users - Padding( - padding: const EdgeInsets.only(left: 4.0), - child: Text.rich( - TextSpan(children: [ - WidgetSpan( - child: Tooltip( - child: const Icon( - Icons.people_outlined, - size: 20, - ), - message: L10n.of(context)! - .numberRoomMembers(room.numJoinedMembers), - ), - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic), - TextSpan(text: ' ${room.numJoinedMembers}') - ]), - style: TextStyle( - fontSize: 13, - color: Theme.of(context).textTheme.bodyText2!.color, - ), - ), - ), - ], - ); - final subtitle = Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Text( - room.topic ?? 'topic', - softWrap: false, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of(context).textTheme.bodyText2!.color, - ), - ), - ), - ], - ); - void handler() => showModalBottomSheet( - context: context, - builder: (c) => PublicRoomBottomSheet( - roomAlias: room.canonicalAlias!, - outerContext: context, - chunk: room, - ), - ); - if (room.roomType == 'm.space') { - return Material( - color: Colors.transparent, - child: ExpansionTile( - leading: leading, - title: title, - subtitle: subtitle, - onExpansionChanged: (open) { - if (!open) handler(); - }, - children: [ - SpacesHierarchyProposals(space: room.roomId), - ], - ), - ); - } else { - return Material( - color: Colors.transparent, - child: ListTile( - leading: leading, - title: title, - subtitle: subtitle, - onTap: handler, - ), - ); - } - } -} diff --git a/lib/pages/chat_list/search_title.dart b/lib/pages/chat_list/search_title.dart deleted file mode 100644 index 7492de0b..00000000 --- a/lib/pages/chat_list/search_title.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter/material.dart'; - -class SearchTitle extends StatelessWidget { - final String title; - final Widget icon; - final Widget? trailing; - final void Function()? onTap; - - const SearchTitle({ - required this.title, - required this.icon, - this.trailing, - this.onTap, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) => Material( - shape: Border( - top: BorderSide( - color: Theme.of(context).dividerColor, - width: 1, - ), - bottom: BorderSide( - color: Theme.of(context).dividerColor, - width: 1, - ), - ), - color: Theme.of(context).colorScheme.surface, - child: InkWell( - onTap: onTap, - splashColor: Theme.of(context).colorScheme.surface, - child: Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - child: IconTheme( - data: Theme.of(context).iconTheme.copyWith(size: 16), - child: Row( - children: [ - icon, - const SizedBox(width: 16), - Text(title, - textAlign: TextAlign.left, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - fontSize: 12, - fontWeight: FontWeight.bold, - )), - if (trailing != null) - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: trailing!, - ), - ), - ], - ), - ), - ), - ), - ), - ); -} diff --git a/lib/pages/chat_list/spaces_drawer.dart b/lib/pages/chat_list/spaces_drawer.dart index e61202c4..c9e5d6be 100644 --- a/lib/pages/chat_list/spaces_drawer.dart +++ b/lib/pages/chat_list/spaces_drawer.dart @@ -1,16 +1,11 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import 'package:collection/collection.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/pages/chat_list/spaces_entry.dart'; import 'package:fluffychat/widgets/avatar.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import 'chat_list.dart'; -import 'spaces_drawer_entry.dart'; class SpacesDrawer extends StatelessWidget { final ChatListController controller; @@ -19,62 +14,19 @@ class SpacesDrawer extends StatelessWidget { @override Widget build(BuildContext context) { - final spaceEntries = controller.spacesEntries - .map((e) => SpacesEntryMaybeChildren.buildIfTopLevel( - e, controller.spacesEntries)) - .whereNotNull() - .toList(); + final currentIndex = controller.spacesEntries.indexWhere((space) => + controller.activeSpacesEntry.runtimeType == space.runtimeType && + (controller.activeSpaceId == space.getSpace(context)?.id)); - final childSpaceIds = {}; - - final spacesHierarchy = []; - - final matrix = Matrix.of(context); - for (final entry in spaceEntries) { - if (entry.spacesEntry is SpaceSpacesEntry) { - final space = entry.spacesEntry.getSpace(context); - if (space != null && space.spaceChildren.isNotEmpty) { - final children = space.spaceChildren; - // computing the children space entries - final childrenSpaceEntries = spaceEntries.where((element) { - // current ID - final id = element.spacesEntry.getSpace(context)?.id; - - // comparing against the supposed IDs of the children and checking - // whether the room is already joined - return children.any( - (child) => - child.roomId == id && - matrix.client.rooms - .any((joinedRoom) => child.roomId == joinedRoom.id), - ); - }); - childSpaceIds.addAll(childrenSpaceEntries - .map((e) => e.spacesEntry.getSpace(context)?.id) - .whereNotNull()); - entry.children.addAll(childrenSpaceEntries); - spacesHierarchy.add(entry); - } else { - if (space?.spaceParents.isEmpty ?? false) { - spacesHierarchy.add(entry); - } - } - } else { - spacesHierarchy.add(entry); - } - } - - spacesHierarchy.removeWhere((element) => - childSpaceIds.contains(element.spacesEntry.getSpace(context)?.id)); - - // final spacesHierarchy = spaceEntries; + final Map spaceHierarchy = + Map.fromEntries(controller.spacesEntries.map((e) => MapEntry(e, null))); // TODO(TheOeWithTheBraid): wait for space hierarchy https://gitlab.com/famedly/company/frontend/libraries/matrix_api_lite/-/merge_requests/58 return ListView.builder( - itemCount: spacesHierarchy.length + 1, + itemCount: spaceHierarchy.length + 1, itemBuilder: (context, i) { - if (i == spacesHierarchy.length) { + if (i == spaceHierarchy.length) { return ListTile( leading: CircleAvatar( radius: Avatar.defaultSize / 2, @@ -91,86 +43,54 @@ class SpacesDrawer extends StatelessWidget { }, ); } - final space = spacesHierarchy[i]; - return SpacesDrawerEntry( - entry: space, - controller: controller, + final space = spaceHierarchy.keys.toList()[i]; + final room = space.getSpace(context); + final active = currentIndex == i; + return ListTile( + selected: active, + leading: room == null + ? CircleAvatar( + child: space.getIcon(active), + radius: Avatar.defaultSize / 2, + backgroundColor: Theme.of(context).colorScheme.secondary, + foregroundColor: Theme.of(context).colorScheme.onSecondary, + ) + : Avatar( + mxContent: room.avatar, + name: space.getName(context), + ), + title: Text( + space.getName(context), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + subtitle: room?.topic.isEmpty ?? true + ? null + : Tooltip( + message: room!.topic, + child: Text( + room.topic.replaceAll('\n', ' '), + softWrap: false, + overflow: TextOverflow.fade, + ), + ), + onTap: () => controller.setActiveSpacesEntry( + context, + space, + ), + trailing: room != null + ? SizedBox( + width: 32, + child: IconButton( + splashRadius: 24, + icon: const Icon(Icons.edit_outlined), + tooltip: L10n.of(context)!.edit, + onPressed: () => controller.editSpace(context, room.id), + ), + ) + : const Icon(Icons.arrow_forward_ios_outlined), ); }, ); } } - -class SpacesEntryMaybeChildren { - final SpacesEntry spacesEntry; - - final Set children; - - const SpacesEntryMaybeChildren(this.spacesEntry, [this.children = const {}]); - - static SpacesEntryMaybeChildren? buildIfTopLevel( - SpacesEntry entry, List allEntries, - [String? parent]) { - if (entry is SpaceSpacesEntry) { - final room = entry.space; - if ((parent == null && room.spaceParents.isNotEmpty) || - (parent != null && - !room.spaceParents.any((element) => element.roomId == parent))) { - return null; - } else { - final children = allEntries - .where((element) => - element is SpaceSpacesEntry && - element.space.spaceParents.any((parent) => - parent.roomId == room.id /*&& (parent.canonical ?? true)*/)) - .toList(); - return SpacesEntryMaybeChildren( - entry, - children - .map((e) => buildIfTopLevel(e, allEntries, room.id)) - .whereNotNull() - .toSet()); - } - } else { - return SpacesEntryMaybeChildren(entry); - } - } - - bool isActiveOfChild(ChatListController controller) => - spacesEntry == controller.activeSpacesEntry || - children.any( - (element) => element.isActiveOfChild(controller), - ); - - Map toJson() => { - 'entry': spacesEntry is SpaceSpacesEntry - ? (spacesEntry as SpaceSpacesEntry).space.id - : spacesEntry.runtimeType.toString(), - if (spacesEntry is SpaceSpacesEntry) - 'rawSpaceParents': (spacesEntry as SpaceSpacesEntry) - .space - .spaceParents - .map((e) => - {'roomId': e.roomId, 'canonical': e.canonical, 'via': e.via}) - .toList(), - if (spacesEntry is SpaceSpacesEntry) - 'rawSpaceChildren': (spacesEntry as SpaceSpacesEntry) - .space - .spaceChildren - .map( - (e) => { - 'roomId': e.roomId, - 'suggested': e.suggested, - 'via': e.via, - 'order': e.order - }, - ) - .toList(), - 'children': children.map((e) => e.toJson()).toList(), - }; - - @override - String toString() { - return jsonEncode(toJson()); - } -} diff --git a/lib/pages/chat_list/spaces_drawer_entry.dart b/lib/pages/chat_list/spaces_drawer_entry.dart deleted file mode 100644 index d5e3f3bd..00000000 --- a/lib/pages/chat_list/spaces_drawer_entry.dart +++ /dev/null @@ -1,96 +0,0 @@ -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/pages/chat_list/spaces_drawer.dart'; -import 'package:fluffychat/widgets/avatar.dart'; - -class SpacesDrawerEntry extends StatelessWidget { - final SpacesEntryMaybeChildren entry; - final ChatListController controller; - - const SpacesDrawerEntry( - {Key? key, required this.entry, required this.controller}) - : super(key: key); - - @override - Widget build(BuildContext context) { - final space = entry.spacesEntry; - final room = space.getSpace(context); - - final active = controller.activeSpacesEntry == entry.spacesEntry; - final leading = room == null - ? CircleAvatar( - child: space.getIcon(active), - radius: Avatar.defaultSize / 2, - backgroundColor: Theme.of(context).colorScheme.secondary, - foregroundColor: Theme.of(context).colorScheme.onSecondary, - ) - : Avatar( - mxContent: room.avatar, - name: space.getName(context), - ); - final title = Text( - space.getName(context), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - final subtitle = room?.topic.isEmpty ?? true - ? null - : Tooltip( - message: room!.topic, - child: Text( - room.topic.replaceAll('\n', ' '), - softWrap: false, - overflow: TextOverflow.fade, - ), - ); - void onTap() => controller.setActiveSpacesEntry( - context, - space, - ); - final trailing = room != null - ? SizedBox( - width: 32, - child: IconButton( - splashRadius: 24, - icon: const Icon(Icons.edit_outlined), - tooltip: L10n.of(context)!.edit, - onPressed: () => controller.editSpace(context, room.id), - ), - ) - : const Icon(Icons.arrow_forward_ios_outlined); - - if (entry.children.isEmpty) { - return ListTile( - selected: active, - leading: leading, - title: title, - subtitle: subtitle, - onTap: onTap, - trailing: trailing, - ); - } else { - return ExpansionTile( - leading: leading, - initiallyExpanded: - entry.children.any((element) => entry.isActiveOfChild(controller)), - title: GestureDetector( - onTap: onTap, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: [ - Expanded(child: title), - const SizedBox(width: 8), - trailing - ]), - ), - children: entry.children - .map((e) => SpacesDrawerEntry(entry: e, controller: controller)) - .toList(), - ); - } - } -} diff --git a/lib/pages/chat_list/spaces_hierarchy_proposal.dart b/lib/pages/chat_list/spaces_hierarchy_proposal.dart deleted file mode 100644 index d1ed9c74..00000000 --- a/lib/pages/chat_list/spaces_hierarchy_proposal.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:animations/animations.dart'; -import 'package:async/async.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/pages/chat_list/search_title.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import 'recommended_room_list_item.dart'; - -class SpacesHierarchyProposals extends StatefulWidget { - static final Map> _cache = {}; - - final String? space; - final String? query; - - const SpacesHierarchyProposals({ - Key? key, - required this.space, - this.query, - }) : super(key: key); - - @override - State createState() => - _SpacesHierarchyProposalsState(); -} - -class _SpacesHierarchyProposalsState extends State { - @override - void didUpdateWidget(covariant SpacesHierarchyProposals oldWidget) { - if (oldWidget.space != widget.space || oldWidget.query != widget.query) { - setState(() {}); - } - super.didUpdateWidget(oldWidget); - } - - @override - Widget build(BuildContext context) { - // check for recommended rooms in case the active space is a [SpaceSpacesEntry] - if (widget.space != null) { - final client = Matrix.of(context).client; - - final cache = SpacesHierarchyProposals._cache[widget.space!] ??= - AsyncCache(const Duration(minutes: 15)); - - /// additionally saving the future's state in the completer in order to - /// display the loading indicator when refreshing as a [FutureBuilder] is - /// a [StatefulWidget]. - final completer = Completer(); - final future = cache.fetch(() => client.getSpaceHierarchy( - widget.space!, - suggestedOnly: true, - maxDepth: 1, - )); - future.then(completer.complete); - - return FutureBuilder( - future: future, - builder: (context, snapshot) { - Widget child; - if (snapshot.hasData) { - final rooms = snapshot.data!.rooms.where( - (element) => - element.roomId != widget.space && - // filtering in case a query is given - (widget.query != null - ? (element.name?.contains(widget.query!) ?? false) || - (element.topic?.contains(widget.query!) ?? false) - // in case not, just leave it... - : true) && - client.rooms - .any((knownRoom) => element.roomId != knownRoom.id), - ); - if (rooms.isEmpty) child = const ListTile(key: ValueKey(false)); - child = Column( - key: ValueKey(widget.space), - mainAxisSize: MainAxisSize.min, - children: [ - SearchTitle( - title: L10n.of(context)!.suggestedRooms, - icon: const Icon(Icons.auto_awesome_outlined), - trailing: completer.isCompleted - ? const Icon( - Icons.refresh_outlined, - size: 16, - ) - : const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator.adaptive( - strokeWidth: 1, - ), - ), - onTap: () => setState( - () => SpacesHierarchyProposals._cache[widget.space!]! - .invalidate(), - ), - ), - ...rooms.map( - (e) => RecommendedRoomListItem( - room: e, - ), - ), - ], - ); - } else { - child = Column( - key: const ValueKey(null), - children: const [ - LinearProgressIndicator(), - ListTile(), - ], - ); - } - return PageTransitionSwitcher( - // prevent the animation from re-building on dependency change - key: ValueKey(widget.space), - transitionBuilder: ( - Widget child, - Animation primaryAnimation, - Animation secondaryAnimation, - ) { - return SharedAxisTransition( - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - transitionType: SharedAxisTransitionType.scaled, - child: child, - fillColor: Colors.transparent, - ); - }, - layoutBuilder: (children) => Stack( - alignment: Alignment.topCenter, - children: children, - ), - child: child, - ); - }, - ); - } else { - return Container(); - } - } -} diff --git a/pubspec.lock b/pubspec.lock index da687cfc..ff9527dc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -72,7 +72,7 @@ packages: source: hosted version: "1.1.0" async: - dependency: "direct main" + dependency: transitive description: name: async url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 77173eba..402e1b77 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,6 @@ dependencies: adaptive_dialog: ^1.5.1 adaptive_theme: ^3.0.0 animations: ^2.0.2 - async: ^2.8.2 blurhash_dart: ^1.1.0 cached_network_image: ^3.2.0 callkeep: ^0.3.2 diff --git a/scripts/enable-android-google-services.patch b/scripts/enable-android-google-services.patch index 299ea1c5..5b1a49ff 100644 --- a/scripts/enable-android-google-services.patch +++ b/scripts/enable-android-google-services.patch @@ -157,7 +157,7 @@ diff --git a/pubspec.yaml b/pubspec.yaml index 6999d0b8..b2c9144f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml -@@ -28,7 +28,7 @@ dependencies: +@@ -27,7 +27,7 @@ dependencies: emoji_proposal: ^0.0.1 emojis: ^0.9.0 encrypt: ^5.0.1