refactor: optimize ChatsDrawer for performance and smooth scrolling

- Added cacheExtent to the ListView in ChatsDrawer for improved scrolling smoothness.
- Updated activeConversationProvider selection to only rebuild tiles when their selected state changes, enhancing performance.
- Replaced Padding with RepaintBoundary to optimize rendering during drag operations.
- Changed clipBehavior to Clip.hardEdge for better performance in avatar rendering.
This commit is contained in:
cogwheel0
2025-10-10 13:12:26 +05:30
parent 778116e258
commit 9fff4d49ea

View File

@@ -108,6 +108,8 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
key: const PageStorageKey<String>('chats_drawer_scroll'), key: const PageStorageKey<String>('chats_drawer_scroll'),
controller: _listController, controller: _listController,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
// Precache a bit ahead for perceived smoothness when scrolling.
cacheExtent: 800,
padding: padding, padding: padding,
children: children, children: children,
), ),
@@ -1068,7 +1070,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
} }
Widget _buildTileFor(dynamic conv, {bool inFolder = false}) { Widget _buildTileFor(dynamic conv, {bool inFolder = false}) {
final isActive = ref.watch(activeConversationProvider)?.id == conv.id; // Only rebuild this tile when its own selected state changes.
final isActive = ref.watch(
activeConversationProvider.select((c) => c?.id == conv.id),
);
final title = conv.title?.isEmpty == true ? 'Chat' : (conv.title ?? 'Chat'); final title = conv.title?.isEmpty == true ? 'Chat' : (conv.title ?? 'Chat');
final theme = context.conduitTheme; final theme = context.conduitTheme;
final bool isLoadingSelected = final bool isLoadingSelected =
@@ -1124,38 +1129,40 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
}, },
); );
return Padding( return RepaintBoundary(
padding: EdgeInsets.only( child: Padding(
bottom: Spacing.xs, padding: EdgeInsets.only(
left: inFolder ? Spacing.md : 0, bottom: Spacing.xs,
), left: inFolder ? Spacing.md : 0,
child: LongPressDraggable<_DragConversationData>(
data: _DragConversationData(id: conv.id, title: title),
dragAnchorStrategy: pointerDragAnchorStrategy,
feedback: _ConversationDragFeedback(
title: title,
pinned: isPinned,
theme: theme,
), ),
childWhenDragging: Opacity( child: LongPressDraggable<_DragConversationData>(
opacity: 0.5, data: _DragConversationData(id: conv.id, title: title),
child: IgnorePointer(child: tile), dragAnchorStrategy: pointerDragAnchorStrategy,
feedback: _ConversationDragFeedback(
title: title,
pinned: isPinned,
theme: theme,
),
childWhenDragging: Opacity(
opacity: 0.5,
child: IgnorePointer(child: tile),
),
onDragStarted: () {
HapticFeedback.lightImpact();
final hasFolder =
(conv.folderId != null && (conv.folderId as String).isNotEmpty);
setState(() {
_isDragging = true;
_draggingHasFolder = hasFolder;
});
},
onDragEnd: (_) => setState(() {
_dragHoverFolderId = null;
_isDragging = false;
_draggingHasFolder = false;
}),
child: tile,
), ),
onDragStarted: () {
HapticFeedback.lightImpact();
final hasFolder =
(conv.folderId != null && (conv.folderId as String).isNotEmpty);
setState(() {
_isDragging = true;
_draggingHasFolder = hasFolder;
});
},
onDragEnd: (_) => setState(() {
_dragHoverFolderId = null;
_isDragging = false;
_draggingHasFolder = false;
}),
child: tile,
), ),
); );
} }
@@ -1375,7 +1382,9 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
width: BorderWidth.thin, width: BorderWidth.thin,
), ),
), ),
clipBehavior: Clip.antiAlias, // Hard-edge clipping is cheaper than anti-aliased clipping
// and sufficient for avatar squares with rounded corners.
clipBehavior: Clip.hardEdge,
child: UserAvatar( child: UserAvatar(
size: 36, size: 36,
imageUrl: avatarUrl, imageUrl: avatarUrl,