Skip to content

Commit cfb7bff

Browse files
feat: ✨ Add Animation in ChatViewList
1 parent 1a911d0 commit cfb7bff

File tree

9 files changed

+934
-146
lines changed

9 files changed

+934
-146
lines changed

example/lib/chat_operations.dart

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import 'dart:math';
2+
3+
import 'package:chatview/chatview.dart';
4+
import 'package:flutter/material.dart';
5+
6+
class ChatOperations extends StatelessWidget {
7+
const ChatOperations({required this.controller, super.key});
8+
9+
final ChatViewListController controller;
10+
11+
@override
12+
Widget build(BuildContext context) {
13+
return Row(
14+
mainAxisSize: MainAxisSize.min,
15+
children: [
16+
FloatingActionButton.small(
17+
child: const Icon(Icons.add),
18+
onPressed: () {
19+
controller.addChat(
20+
ChatViewListItem(
21+
id: DateTime.now().millisecondsSinceEpoch.toString(),
22+
name: 'New Chat ${DateTime.now().millisecondsSinceEpoch}',
23+
lastMessage: Message(
24+
message: 'Hello, this is a new chat!',
25+
createdAt: DateTime.now(),
26+
sentBy: 'User',
27+
id: 'msg-${DateTime.now().millisecondsSinceEpoch}',
28+
),
29+
settings: const ChatSettings(
30+
pinStatus: PinStatus.unpinned,
31+
muteStatus: MuteStatus.unmute,
32+
),
33+
),
34+
);
35+
},
36+
),
37+
const SizedBox(width: 6),
38+
FloatingActionButton.small(
39+
child: const Icon(Icons.remove),
40+
onPressed: () {
41+
final key = controller.chatListMap.keys.firstOrNull;
42+
if (key == null) return;
43+
controller.removeChat(key);
44+
},
45+
),
46+
const SizedBox(width: 6),
47+
FloatingActionButton.small(
48+
child: const Icon(Icons.edit),
49+
onPressed: () {
50+
final randomChatIndex =
51+
Random().nextInt(controller.chatListMap.length);
52+
final chatId =
53+
controller.chatListMap.keys.elementAt(randomChatIndex);
54+
55+
controller.updateChat(
56+
chatId,
57+
(previousChat) {
58+
final newPinStatus = switch (previousChat.settings.pinStatus) {
59+
PinStatus.pinned => PinStatus.unpinned,
60+
PinStatus.unpinned => PinStatus.pinned,
61+
};
62+
return previousChat.copyWith(
63+
settings: previousChat.settings.copyWith(
64+
pinStatus: newPinStatus,
65+
pinTime: newPinStatus.isPinned ? DateTime.now() : null,
66+
),
67+
);
68+
},
69+
);
70+
},
71+
),
72+
const SizedBox(width: 6),
73+
FloatingActionButton.small(
74+
child: const Icon(Icons.person),
75+
onPressed: () {
76+
final randomChatIndex =
77+
Random().nextInt(controller.chatListMap.length);
78+
final chatId =
79+
controller.chatListMap.keys.elementAt(randomChatIndex);
80+
controller.updateChat(
81+
chatId,
82+
(previousChat) => previousChat.copyWith(
83+
name: 'Updated Chat ${Random().nextInt(100)}',
84+
),
85+
);
86+
},
87+
),
88+
const SizedBox(width: 6),
89+
FloatingActionButton.small(
90+
child: const Icon(Icons.more_horiz),
91+
onPressed: () {
92+
final chatId = controller.chatListMap.keys.elementAt(0);
93+
controller.updateChat(
94+
chatId,
95+
(previousChat) => previousChat.copyWith(
96+
typingUsers: previousChat.typingUsers.isEmpty
97+
? {const ChatUser(id: '1', name: 'John Doe')}
98+
: {},
99+
),
100+
);
101+
},
102+
),
103+
const SizedBox(width: 6),
104+
FloatingActionButton.small(
105+
child: const Icon(Icons.message),
106+
onPressed: () {
107+
final randomChatIndex =
108+
Random().nextInt(controller.chatListMap.length);
109+
final chatId =
110+
controller.chatListMap.keys.elementAt(randomChatIndex);
111+
final randomId = Random().nextInt(100);
112+
controller.updateChat(
113+
chatId,
114+
(previousChat) => previousChat.copyWith(
115+
lastMessage: Message(
116+
message: 'Random message $randomId',
117+
createdAt: DateTime.now(),
118+
sentBy: previousChat.lastMessage?.sentBy ?? '',
119+
id: '$randomId',
120+
),
121+
),
122+
);
123+
},
124+
),
125+
],
126+
);
127+
}
128+
}

lib/src/controller/chat_list_view_controller.dart

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import 'package:flutter/material.dart';
2626
import '../models/chat_view_list_item.dart';
2727
import '../values/enumeration.dart';
2828
import '../values/typedefs.dart';
29+
import '../widgets/chat_view_list/auto_animate_sliver_list.dart';
2930

3031
class ChatViewListController {
3132
ChatViewListController({
@@ -64,10 +65,14 @@ class ChatViewListController {
6465
);
6566
}
6667

68+
final animatedList =
69+
GlobalKey<AutoAnimateSliverListState<ChatViewListItem>>();
70+
6771
/// Stores and manages chat items by their unique IDs.
6872
/// A map is used for efficient lookup, update, and removal of chats
6973
/// by their unique id.
7074
Map<String, ChatViewListItem> chatListMap = {};
75+
Map<String, ChatViewListItem>? _searchResultMap;
7176

7277
/// Provides scroll controller for chat list.
7378
ScrollController scrollController;
@@ -83,9 +88,39 @@ class ChatViewListController {
8388

8489
/// Adds a chat to the chat list.
8590
void addChat(ChatViewListItem chat) {
91+
if (_searchResultMap != null) {
92+
chatListMap[chat.id] = chat;
93+
return;
94+
}
95+
8696
chatListMap[chat.id] = chat;
8797
if (_chatListStreamController.isClosed) return;
8898
_chatListStreamController.add(chatListMap);
99+
animatedList.currentState?.addChatItem(
100+
chat,
101+
isPinned: (item) => item.settings.pinStatus.isPinned,
102+
);
103+
}
104+
105+
/// Removes the chat with the given [chatId] from the chat list.
106+
///
107+
/// If the chat with [chatId] does not exist, the method returns without
108+
/// making changes.
109+
void removeChat(String chatId) {
110+
chatListMap.remove(chatId);
111+
112+
if (_searchResultMap != null) {
113+
if (_searchResultMap?.containsKey(chatId) ?? false) {
114+
_searchResultMap?.remove(chatId);
115+
if (_chatListStreamController.isClosed) return;
116+
animatedList.currentState?.removeItemByKey(chatId);
117+
_chatListStreamController.add(_searchResultMap ?? chatListMap);
118+
}
119+
return;
120+
}
121+
if (_chatListStreamController.isClosed) return;
122+
animatedList.currentState?.removeItemByKey(chatId);
123+
_chatListStreamController.add(chatListMap);
89124
}
90125

91126
/// Function for loading data while pagination.
@@ -107,6 +142,23 @@ class ChatViewListController {
107142
/// If the chat with [chatId] does not exist, the method returns without
108143
/// making changes.
109144
void updateChat(String chatId, UpdateChatCallback newChat) {
145+
if (_searchResultMap != null) {
146+
final searchChat = _searchResultMap?[chatId];
147+
if (searchChat == null) {
148+
final chat = chatListMap[chatId];
149+
if (chat == null) return;
150+
chatListMap[chatId] = newChat(chat);
151+
return;
152+
}
153+
154+
final updatedChat = newChat(searchChat);
155+
_searchResultMap?[chatId] = updatedChat;
156+
chatListMap[chatId] = updatedChat;
157+
if (_chatListStreamController.isClosed) return;
158+
_chatListStreamController.add(_searchResultMap ?? chatListMap);
159+
return;
160+
}
161+
110162
final chat = chatListMap[chatId];
111163
if (chat == null) return;
112164

@@ -115,29 +167,21 @@ class ChatViewListController {
115167
_chatListStreamController.add(chatListMap);
116168
}
117169

118-
/// Removes the chat with the given [chatId] from the chat list.
119-
///
120-
/// If the chat with [chatId] does not exist, the method returns without
121-
/// making changes.
122-
void removeChat(String chatId) {
123-
chatListMap.remove(chatId);
124-
if (_chatListStreamController.isClosed) return;
125-
_chatListStreamController.add(chatListMap);
126-
}
127-
128170
/// Adds the given chat search results to the stream after the current frame.
129171
void setSearchChats(List<ChatViewListItem> searchResults) {
130172
final searchResultLength = searchResults.length;
131-
final searchResultMap = {
173+
_searchResultMap = {
132174
for (var i = 0; i < searchResultLength; i++)
133175
if (searchResults[i] case final chat) chat.id: chat,
134176
};
135177
if (_chatListStreamController.isClosed) return;
136-
_chatListStreamController.add(searchResultMap);
178+
_chatListStreamController.add(_searchResultMap ?? chatListMap);
137179
}
138180

139181
/// Function to clear the search results and show the original chat list.
140182
void clearSearch() {
183+
_searchResultMap?.clear();
184+
_searchResultMap = null;
141185
if (_chatListStreamController.isClosed) return;
142186
_chatListStreamController.add(chatListMap);
143187
}

lib/src/models/config_models/chat_view_list/chat_view_list_config.dart

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,13 @@ class ChatViewListConfig {
3333
this.enablePagination = false,
3434
this.loadMoreConfig = const LoadMoreConfig(),
3535
this.tileConfig = const ListTileConfig(),
36-
this.separator = const Divider(height: 12),
3736
this.extraSpaceAtLast = 32,
3837
this.searchConfig,
3938
});
4039

4140
/// Configuration for the search text field in the chat list.
4241
final ChatViewListSearchConfig? searchConfig;
4342

44-
/// Widget to be used as a separator between chat items.
45-
///
46-
/// Defaults to a `Divider` with a height of `12`.
47-
final Widget separator;
48-
4943
/// Configuration for the chat tile widget in the chat list.
5044
final ListTileConfig tileConfig;
5145

lib/src/values/typedefs.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,10 @@ typedef MenuBuilderCallback = Widget Function(
144144
Widget child,
145145
);
146146
typedef MenuActionBuilder = List<Widget> Function(ChatViewListItem chat);
147+
typedef AutoAnimateItemBuilder<T> = Widget Function(
148+
BuildContext context,
149+
int index,
150+
bool isLastItem,
151+
T item,
152+
);
153+
typedef AutoAnimateItemExtractor<T> = String Function(T item);

0 commit comments

Comments
 (0)