Merge pull request #503 from pangeachat/reduce-chat-list-builds

don't rebuild the whole chat list on selection change
This commit is contained in:
ggurdin 2024-07-22 12:18:47 -04:00 committed by GitHub
commit 0556128afc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 185 additions and 26 deletions

View file

@ -599,12 +599,23 @@ class ChatListController extends State<ChatList>
super.dispose();
}
// #Pangea
final StreamController<String> selectionsStream =
StreamController.broadcast();
// Pangea#
void toggleSelection(String roomId) {
setState(
() => selectedRoomIds.contains(roomId)
? selectedRoomIds.remove(roomId)
: selectedRoomIds.add(roomId),
);
// #Pangea
// setState(
// () => selectedRoomIds.contains(roomId)
// ? selectedRoomIds.remove(roomId)
// : selectedRoomIds.add(roomId),
// );
selectedRoomIds.contains(roomId)
? selectedRoomIds.remove(roomId)
: selectedRoomIds.add(roomId);
selectionsStream.add(roomId);
// Pangea#
}
Future<void> toggleUnread() async {
@ -676,8 +687,8 @@ class ChatListController extends State<ChatList>
context: context,
future: () => _archiveSelectedRooms(),
);
setState(() {});
// #Pangea
// setState(() {});
if (archivedActiveRoom) {
context.go('/rooms');
}
@ -709,7 +720,6 @@ class ChatListController extends State<ChatList>
context: context,
future: () => _leaveSelectedRooms(onlyAdmin),
);
setState(() {});
if (leftActiveRoom) {
context.go('/rooms');
}
@ -871,7 +881,12 @@ class ChatListController extends State<ChatList>
);
}
setState(() => selectedRoomIds.clear());
// #Pangea
// setState(() => selectedRoomIds.clear());
if (firstSelectedRoom != null) {
toggleSelection(firstSelectedRoom.id);
}
// Pangea#
}
bool get anySelectedRoomNotMarkedUnread => selectedRoomIds.any(
@ -941,7 +956,12 @@ class ChatListController extends State<ChatList>
if (selectMode == SelectMode.share) {
setState(() => Matrix.of(context).shareContent = null);
} else {
setState(() => selectedRoomIds.clear());
// #Pangea
// setState(() => selectedRoomIds.clear());
for (final roomId in selectedRoomIds.toList()) {
toggleSelection(roomId);
}
// Pangea#
}
}

View file

@ -1,11 +1,11 @@
import 'package:animations/animations.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/space_view.dart';
import 'package:fluffychat/pages/chat_list/utils/on_chat_tap.dart';
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_body_text.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_header_wrapper.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_item_wrapper.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/stream_extension.dart';
import 'package:fluffychat/widgets/avatar.dart';
@ -17,7 +17,6 @@ import 'package:matrix/matrix.dart';
import '../../config/themes.dart';
import '../../widgets/connection_status_header.dart';
import '../../widgets/matrix.dart';
import 'chat_list_header.dart';
class ChatListViewBody extends StatelessWidget {
final ChatListController controller;
@ -76,7 +75,10 @@ class ChatListViewBody extends StatelessWidget {
child: CustomScrollView(
controller: controller.scrollController,
slivers: [
ChatListHeader(controller: controller),
// #Pangea
// ChatListHeader(controller: controller),
ChatListHeaderWrapper(controller: controller),
// Pangea#
SliverList(
delegate: SliverChildListDelegate(
[
@ -247,17 +249,23 @@ class ChatListViewBody extends StatelessWidget {
SliverList.builder(
itemCount: rooms.length,
itemBuilder: (BuildContext context, int i) {
return ChatListItem(
// #Pangea
// return ChatListItem(
return ChatListItemWrapper(
controller: controller,
// Pangea#
rooms[i],
key: Key('chat_list_item_${rooms[i].id}'),
filter: filter,
selected:
controller.selectedRoomIds.contains(rooms[i].id),
onTap: controller.selectMode == SelectMode.select
? () => controller.toggleSelection(rooms[i].id)
: () => onChatTap(rooms[i], context),
onLongPress: () =>
controller.toggleSelection(rooms[i].id),
// #Pangea
// selected:
// controller.selectedRoomIds.contains(rooms[i].id),
// onTap: controller.selectMode == SelectMode.select
// ? () => controller.toggleSelection(rooms[i].id)
// : () => onChatTap(rooms[i], context),
// onLongPress: () =>
// controller.toggleSelection(rooms[i].id),
// Pangea#
activeChat: controller.activeChat == rooms[i].id,
);
},

View file

@ -4,7 +4,6 @@ import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/config/themes.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/utils/on_chat_tap.dart';
import 'package:fluffychat/pangea/constants/class_default_values.dart';
@ -12,6 +11,8 @@ import 'package:fluffychat/pangea/constants/pangea_room_types.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_header_wrapper.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_item_wrapper.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:flutter/material.dart';
@ -23,7 +24,6 @@ import 'package:matrix/matrix.dart';
import '../../utils/localized_exception_extension.dart';
import '../../widgets/matrix.dart';
import 'chat_list_header.dart';
class SpaceView extends StatefulWidget {
final ChatListController controller;
@ -709,7 +709,10 @@ class _SpaceViewState extends State<SpaceView> {
child: CustomScrollView(
controller: widget.scrollController,
slivers: [
ChatListHeader(controller: widget.controller),
// #Pangea
// ChatListHeader(controller: widget.controller),
ChatListHeaderWrapper(controller: widget.controller),
// Pangea#
SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) {
@ -789,7 +792,13 @@ class _SpaceViewState extends State<SpaceView> {
child: CustomScrollView(
controller: widget.scrollController,
slivers: [
ChatListHeader(controller: widget.controller, globalSearch: false),
// #Pangea
// ChatListHeader(controller: widget.controller, globalSearch: false),
ChatListHeaderWrapper(
controller: widget.controller,
globalSearch: false,
),
// Pangea#
SliverAppBar(
automaticallyImplyLeading: false,
primary: false,
@ -911,7 +920,11 @@ class _SpaceViewState extends State<SpaceView> {
room.membership != Membership.leave
// Pangea#
) {
return ChatListItem(
// #Pangea
// return ChatListItem(
return ChatListItemWrapper(
controller: widget.controller,
// Pangea#
room,
onLongPress: () =>
_onSpaceChildContextMenu(spaceChild, room),

View file

@ -0,0 +1,47 @@
import 'dart:async';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_header.dart';
import 'package:flutter/material.dart';
/// A wrapper around ChatListHeader to allow rebuilding on state changes.
/// Prevents having to rebuild the entire ChatList when a single item changes.
class ChatListHeaderWrapper extends StatefulWidget {
final ChatListController controller;
final bool globalSearch;
const ChatListHeaderWrapper({
super.key,
required this.controller,
this.globalSearch = true,
});
@override
ChatListHeaderWrapperState createState() => ChatListHeaderWrapperState();
}
class ChatListHeaderWrapperState extends State<ChatListHeaderWrapper> {
StreamSubscription? stateSub;
@override
void initState() {
super.initState();
stateSub = widget.controller.selectionsStream.stream.listen((roomID) {
setState(() {});
});
}
@override
void dispose() {
stateSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChatListHeader(
controller: widget.controller,
globalSearch: widget.globalSearch,
);
}
}

View file

@ -0,0 +1,71 @@
import 'dart:async';
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/utils/on_chat_tap.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
/// A wrapper around ChatListItem to allow rebuilding on state changes.
/// Prevents having to rebuild the entire ChatList when a single item changes.
class ChatListItemWrapper extends StatefulWidget {
final Room room;
final bool activeChat;
final void Function()? onForget;
final String? filter;
final ChatListController controller;
final void Function()? onLongPress;
final void Function()? onTap;
const ChatListItemWrapper(
this.room, {
this.activeChat = false,
this.onForget,
this.filter,
required this.controller,
this.onLongPress,
this.onTap,
super.key,
});
@override
ChatListItemWrapperState createState() => ChatListItemWrapperState();
}
class ChatListItemWrapperState extends State<ChatListItemWrapper> {
StreamSubscription? stateSub;
@override
void initState() {
super.initState();
stateSub = widget.controller.selectionsStream.stream.listen((roomID) {
if (roomID == widget.room.id) {
setState(() {});
}
});
}
@override
void dispose() {
stateSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChatListItem(
widget.room,
activeChat: widget.activeChat,
selected: widget.controller.selectedRoomIds.contains(widget.room.id),
onTap: widget.onTap ??
(widget.controller.selectMode == SelectMode.select
? () => widget.controller.toggleSelection(widget.room.id)
: () => onChatTap(widget.room, context)),
onLongPress: widget.onLongPress ??
() => widget.controller.toggleSelection(widget.room.id),
onForget: widget.onForget,
filter: widget.filter,
);
}
}