LCOV - code coverage report
Current view: top level - src - channels_bloc.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 87 87 100.0 %
Date: 2021-05-27 10:59:48 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : 
       3             : import 'package:flutter/foundation.dart';
       4             : import 'package:flutter/material.dart';
       5             : import 'package:rxdart/rxdart.dart';
       6             : import 'package:stream_chat/stream_chat.dart';
       7             : import 'package:stream_chat_flutter_core/src/channel_list_core.dart';
       8             : import 'package:stream_chat_flutter_core/src/stream_chat_core.dart';
       9             : 
      10             : /// Widget dedicated to the management of a channel list with pagination
      11             : /// [ChannelsBloc] is used together with [ChannelListCore] to manage a list of
      12             : /// [Channel]s with pagination, re-ordering, querying and other operations
      13             : /// associated with [Channel]s.
      14             : ///
      15             : /// [ChannelsBloc] can be access at anytime by using the static [of] method
      16             : /// using Flutter's [BuildContext].
      17             : ///
      18             : /// API docs: https://getstream.io/chat/docs/flutter-dart/query_channels/
      19             : class ChannelsBloc extends StatefulWidget {
      20             :   /// Creates a new [ChannelsBloc]. The parameter [child] must be supplied and
      21             :   /// not null.
      22           2 :   const ChannelsBloc({
      23             :     Key? key,
      24             :     required this.child,
      25             :     this.lockChannelsOrder = false,
      26             :     this.channelsComparator,
      27             :     this.shouldAddChannel,
      28           2 :   }) : super(key: key);
      29             : 
      30             :   /// The widget child
      31             :   final Widget child;
      32             : 
      33             :   /// Set this to true to prevent channels to be brought to the top of the list
      34             :   /// when a new message arrives
      35             :   final bool lockChannelsOrder;
      36             : 
      37             :   /// Comparator used to sort the channels when a message.new event is received
      38             :   final Comparator<Channel>? channelsComparator;
      39             : 
      40             :   /// Function used to evaluate if a channel should be added to the list when a
      41             :   /// message.new event is received
      42             :   final bool Function(Event)? shouldAddChannel;
      43             : 
      44           2 :   @override
      45           2 :   ChannelsBlocState createState() => ChannelsBlocState();
      46             : 
      47             :   /// Use this method to get the current [ChannelsBlocState] instance
      48           1 :   static ChannelsBlocState of(BuildContext context) {
      49             :     ChannelsBlocState? streamChatState;
      50             : 
      51           1 :     streamChatState = context.findAncestorStateOfType<ChannelsBlocState>();
      52             : 
      53             :     assert(
      54           1 :       streamChatState != null,
      55             :       'You must have a ChannelsBloc widget as ancestor',
      56             :     );
      57             : 
      58             :     return streamChatState!;
      59             :   }
      60             : }
      61             : 
      62             : /// The current state of the [ChannelsBloc].
      63             : class ChannelsBlocState extends State<ChannelsBloc>
      64             :     with AutomaticKeepAliveClientMixin {
      65             :   StreamChatCoreState? _streamChatCoreState;
      66             : 
      67           2 :   @override
      68             :   Widget build(BuildContext context) {
      69           2 :     super.build(context);
      70           4 :     return widget.child;
      71             :   }
      72             : 
      73             :   /// The current channel list
      74           6 :   List<Channel>? get channels => _channelsController.valueOrNull;
      75             : 
      76             :   /// The current channel list as a stream
      77           6 :   Stream<List<Channel>> get channelsStream => _channelsController.stream;
      78             : 
      79             :   final _queryChannelsLoadingController = BehaviorSubject.seeded(false);
      80             : 
      81             :   final BehaviorSubject<List<Channel>> _channelsController =
      82             :       BehaviorSubject<List<Channel>>();
      83             : 
      84             :   /// The stream notifying the state of queryChannel call
      85           1 :   Stream<bool> get queryChannelsLoading =>
      86           2 :       _queryChannelsLoadingController.stream;
      87             : 
      88             :   final List<Channel> _hiddenChannels = [];
      89             : 
      90             :   bool _paginationEnded = false;
      91             : 
      92             :   final List<StreamSubscription> _subscriptions = [];
      93             : 
      94             :   /// Calls [client.queryChannels] updating [queryChannelsLoading] stream
      95           2 :   Future<void> queryChannels({
      96             :     Filter? filter,
      97             :     List<SortOption<ChannelModel>>? sortOptions,
      98             :     PaginationParams paginationParams = const PaginationParams(limit: 30),
      99             :     Map<String, dynamic>? options,
     100             :   }) async {
     101           4 :     final client = _streamChatCoreState!.client;
     102             : 
     103           4 :     final clear = paginationParams.offset == 0;
     104             : 
     105           2 :     if ((!clear && _paginationEnded) ||
     106           6 :         _queryChannelsLoadingController.value == true) {
     107             :       return;
     108             :     }
     109             : 
     110           4 :     if (_channelsController.hasValue) {
     111           4 :       _queryChannelsLoadingController.add(true);
     112             :     }
     113             : 
     114             :     try {
     115           6 :       final oldChannels = List<Channel>.from(channels ?? []);
     116           2 :       var newChannels = <Channel>[];
     117           4 :       await for (final channels in client.queryChannels(
     118             :         filter: filter,
     119             :         sort: sortOptions,
     120             :         options: options,
     121             :         paginationParams: paginationParams,
     122           2 :       )) {
     123             :         newChannels = channels;
     124             :         if (clear) {
     125           4 :           _channelsController.add(channels);
     126             :         } else {
     127           2 :           final temp = oldChannels + channels;
     128           4 :           _channelsController.add(temp);
     129             :         }
     130           4 :         if (_channelsController.hasValue &&
     131           4 :             _queryChannelsLoadingController.value) {
     132           6 :           _queryChannelsLoadingController.sink.add(false);
     133             :         }
     134             :       }
     135           8 :       if (newChannels.isEmpty || newChannels.length < paginationParams.limit) {
     136           2 :         _paginationEnded = true;
     137             :       }
     138             :     } catch (e, stk) {
     139           4 :       if (_channelsController.hasValue) {
     140           2 :         _queryChannelsLoadingController.addError(e, stk);
     141             :       } else {
     142           4 :         _channelsController.addError(e, stk);
     143             :       }
     144             :     }
     145             :   }
     146             : 
     147           2 :   @override
     148             :   void didChangeDependencies() {
     149           4 :     final newStreamChatCoreState = StreamChatCore.of(context);
     150             : 
     151           4 :     if (newStreamChatCoreState != _streamChatCoreState) {
     152           2 :       _streamChatCoreState = newStreamChatCoreState;
     153           4 :       final client = _streamChatCoreState!.client;
     154             : 
     155           2 :       _cancelSubscriptions();
     156           4 :       if (!widget.lockChannelsOrder) {
     157           4 :         _subscriptions.add(client
     158           2 :             .on(
     159             :           EventType.messageNew,
     160             :         )
     161           3 :             .listen((e) {
     162           2 :           final newChannels = List<Channel>.from(channels ?? []);
     163           5 :           final index = newChannels.indexWhere((c) => c.cid == e.cid);
     164           2 :           if (index != -1) {
     165           1 :             if (index > 0) {
     166           1 :               final channel = newChannels.removeAt(index);
     167           1 :               newChannels.insert(0, channel);
     168             :             }
     169           4 :           } else if (widget.shouldAddChannel?.call(e) == true) {
     170             :             final hiddenIndex =
     171           6 :                 _hiddenChannels.indexWhere((c) => c.cid == e.cid);
     172           2 :             if (hiddenIndex != -1) {
     173           3 :               newChannels.insert(0, _hiddenChannels[hiddenIndex]);
     174           2 :               _hiddenChannels.removeAt(hiddenIndex);
     175             :             } else {
     176           4 :               if (client.state.channels[e.cid] != null) {
     177           5 :                 newChannels.insert(0, client.state.channels[e.cid]!);
     178             :               }
     179             :             }
     180             :           }
     181             : 
     182           2 :           if (widget.channelsComparator != null) {
     183           3 :             newChannels.sort(widget.channelsComparator);
     184             :           }
     185           2 :           _channelsController.add(newChannels);
     186             :         }));
     187             :       }
     188             : 
     189           2 :       _subscriptions
     190           7 :         ..add(client.on(EventType.channelHidden).listen((event) async {
     191           2 :           final newChannels = List<Channel>.from(channels ?? []);
     192             :           final channelIndex =
     193           5 :               newChannels.indexWhere((c) => c.cid == event.cid);
     194           2 :           if (channelIndex > -1) {
     195           1 :             final channel = newChannels.removeAt(channelIndex);
     196           2 :             _hiddenChannels.add(channel);
     197           2 :             _channelsController.add(newChannels);
     198             :           }
     199             :         }))
     200           2 :         ..add(client
     201           2 :             .on(
     202             :           EventType.channelDeleted,
     203             :           EventType.notificationRemovedFromChannel,
     204             :         )
     205           3 :             .listen((e) {
     206           1 :           final channel = e.channel;
     207           3 :           _channelsController.add(List.from(
     208           6 :               (channels ?? [])..removeWhere((c) => c.cid == channel?.cid)));
     209             :         }));
     210             :     }
     211             : 
     212           2 :     super.didChangeDependencies();
     213             :   }
     214             : 
     215           2 :   @override
     216             :   void dispose() {
     217           4 :     _channelsController.close();
     218           4 :     _queryChannelsLoadingController.close();
     219           2 :     _cancelSubscriptions();
     220           2 :     super.dispose();
     221             :   }
     222             : 
     223           2 :   void _cancelSubscriptions() {
     224           2 :     _subscriptions
     225           6 :       ..forEach((s) => s.cancel())
     226           2 :       ..clear();
     227             :   }
     228             : 
     229           2 :   @override
     230             :   bool get wantKeepAlive => true;
     231             : }

Generated by: LCOV version 1.14