Line data Source code
1 : import 'dart:async';
2 : import 'dart:convert';
3 :
4 : import 'package:flutter/foundation.dart';
5 : import 'package:flutter/material.dart';
6 : import 'package:stream_chat/stream_chat.dart';
7 : import 'package:stream_chat_flutter_core/src/channels_bloc.dart';
8 : import 'package:stream_chat_flutter_core/src/stream_chat_core.dart';
9 : import 'package:stream_chat_flutter_core/src/typedef.dart';
10 :
11 : /// [ChannelListCore] is a simplified class that allows fetching a list of
12 : /// channels while exposing UI builders.
13 : /// A [ChannelListController] is used to reload and paginate data.
14 : ///
15 : ///
16 : /// ```dart
17 : /// class ChannelListPage extends StatelessWidget {
18 : /// @override
19 : /// Widget build(BuildContext context) {
20 : /// return Scaffold(
21 : /// body: ChannelListCore(
22 : /// filter: {
23 : /// 'members': {
24 : /// '\$in': [StreamChat.of(context).user.id],
25 : /// }
26 : /// },
27 : /// sort: [SortOption('last_message_at')],
28 : /// pagination: PaginationParams(
29 : /// limit: 20,
30 : /// ),
31 : /// errorBuilder: (err) {
32 : /// return Center(
33 : /// child: Text('An error has occured'),
34 : /// );
35 : /// },
36 : /// emptyBuilder: (context) {
37 : /// return Center(
38 : /// child: Text('Nothing here...'),
39 : /// );
40 : /// },
41 : /// emptyBuilder: (context) {
42 : /// return Center(
43 : /// child: CircularProgressIndicator(),
44 : /// );
45 : /// },
46 : /// listBuilder: (context, list) {
47 : /// return ChannelPage(list);
48 : /// }
49 : /// ),
50 : /// );
51 : /// }
52 : /// }
53 : /// ```
54 : ///
55 : /// Make sure to have a [StreamChatCore] ancestor in order to provide the
56 : /// information about the channels.
57 : class ChannelListCore extends StatefulWidget {
58 : /// Instantiate a new ChannelListView
59 1 : const ChannelListCore({
60 : Key? key,
61 : required this.errorBuilder,
62 : required this.emptyBuilder,
63 : required this.loadingBuilder,
64 : required this.listBuilder,
65 : this.filter,
66 : this.options,
67 : this.sort,
68 : this.pagination = const PaginationParams(
69 : limit: 25,
70 : ),
71 : this.channelListController,
72 1 : }) : super(key: key);
73 :
74 : /// A [ChannelListController] allows reloading and pagination.
75 : /// Use [ChannelListController.loadData] and
76 : /// [ChannelListController.paginateData] respectively for reloading and
77 : /// pagination.
78 : final ChannelListController? channelListController;
79 :
80 : /// The builder that will be used in case of error
81 : final ErrorBuilder errorBuilder;
82 :
83 : /// The builder that will be used in case of loading
84 : final WidgetBuilder loadingBuilder;
85 :
86 : /// The builder which is used when list of channels loads
87 : final Function(BuildContext, List<Channel>) listBuilder;
88 :
89 : /// The builder used when the channel list is empty.
90 : final WidgetBuilder emptyBuilder;
91 :
92 : /// The query filters to use.
93 : /// You can query on any of the custom fields you've defined on the [Channel].
94 : /// You can also filter other built-in channel fields.
95 : final Filter? filter;
96 :
97 : /// Query channels options.
98 : ///
99 : /// state: if true returns the Channel state
100 : /// watch: if true listen to changes to this Channel in real time.
101 : final Map<String, dynamic>? options;
102 :
103 : /// The sorting used for the channels matching the filters.
104 : /// Sorting is based on field and direction, multiple sorting options can be
105 : /// provided.
106 : /// You can sort based on last_updated, last_message_at, updated_at, created
107 : /// _at or member_count. Direction can be ascending or descending.
108 : final List<SortOption<ChannelModel>>? sort;
109 :
110 : /// Pagination parameters
111 : /// limit: the number of channels to return (max is 30)
112 : /// offset: the offset (max is 1000)
113 : /// message_limit: how many messages should be included to each channel
114 : final PaginationParams pagination;
115 :
116 1 : @override
117 1 : ChannelListCoreState createState() => ChannelListCoreState();
118 : }
119 :
120 : /// The current state of the [ChannelListCore].
121 : class ChannelListCoreState extends State<ChannelListCore> {
122 : late ChannelsBlocState _channelsBloc;
123 : StreamChatCoreState? _streamChatCoreState;
124 :
125 1 : @override
126 2 : Widget build(BuildContext context) => _buildListView(_channelsBloc);
127 :
128 1 : StreamBuilder<List<Channel>> _buildListView(
129 : ChannelsBlocState channelsBlocState,
130 : ) =>
131 1 : StreamBuilder<List<Channel>>(
132 1 : stream: channelsBlocState.channelsStream,
133 1 : builder: (context, snapshot) {
134 1 : if (snapshot.hasError) {
135 4 : return widget.errorBuilder(context, snapshot.error!);
136 : }
137 1 : if (!snapshot.hasData) {
138 3 : return widget.loadingBuilder(context);
139 : }
140 1 : final channels = snapshot.data!;
141 1 : if (channels.isEmpty) {
142 3 : return widget.emptyBuilder(context);
143 : }
144 3 : return widget.listBuilder(context, channels);
145 : },
146 : );
147 :
148 : /// Fetches initial channels and updates the widget
149 3 : Future<void> loadData() => _channelsBloc.queryChannels(
150 2 : filter: widget.filter,
151 2 : sortOptions: widget.sort,
152 2 : paginationParams: widget.pagination,
153 2 : options: widget.options,
154 : );
155 :
156 : /// Fetches more channels with updated pagination and updates the widget
157 3 : Future<void> paginateData() => _channelsBloc.queryChannels(
158 2 : filter: widget.filter,
159 2 : sortOptions: widget.sort,
160 3 : paginationParams: widget.pagination.copyWith(
161 3 : offset: _channelsBloc.channels?.length ?? 0,
162 : ),
163 2 : options: widget.options,
164 : );
165 :
166 : StreamSubscription<Event>? _subscription;
167 :
168 1 : @override
169 : void initState() {
170 1 : super.initState();
171 1 : _setupController();
172 : }
173 :
174 1 : @override
175 : void didChangeDependencies() {
176 3 : _channelsBloc = ChannelsBloc.of(context);
177 2 : final newStreamChatCoreState = StreamChatCore.of(context);
178 :
179 2 : if (newStreamChatCoreState != _streamChatCoreState) {
180 1 : _streamChatCoreState = newStreamChatCoreState;
181 1 : loadData();
182 2 : final client = _streamChatCoreState!.client;
183 1 : _subscription?.cancel();
184 1 : _subscription = client
185 1 : .on(
186 : EventType.connectionRecovered,
187 : EventType.notificationAddedToChannel,
188 : EventType.notificationMessageNew,
189 : EventType.channelVisible,
190 : )
191 1 : .listen((event) => loadData());
192 : }
193 :
194 1 : super.didChangeDependencies();
195 : }
196 :
197 1 : @override
198 : void didUpdateWidget(ChannelListCore oldWidget) {
199 1 : super.didUpdateWidget(oldWidget);
200 :
201 4 : if (widget.filter?.toString() != oldWidget.filter?.toString() ||
202 6 : jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
203 4 : widget.options?.toString() != oldWidget.options?.toString() ||
204 5 : widget.pagination.toJson().toString() !=
205 3 : oldWidget.pagination.toJson().toString()) {
206 1 : loadData();
207 : }
208 :
209 4 : if (widget.channelListController != oldWidget.channelListController) {
210 0 : _setupController();
211 : }
212 : }
213 :
214 1 : void _setupController() {
215 2 : if (widget.channelListController != null) {
216 4 : widget.channelListController!.loadData = loadData;
217 4 : widget.channelListController!.paginateData = paginateData;
218 : }
219 : }
220 :
221 1 : @override
222 : void dispose() {
223 2 : _subscription?.cancel();
224 1 : super.dispose();
225 : }
226 : }
227 :
228 : /// Controller used for loading more data and controlling pagination in
229 : /// [ChannelListCore].
230 : class ChannelListController {
231 : /// This function calls Stream's servers to load a list of channels.
232 : /// If there is existing data, calling this function causes a reload.
233 : AsyncCallback? loadData;
234 :
235 : /// This function is used to load another page of data. Note, [loadData]
236 : /// should be used to populate the initial page of data. Calling
237 : /// [paginateData] performs a query to load subsequent pages.
238 : AsyncCallback? paginateData;
239 : }
|