fluffychat/lib/pages/chat_list/spaces_drawer.dart
TheOneWithTheBraid 1ce0cb3749 fix: errors in space hierarchy
- fix suggestions of joined rooms
- fix missing spaces when parent not joined

Signed-off-by: TheOneWithTheBraid <the-one@with-the-braid.cf>
2022-08-06 15:48:18 +02:00

191 lines
6.1 KiB
Dart

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 = <String>{};
final spacesHierarchy = <SpacesEntryMaybeChildren>[];
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<SpacesEntryMaybeChildren> children;
const SpacesEntryMaybeChildren(this.spacesEntry, [this.children = const {}]);
static SpacesEntryMaybeChildren? buildIfTopLevel(
SpacesEntry entry, List<SpacesEntry> 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<String, dynamic> 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),
));
}
}