import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions.dart/client_stories_extension.dart'; import '../../widgets/matrix.dart'; // This is not necessarily a Space, but an abstract categorization of a room. // More to the point, it's a selectable entry that *could* be a Space. // Note that view code is in spaces_bottom_bar.dart because of type-specific UI. // So only really generic functions (so far, anything ChatList cares about) go here. // If getRoom returns something non-null, then it gets the avatar and such of a Space. // Otherwise it gets to look like All Rooms. Future work impending. abstract class SpacesEntry { const SpacesEntry(); // Gets the (translated) name of this entry. String getName(BuildContext context); // Gets an icon for this entry (avoided if a space is given) Icon getIcon(bool active) => active ? const Icon(CupertinoIcons.chat_bubble_2_fill) : const Icon(CupertinoIcons.chat_bubble_2); // If this is a specific Room, returns the space Room for various purposes. Room? getSpace(BuildContext context) => null; // Gets a list of rooms - this is done as part of _ChatListViewBodyState to get the full list of rooms visible from this SpacesEntry. List getRooms(BuildContext context); // Checks that this entry is still valid. bool stillValid(BuildContext context) => true; // Returns true if the Stories header should be shown. bool shouldShowStoriesHeader(BuildContext context) => false; String? get routeHandle; } // Common room validity checks bool _roomCheckCommon(Room room, BuildContext context) { if (room.isSpace && room.membership == Membership.join && !room.isUnread) { return false; } if (room.getState(EventTypes.RoomCreate)?.content.tryGet('type') == ClientStoriesExtension.storiesRoomType) { return false; } return true; } bool _roomInsideSpace(Room room, Room space) { if (space.spaceChildren.any((child) => child.roomId == room.id)) { return true; } if (room.spaceParents.any((parent) => parent.roomId == space.id)) { return true; } return false; } // "All rooms" entry. class AllRoomsSpacesEntry extends SpacesEntry { static final AllRoomsSpacesEntry _value = AllRoomsSpacesEntry._(); AllRoomsSpacesEntry._(); factory AllRoomsSpacesEntry() { return _value; } @override String getName(BuildContext context) => L10n.of(context)!.allChats; @override List getRooms(BuildContext context) { return Matrix.of(context) .client .rooms .where((room) => _roomCheckCommon(room, context)) .toList(); } @override final String? routeHandle = null; @override bool shouldShowStoriesHeader(BuildContext context) => true; @override bool operator ==(Object other) { return runtimeType == other.runtimeType; } @override int get hashCode => runtimeType.hashCode; } // "Direct Chats" entry. class DirectChatsSpacesEntry extends SpacesEntry { static final DirectChatsSpacesEntry _value = DirectChatsSpacesEntry._(); DirectChatsSpacesEntry._(); factory DirectChatsSpacesEntry() { return _value; } @override String getName(BuildContext context) => L10n.of(context)!.directChats; @override List getRooms(BuildContext context) { return Matrix.of(context) .client .rooms .where((room) => room.isDirectChat && _roomCheckCommon(room, context)) .toList(); } @override final String? routeHandle = null; @override bool shouldShowStoriesHeader(BuildContext context) => true; @override bool operator ==(Object other) { return runtimeType == other.runtimeType; } @override int get hashCode => runtimeType.hashCode; } // "Groups" entry. class GroupsSpacesEntry extends SpacesEntry { static final GroupsSpacesEntry _value = GroupsSpacesEntry._(); GroupsSpacesEntry._(); factory GroupsSpacesEntry() { return _value; } @override String getName(BuildContext context) => L10n.of(context)!.groups; @override Icon getIcon(bool active) => active ? const Icon(Icons.group) : const Icon(Icons.group_outlined); @override List getRooms(BuildContext context) { final rooms = Matrix.of(context).client.rooms; // Needs to match ChatList's definition of a space. final spaces = rooms.where((room) => room.isSpace).toList(); return rooms .where((room) => (!room.isDirectChat) && _roomCheckCommon(room, context) && separatedGroup(room, spaces)) .toList(); } @override final String? routeHandle = 'groups'; bool separatedGroup(Room room, List spaces) { return !spaces.any((space) => _roomInsideSpace(room, space)); } @override bool operator ==(Object other) { return runtimeType == other.runtimeType; } @override int get hashCode => runtimeType.hashCode; } // All rooms associated with a specific space. class SpaceSpacesEntry extends SpacesEntry { final Room space; const SpaceSpacesEntry(this.space); @override String getName(BuildContext context) => space.displayname; @override Room? getSpace(BuildContext context) => space; @override List getRooms(BuildContext context) { return Matrix.of(context) .client .rooms .where((room) => roomCheck(room, context)) .toList(); } bool roomCheck(Room room, BuildContext context) { if (!_roomCheckCommon(room, context)) { return false; } if (_roomInsideSpace(room, space)) { return true; } if (AppConfig.showDirectChatsInSpaces) { if (room.isDirectChat && room.summary.mHeroes != null && room.summary.mHeroes!.any((userId) { final user = space.getState(EventTypes.RoomMember, userId)?.asUser; return user != null && user.membership == Membership.join; })) { return true; } } return false; } @override bool stillValid(BuildContext context) => Matrix.of(context).client.getRoomById(space.id) != null; @override String? get routeHandle => space.id; @override bool operator ==(Object other) { return hashCode == other.hashCode; } @override int get hashCode => space.id.hashCode; }