import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.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; const SpacesDrawer({Key? key, required this.controller}) : super(key: key); @override Widget build(BuildContext context) { final spaceEntries = controller.spacesEntries .map((e) => SpacesEntryMaybeChildren.buildIfTopLevel( e, controller.spacesEntries)) .whereNotNull() .toList(); 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 { // don't add rooms with parent space apart from those where the // parent space is not joined if (space?.hasNotJoinedParentSpace() ?? false) { spacesHierarchy.add(entry); } } } else { spacesHierarchy.add(entry); } } spacesHierarchy.removeWhere((element) => childSpaceIds.contains(element.spacesEntry.getSpace(context)?.id)); return ListView.builder( itemCount: spacesHierarchy.length + 1, itemBuilder: (context, i) { if (i == spacesHierarchy.length) { return ListTile( leading: CircleAvatar( radius: Avatar.defaultSize / 2, backgroundColor: Theme.of(context).colorScheme.secondary, foregroundColor: Theme.of(context).colorScheme.onSecondary, child: const Icon( Icons.archive_outlined, ), ), title: Text(L10n.of(context)!.archive), onTap: () { Scaffold.of(context).closeDrawer(); VRouter.of(context).to('/archive'); }, ); } final space = spacesHierarchy[i]; return SpacesDrawerEntry( entry: space, controller: controller, ); }, ); } } 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; // don't add rooms with parent space apart from those where the // parent space is not joined if ((parent == null && room.spaceParents.isNotEmpty && room.hasNotJoinedParentSpace()) || (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()); } } extension on Room { bool hasNotJoinedParentSpace() { return (spaceParents.isEmpty || spaceParents.none( (p0) => (p0.canonical ?? true) && client.rooms.map((e) => e.id).contains(p0.roomId), )); } }