feat(ui): add channel list event handler in channel list controller.
Signed-off-by: xsahil03x <[email protected]>
This commit is contained in:
@@ -10,6 +10,14 @@ const defaultInitialPagedLimitMultiplier = 3;
|
|||||||
typedef PagedValueListenableBuilder<Key, Value>
|
typedef PagedValueListenableBuilder<Key, Value>
|
||||||
= ValueListenableBuilder<PagedValue<Key, Value>>;
|
= ValueListenableBuilder<PagedValue<Key, Value>>;
|
||||||
|
|
||||||
|
/// A [PagedValueNotifier] that uses a [PagedListenable] to load data.
|
||||||
|
///
|
||||||
|
/// This class is useful when you need to load data from a server
|
||||||
|
/// using a [PagedListenable] and want to keep the UI-driven refresh
|
||||||
|
/// signals in the [PagedListenable].
|
||||||
|
///
|
||||||
|
/// [PagedValueNotifier] is a [ValueNotifier] that emits a [PagedValue]
|
||||||
|
/// whenever the data is loaded or an error occurs.
|
||||||
abstract class PagedValueNotifier<Key, Value>
|
abstract class PagedValueNotifier<Key, Value>
|
||||||
extends ValueNotifier<PagedValue<Key, Value>> {
|
extends ValueNotifier<PagedValue<Key, Value>> {
|
||||||
/// Creates a [PagedValueNotifier]
|
/// Creates a [PagedValueNotifier]
|
||||||
@@ -18,14 +26,33 @@ abstract class PagedValueNotifier<Key, Value>
|
|||||||
/// Stores initialValue in case we need to call [refresh].
|
/// Stores initialValue in case we need to call [refresh].
|
||||||
final PagedValue<Key, Value> _initialValue;
|
final PagedValue<Key, Value> _initialValue;
|
||||||
|
|
||||||
|
/// Returns the currently loaded items
|
||||||
|
List<Value> get currentItems => value.asSuccess.items;
|
||||||
|
|
||||||
|
/// Appends [newItems] to the previously loaded ones and replaces
|
||||||
|
/// the next page's key.
|
||||||
|
void appendPage({
|
||||||
|
required List<Value> newItems,
|
||||||
|
required Key nextPageKey,
|
||||||
|
}) {
|
||||||
|
final updatedItems = currentItems + newItems;
|
||||||
|
value = PagedValue(items: updatedItems, nextPageKey: nextPageKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends [newItems] to the previously loaded ones and sets the next page
|
||||||
|
/// key to `null`.
|
||||||
|
void appendLastPage(List<Value> newItems) {
|
||||||
|
final updatedItems = currentItems + newItems;
|
||||||
|
value = PagedValue(items: updatedItems);
|
||||||
|
}
|
||||||
|
|
||||||
/// Retry any failed load requests.
|
/// Retry any failed load requests.
|
||||||
///
|
///
|
||||||
/// Unlike [refresh], this does not resets the whole [value],
|
/// Unlike [refresh], this does not resets the whole [value],
|
||||||
/// it only retries the last failed load request.
|
/// it only retries the last failed load request.
|
||||||
Future<void> retry() {
|
Future<void> retry() {
|
||||||
var lastValue = value;
|
final lastValue = value.asSuccess;
|
||||||
assert(lastValue.hasError, '');
|
assert(lastValue.hasError, '');
|
||||||
lastValue = lastValue as Success<Key, Value>;
|
|
||||||
|
|
||||||
final nextPageKey = lastValue.nextPageKey;
|
final nextPageKey = lastValue.nextPageKey;
|
||||||
// resetting the error
|
// resetting the error
|
||||||
@@ -53,7 +80,7 @@ abstract class PagedValueNotifier<Key, Value>
|
|||||||
abstract class PagedValue<Key, Value> with _$PagedValue<Key, Value> {
|
abstract class PagedValue<Key, Value> with _$PagedValue<Key, Value> {
|
||||||
const PagedValue._();
|
const PagedValue._();
|
||||||
|
|
||||||
/// Creates a new instance of [PagedValue] with the given [key] and [value].
|
/// Represents the success state of the [PagedValue]
|
||||||
// @Assert(
|
// @Assert(
|
||||||
// 'nextPageKey != null',
|
// 'nextPageKey != null',
|
||||||
// 'Cannot set an error if all the pages are already fetched',
|
// 'Cannot set an error if all the pages are already fetched',
|
||||||
@@ -69,24 +96,35 @@ abstract class PagedValue<Key, Value> with _$PagedValue<Key, Value> {
|
|||||||
StreamChatError? error,
|
StreamChatError? error,
|
||||||
}) = Success<Key, Value>;
|
}) = Success<Key, Value>;
|
||||||
|
|
||||||
bool get hasNextPage {
|
/// Represents the loading state of the [PagedValue].
|
||||||
assert(this is Success<Key, Value>, '');
|
const factory PagedValue.loading() = Loading;
|
||||||
return (this as Success<Key, Value>).nextPageKey != null;
|
|
||||||
|
/// Represents the error state of the [PagedValue].
|
||||||
|
const factory PagedValue.error(StreamChatError error) = Error;
|
||||||
|
|
||||||
|
/// Returns `true` if the [PagedValue] is [Success].
|
||||||
|
bool get isSuccess => this is Success<Key, Value>;
|
||||||
|
|
||||||
|
/// Returns the [PagedValue] as [Success].
|
||||||
|
Success<Key, Value> get asSuccess {
|
||||||
|
assert(
|
||||||
|
isSuccess,
|
||||||
|
'Cannot get asSuccess if the PagedValue is not in the Success state',
|
||||||
|
);
|
||||||
|
return this as Success<Key, Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get hasError {
|
/// Returns `true` if the [PagedValue] is [Success]
|
||||||
assert(this is Success<Key, Value>, '');
|
/// and has more items to load.
|
||||||
return (this as Success<Key, Value>).error != null;
|
bool get hasNextPage => asSuccess.nextPageKey != null;
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Returns `true` if the [PagedValue] is [Success] and has an error.
|
||||||
|
bool get hasError => asSuccess.error != null;
|
||||||
|
|
||||||
|
///
|
||||||
int get itemCount {
|
int get itemCount {
|
||||||
assert(this is Success<Key, Value>, '');
|
final count = asSuccess.items.length;
|
||||||
final count = (this as Success<Key, Value>).items.length;
|
|
||||||
if (hasNextPage || hasError) return count + 1;
|
if (hasNextPage || hasError) return count + 1;
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
const factory PagedValue.loading() = Loading;
|
|
||||||
|
|
||||||
const factory PagedValue.error(StreamChatError error) = Error;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+205
-8
@@ -1,15 +1,40 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:stream_chat/stream_chat.dart' hide Success;
|
import 'package:stream_chat/stream_chat.dart' hide Success;
|
||||||
import 'package:stream_chat_flutter/src/paged_value_notifier.dart';
|
import 'package:stream_chat_flutter/src/paged_value_notifier.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/v4/channel_list_view/stream_channel_list_event_handler.dart'
|
||||||
|
as event_handler;
|
||||||
|
|
||||||
|
const defaultChannelPagedLimit = 10;
|
||||||
|
|
||||||
|
typedef ChannelListEventHandler = void Function(
|
||||||
|
Event event,
|
||||||
|
StreamChannelListController controller,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// A controller for the channel list view.
|
||||||
class StreamChannelListController extends PagedValueNotifier<int, Channel> {
|
class StreamChannelListController extends PagedValueNotifier<int, Channel> {
|
||||||
/// Creates a [StreamChannelListController].
|
/// Creates a [StreamChannelListController].
|
||||||
StreamChannelListController({
|
StreamChannelListController({
|
||||||
required this.client,
|
required this.client,
|
||||||
this.filter,
|
this.filter,
|
||||||
this.sort,
|
this.sort,
|
||||||
this.limit = 2,
|
this.limit = defaultChannelPagedLimit,
|
||||||
this.messageLimit,
|
this.messageLimit,
|
||||||
this.memberLimit,
|
this.memberLimit,
|
||||||
|
this.onChannelDeleted = event_handler.onChannelDeleted,
|
||||||
|
this.onChannelHidden = event_handler.onChannelHidden,
|
||||||
|
this.onChannelTruncated = event_handler.onChannelTruncated,
|
||||||
|
this.onChannelUpdated = event_handler.onChannelUpdated,
|
||||||
|
this.onChannelVisible = event_handler.onChannelVisible,
|
||||||
|
this.onConnectionRecovered = event_handler.onConnectionRecovered,
|
||||||
|
this.onMessageNew = event_handler.onMessageNew,
|
||||||
|
this.onNotificationAddedToChannel =
|
||||||
|
event_handler.onNotificationAddedToChannel,
|
||||||
|
this.onNotificationMessageNew = event_handler.onNotificationMessageNew,
|
||||||
|
this.onNotificationRemovedFromChannel =
|
||||||
|
event_handler.onNotificationRemovedFromChannel,
|
||||||
|
this.onUserPresenceChanged = event_handler.onUserPresenceChanged,
|
||||||
}) : super(const PagedValue.loading());
|
}) : super(const PagedValue.loading());
|
||||||
|
|
||||||
/// Creates a [StreamChannelListController] from the passed [value].
|
/// Creates a [StreamChannelListController] from the passed [value].
|
||||||
@@ -18,9 +43,22 @@ class StreamChannelListController extends PagedValueNotifier<int, Channel> {
|
|||||||
required this.client,
|
required this.client,
|
||||||
this.filter,
|
this.filter,
|
||||||
this.sort,
|
this.sort,
|
||||||
this.limit = 2,
|
this.limit = defaultChannelPagedLimit,
|
||||||
this.messageLimit,
|
this.messageLimit,
|
||||||
this.memberLimit,
|
this.memberLimit,
|
||||||
|
this.onChannelDeleted = event_handler.onChannelDeleted,
|
||||||
|
this.onChannelHidden = event_handler.onChannelHidden,
|
||||||
|
this.onChannelTruncated = event_handler.onChannelTruncated,
|
||||||
|
this.onChannelUpdated = event_handler.onChannelUpdated,
|
||||||
|
this.onChannelVisible = event_handler.onChannelVisible,
|
||||||
|
this.onConnectionRecovered = event_handler.onConnectionRecovered,
|
||||||
|
this.onMessageNew = event_handler.onMessageNew,
|
||||||
|
this.onNotificationAddedToChannel =
|
||||||
|
event_handler.onNotificationAddedToChannel,
|
||||||
|
this.onNotificationMessageNew = event_handler.onNotificationMessageNew,
|
||||||
|
this.onNotificationRemovedFromChannel =
|
||||||
|
event_handler.onNotificationRemovedFromChannel,
|
||||||
|
this.onUserPresenceChanged = event_handler.onUserPresenceChanged,
|
||||||
}) : super(value);
|
}) : super(value);
|
||||||
|
|
||||||
/// The client to use for the channel list.
|
/// The client to use for the channel list.
|
||||||
@@ -41,6 +79,82 @@ class StreamChannelListController extends PagedValueNotifier<int, Channel> {
|
|||||||
/// The limit to apply to the member list.
|
/// The limit to apply to the member list.
|
||||||
final int? memberLimit;
|
final int? memberLimit;
|
||||||
|
|
||||||
|
/// Callback function which gets called for the event
|
||||||
|
/// [EventType.channelDeleted].
|
||||||
|
///
|
||||||
|
/// By default, calls [event_handler.onChannelDeleted]
|
||||||
|
/// with the [Event] and the [StreamChannelListController].
|
||||||
|
final ChannelListEventHandler onChannelDeleted;
|
||||||
|
|
||||||
|
/// Callback function which gets called for the event
|
||||||
|
/// [EventType.channelHidden].
|
||||||
|
///
|
||||||
|
/// By default, calls [event_handler.onChannelHidden]
|
||||||
|
/// with the [Event] and the [StreamChannelListController].
|
||||||
|
final ChannelListEventHandler onChannelHidden;
|
||||||
|
|
||||||
|
/// Callback function which gets called for the event
|
||||||
|
/// [EventType.channelTruncated].
|
||||||
|
///
|
||||||
|
/// By default, calls [event_handler.onChannelTruncated]
|
||||||
|
/// with the [Event] and the [StreamChannelListController].
|
||||||
|
final ChannelListEventHandler onChannelTruncated;
|
||||||
|
|
||||||
|
/// Callback function which gets called for the event
|
||||||
|
/// [EventType.channelUpdated].
|
||||||
|
///
|
||||||
|
/// By default, calls [event_handler.onChannelUpdated]
|
||||||
|
/// with the [Event] and the [StreamChannelListController].
|
||||||
|
final ChannelListEventHandler onChannelUpdated;
|
||||||
|
|
||||||
|
/// Callback function which gets called for the event
|
||||||
|
/// [EventType.channelVisible].
|
||||||
|
///
|
||||||
|
/// By default, calls [event_handler.onChannelVisible]
|
||||||
|
/// with the [Event] and the [StreamChannelListController].
|
||||||
|
final ChannelListEventHandler onChannelVisible;
|
||||||
|
|
||||||
|
/// Callback function which gets called for the event
|
||||||
|
/// [EventType.connectionRecovered].
|
||||||
|
///
|
||||||
|
/// By default, calls [event_handler.onConnectionRecovered]
|
||||||
|
/// with the [Event] and the [StreamChannelListController].
|
||||||
|
final ChannelListEventHandler onConnectionRecovered;
|
||||||
|
|
||||||
|
/// Callback function which gets called for the event [EventType.messageNew].
|
||||||
|
///
|
||||||
|
/// By default, calls [event_handler.onMessageNew]
|
||||||
|
/// with the [Event] and the [StreamChannelListController].
|
||||||
|
final ChannelListEventHandler onMessageNew;
|
||||||
|
|
||||||
|
/// Callback function which gets called for the event
|
||||||
|
/// [EventType.notificationAddedToChannel].
|
||||||
|
///
|
||||||
|
/// By default, calls [event_handler.onNotificationAddedToChannel]
|
||||||
|
/// with the [Event] and the [StreamChannelListController].
|
||||||
|
final ChannelListEventHandler onNotificationAddedToChannel;
|
||||||
|
|
||||||
|
/// Callback function which gets called for the event
|
||||||
|
/// [EventType.notificationMessageNew].
|
||||||
|
///
|
||||||
|
/// By default, calls [event_handler.onNotificationMessageNew]
|
||||||
|
/// with the [Event] and the [StreamChannelListController].
|
||||||
|
final ChannelListEventHandler onNotificationMessageNew;
|
||||||
|
|
||||||
|
/// Callback function which gets called for the event
|
||||||
|
/// [EventType.notificationRemovedFromChannel].
|
||||||
|
///
|
||||||
|
/// By default, calls [event_handler.onNotificationRemovedFromChannel]
|
||||||
|
/// with the [Event] and the [StreamChannelListController].
|
||||||
|
final ChannelListEventHandler onNotificationRemovedFromChannel;
|
||||||
|
|
||||||
|
/// Callback function which gets called for the event
|
||||||
|
/// 'user.presence.changed' and [EventType.userUpdated].
|
||||||
|
///
|
||||||
|
/// By default, calls [event_handler.onUserPresenceChanged]
|
||||||
|
/// with the [Event] and the [StreamChannelListController].
|
||||||
|
final ChannelListEventHandler onUserPresenceChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> doInitialLoad() async {
|
Future<void> doInitialLoad() async {
|
||||||
final limit = this.limit * defaultInitialPagedLimitMultiplier;
|
final limit = this.limit * defaultInitialPagedLimitMultiplier;
|
||||||
@@ -58,15 +172,16 @@ class StreamChannelListController extends PagedValueNotifier<int, Channel> {
|
|||||||
nextPageKey: nextKey,
|
nextPageKey: nextKey,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
// start listening events
|
||||||
value = PagedValue.error(StreamChatError('error'));
|
_subscribeToChannelListEvents();
|
||||||
|
} on StreamChatError catch (error) {
|
||||||
|
value = PagedValue.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> loadMore(int nextPageKey) async {
|
Future<void> loadMore(int nextPageKey) async {
|
||||||
assert(value is Success<int, Channel>, '');
|
final previousValue = value.asSuccess;
|
||||||
final previousValue = value as Success<int, Channel>;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await for (final channels in client.queryChannels(
|
await for (final channels in client.queryChannels(
|
||||||
@@ -84,8 +199,90 @@ class StreamChannelListController extends PagedValueNotifier<int, Channel> {
|
|||||||
nextPageKey: nextKey,
|
nextPageKey: nextKey,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} on StreamChatError catch (error) {
|
||||||
value = previousValue.copyWith(error: StreamChatError('error'));
|
value = previousValue.copyWith(error: error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replaces the previously loaded channels with [channels] and updates
|
||||||
|
/// the nextPageKey.
|
||||||
|
set channels(List<Channel> channels) {
|
||||||
|
value = PagedValue(
|
||||||
|
items: channels,
|
||||||
|
nextPageKey: channels.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns/Creates a new Channel and starts watching it.
|
||||||
|
Future<Channel> getChannel({
|
||||||
|
required String id,
|
||||||
|
required String type,
|
||||||
|
}) async {
|
||||||
|
final channel = client.channel(type, id: id);
|
||||||
|
await channel.watch();
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription<Event>? _channelEventSubscription;
|
||||||
|
|
||||||
|
// Subscribes to the channel list events.
|
||||||
|
void _subscribeToChannelListEvents() {
|
||||||
|
if (_channelEventSubscription != null) {
|
||||||
|
_unsubscribeFromChannelListEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
_channelEventSubscription = client.on().listen((event) {
|
||||||
|
final eventType = event.type;
|
||||||
|
if (eventType == EventType.channelDeleted) {
|
||||||
|
onChannelDeleted(event, this);
|
||||||
|
} else if (eventType == EventType.channelHidden) {
|
||||||
|
onChannelHidden(event, this);
|
||||||
|
} else if (eventType == EventType.channelTruncated) {
|
||||||
|
onChannelTruncated(event, this);
|
||||||
|
} else if (eventType == EventType.channelUpdated) {
|
||||||
|
onChannelUpdated(event, this);
|
||||||
|
} else if (eventType == EventType.channelVisible) {
|
||||||
|
onChannelVisible(event, this);
|
||||||
|
} else if (eventType == EventType.connectionRecovered) {
|
||||||
|
onConnectionRecovered(event, this);
|
||||||
|
} else if (eventType == EventType.connectionChanged) {
|
||||||
|
if (event.online != null) onConnectionRecovered(event, this);
|
||||||
|
} else if (eventType == EventType.messageNew) {
|
||||||
|
onMessageNew(event, this);
|
||||||
|
} else if (eventType == EventType.notificationAddedToChannel) {
|
||||||
|
onNotificationAddedToChannel(event, this);
|
||||||
|
} else if (eventType == EventType.notificationMessageNew) {
|
||||||
|
onNotificationMessageNew(event, this);
|
||||||
|
} else if (eventType == EventType.notificationRemovedFromChannel) {
|
||||||
|
onNotificationRemovedFromChannel(event, this);
|
||||||
|
} else if (eventType == 'user.presence.changed' ||
|
||||||
|
eventType == EventType.userUpdated) {
|
||||||
|
onUserPresenceChanged(event, this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribes from all channel list events.
|
||||||
|
void _unsubscribeFromChannelListEvents() {
|
||||||
|
if (_channelEventSubscription != null) {
|
||||||
|
_channelEventSubscription!.cancel();
|
||||||
|
_channelEventSubscription = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pauses all subscriptions added to this composite.
|
||||||
|
void pauseEventsSubscription([Future<void>? resumeSignal]) {
|
||||||
|
_channelEventSubscription?.pause(resumeSignal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resumes all subscriptions added to this composite.
|
||||||
|
void resumeEventsSubscription() {
|
||||||
|
_channelEventSubscription?.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_unsubscribeFromChannelListEvents();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+311
@@ -0,0 +1,311 @@
|
|||||||
|
import 'package:stream_chat/stream_chat.dart' show Event;
|
||||||
|
import 'package:stream_chat_flutter/src/v4/channel_list_view/stream_channel_list_controller.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
/// Handles [EventType.channelDeleted] event.
|
||||||
|
///
|
||||||
|
/// This event is fired when a channel is deleted.
|
||||||
|
///
|
||||||
|
/// By default, this removes the channel from the list of channels.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// StreamChannelListController(
|
||||||
|
/// client: client,
|
||||||
|
/// onChannelDeleted: (event, controller) {
|
||||||
|
/// // Do something
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
void onChannelDeleted(
|
||||||
|
Event event,
|
||||||
|
StreamChannelListController controller,
|
||||||
|
) {
|
||||||
|
final channels = [...controller.currentItems];
|
||||||
|
|
||||||
|
final updatedChannels = channels
|
||||||
|
..removeWhere(
|
||||||
|
(it) => it.cid == (event.cid ?? event.channel?.cid),
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.channels = updatedChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles [EventType.channelHidden] event.
|
||||||
|
///
|
||||||
|
/// This event is fired when a channel is hidden.
|
||||||
|
///
|
||||||
|
/// By default, this removes the channel from the list of channels.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// StreamChannelListController(
|
||||||
|
/// client: client,
|
||||||
|
/// onChannelHidden: (event, controller) {
|
||||||
|
/// // Do something
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
void onChannelHidden(
|
||||||
|
Event event,
|
||||||
|
StreamChannelListController controller,
|
||||||
|
) {
|
||||||
|
onChannelDeleted(event, controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles [EventType.channelTruncated] event.
|
||||||
|
///
|
||||||
|
/// This event is fired when a channel is truncated.
|
||||||
|
///
|
||||||
|
/// By default, this refreshes the whole channel list.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// StreamChannelListController(
|
||||||
|
/// client: client,
|
||||||
|
/// onChannelTruncated: (event, controller) {
|
||||||
|
/// // Do something
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
void onChannelTruncated(
|
||||||
|
Event event,
|
||||||
|
StreamChannelListController controller,
|
||||||
|
) {
|
||||||
|
controller.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles [EventType.channelUpdated] event.
|
||||||
|
///
|
||||||
|
/// This event is fired when a channel is updated.
|
||||||
|
///
|
||||||
|
/// By default, this updates the channel received in the event.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// StreamChannelListController(
|
||||||
|
/// client: client,
|
||||||
|
/// onChannelUpdated: (event, controller) {
|
||||||
|
/// // Do something
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
void onChannelUpdated(
|
||||||
|
Event event,
|
||||||
|
StreamChannelListController controller,
|
||||||
|
) {
|
||||||
|
final eventChannel = event.channel;
|
||||||
|
if (eventChannel == null) return;
|
||||||
|
|
||||||
|
final channels = [...controller.currentItems];
|
||||||
|
final channelIndex = channels.indexWhere(
|
||||||
|
(it) => it.cid == (event.cid ?? eventChannel.cid),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (channelIndex >= 0) {
|
||||||
|
final channelState = ChannelState(channel: eventChannel);
|
||||||
|
channels[channelIndex].state?.updateChannelState(channelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.channels = channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles [EventType.channelVisible] event.
|
||||||
|
///
|
||||||
|
/// This event is fired when a channel is made visible.
|
||||||
|
///
|
||||||
|
/// By default, this adds the channel to the list of channels.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// StreamChannelListController(
|
||||||
|
/// client: client,
|
||||||
|
/// onChannelVisible: (event, controller) {
|
||||||
|
/// // Do something
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
void onChannelVisible(
|
||||||
|
Event event,
|
||||||
|
StreamChannelListController controller,
|
||||||
|
) async {
|
||||||
|
final channelId = event.channelId;
|
||||||
|
final channelType = event.channelType;
|
||||||
|
|
||||||
|
if (channelId == null || channelType == null) return;
|
||||||
|
|
||||||
|
final channel = await controller.getChannel(
|
||||||
|
id: channelId,
|
||||||
|
type: channelType,
|
||||||
|
);
|
||||||
|
|
||||||
|
final currentChannels = [...controller.currentItems];
|
||||||
|
|
||||||
|
final updatedChannels = [
|
||||||
|
channel,
|
||||||
|
...currentChannels..removeWhere((it) => it.cid == channel.cid),
|
||||||
|
];
|
||||||
|
|
||||||
|
controller.channels = updatedChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles [EventType.connectionRecovered] event.
|
||||||
|
///
|
||||||
|
/// This event is fired when the client web-socket connection recovers.
|
||||||
|
///
|
||||||
|
/// By default, this refreshes the whole channel list.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// StreamChannelListController(
|
||||||
|
/// client: client,
|
||||||
|
/// onConnectionRecovered: (event, controller) {
|
||||||
|
/// // Do something
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
void onConnectionRecovered(
|
||||||
|
Event event,
|
||||||
|
StreamChannelListController controller,
|
||||||
|
) {
|
||||||
|
controller.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles [EventType.messageNew] event.
|
||||||
|
///
|
||||||
|
/// This event is fired when a new message is created in one of the channels
|
||||||
|
/// we are currently watching.
|
||||||
|
///
|
||||||
|
/// By default, this moves the channel to the top of the list.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// StreamChannelListController(
|
||||||
|
/// client: client,
|
||||||
|
/// onMessageNew: (event, controller) {
|
||||||
|
/// // Do something
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
void onMessageNew(
|
||||||
|
Event event,
|
||||||
|
StreamChannelListController controller,
|
||||||
|
) {
|
||||||
|
final channelCid = event.cid;
|
||||||
|
if (channelCid == null) return;
|
||||||
|
|
||||||
|
final channels = [...controller.currentItems];
|
||||||
|
|
||||||
|
final channelIndex = channels.indexWhere((it) => it.cid == channelCid);
|
||||||
|
if (channelIndex <= 0) return;
|
||||||
|
|
||||||
|
final channel = channels.removeAt(channelIndex);
|
||||||
|
channels.insert(0, channel);
|
||||||
|
|
||||||
|
controller.channels = [...channels];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles [EventType.notificationAddedToChannel] event.
|
||||||
|
///
|
||||||
|
/// This event is fired when a channel is added which we are not watching.
|
||||||
|
///
|
||||||
|
/// By default, this adds the channel and moves it to the top of list.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// StreamChannelListController(
|
||||||
|
/// client: client,
|
||||||
|
/// onNotificationAddedToChannel: (event, controller) {
|
||||||
|
/// // Do something
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
void onNotificationAddedToChannel(
|
||||||
|
Event event,
|
||||||
|
StreamChannelListController controller,
|
||||||
|
) {
|
||||||
|
onChannelVisible(event, controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles [EventType.notificationMessageNew] event.
|
||||||
|
///
|
||||||
|
/// This event is fired when a new message is created in a channel which we are
|
||||||
|
/// not currently watching.
|
||||||
|
///
|
||||||
|
/// By default, this adds the channel and moves it to the top of list.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// StreamChannelListController(
|
||||||
|
/// client: client,
|
||||||
|
/// onNotificationMessageNew: (event, controller) {
|
||||||
|
/// // Do something
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
void onNotificationMessageNew(
|
||||||
|
Event event,
|
||||||
|
StreamChannelListController controller,
|
||||||
|
) {
|
||||||
|
onChannelVisible(event, controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles [EventType.notificationRemovedFromChannel] event.
|
||||||
|
///
|
||||||
|
/// This event is fired when a user is removed from a channel which we are
|
||||||
|
/// not currently watching.
|
||||||
|
///
|
||||||
|
/// By default, this removes the event channel from the list.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// StreamChannelListController(
|
||||||
|
/// client: client,
|
||||||
|
/// onNotificationRemovedFromChannel: (event, controller) {
|
||||||
|
/// // Do something
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
void onNotificationRemovedFromChannel(
|
||||||
|
Event event,
|
||||||
|
StreamChannelListController controller,
|
||||||
|
) {
|
||||||
|
final channels = [...controller.currentItems];
|
||||||
|
final updatedChannels = channels.where((it) => it.cid != event.channel?.cid);
|
||||||
|
final listChanged = channels.length != updatedChannels.length;
|
||||||
|
|
||||||
|
if (!listChanged) return;
|
||||||
|
|
||||||
|
controller.channels = [...updatedChannels];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles 'user.presence.changed' and [EventType.userUpdated] event.
|
||||||
|
///
|
||||||
|
/// This event is fired when a user's presence changes or gets updated.
|
||||||
|
///
|
||||||
|
/// By default, this updates the channel member with the event user.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// StreamChannelListController(
|
||||||
|
/// client: client,
|
||||||
|
/// onUserPresenceChanged: (event, controller) {
|
||||||
|
/// // Do something
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
void onUserPresenceChanged(
|
||||||
|
Event event,
|
||||||
|
StreamChannelListController controller,
|
||||||
|
) {
|
||||||
|
final user = event.user;
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
final channels = [...controller.currentItems];
|
||||||
|
|
||||||
|
final updatedChannels = channels.map((channel) {
|
||||||
|
final members = [...channel.state!.members];
|
||||||
|
final memberIndex = members.indexWhere(
|
||||||
|
(it) => user.id == (it.userId ?? it.user?.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (memberIndex < 0) return channel;
|
||||||
|
|
||||||
|
members[memberIndex] = members[memberIndex].copyWith(user: user);
|
||||||
|
final updatedState = ChannelState(members: [...members]);
|
||||||
|
channel.state!.updateChannelState(updatedState);
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
});
|
||||||
|
|
||||||
|
controller.channels = [...updatedChannels];
|
||||||
|
}
|
||||||
+58
-33
@@ -10,32 +10,44 @@ import 'package:stream_chat_flutter/src/v4/channel_list_view/stream_channel_list
|
|||||||
import 'package:stream_chat_flutter/src/v4/channel_list_view/stream_channel_list_tile.dart';
|
import 'package:stream_chat_flutter/src/v4/channel_list_view/stream_channel_list_tile.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
|
||||||
/// Signature for a function that creates a widget for a given index, e.g., in a
|
Widget defaultSeparatorBuilder(context, index) =>
|
||||||
/// list.
|
const StreamChannelListSeparator();
|
||||||
///
|
|
||||||
/// Used by [GridView.builder] and other APIs that use lazily-generated widgets.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [WidgetBuilder], which is similar but only takes a [BuildContext].
|
|
||||||
/// * [TransitionBuilder], which is similar but also takes a child.
|
|
||||||
/// * [NullableIndexedWidgetBuilder], which is similar but may return null.
|
|
||||||
typedef StreamChannelListViewItemBuilder = Widget Function(
|
typedef StreamChannelListViewItemBuilder = Widget Function(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Channel channel,
|
Channel channel,
|
||||||
);
|
);
|
||||||
|
|
||||||
typedef StreamChannelTapCallback = void Function(Channel);
|
/// A [ListView] that shows a list of [Channel]s,
|
||||||
|
/// it uses [StreamChannelListTile] as a default item.
|
||||||
Widget _defaultSeparatorBuilder(context, index) =>
|
///
|
||||||
const _ChannelListSeparator();
|
/// This is the new version of [ChannelListView] that uses
|
||||||
|
/// [StreamChannelListController].
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// StreamChannelListView(
|
||||||
|
/// controller: controller,
|
||||||
|
/// onChannelTap: (channel) {
|
||||||
|
/// // Handle channel tap event
|
||||||
|
/// },
|
||||||
|
/// onChannelLongPress: (channel) {
|
||||||
|
/// // Handle channel long press event
|
||||||
|
/// },
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [StreamChannelListTile]
|
||||||
|
/// * [StreamChannelListController]
|
||||||
class StreamChannelListView extends StatefulWidget {
|
class StreamChannelListView extends StatefulWidget {
|
||||||
|
/// Creates a new instance of [StreamChannelListView].
|
||||||
const StreamChannelListView({
|
const StreamChannelListView({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
this.itemBuilder,
|
this.itemBuilder,
|
||||||
this.separatorBuilder = _defaultSeparatorBuilder,
|
this.separatorBuilder = defaultSeparatorBuilder,
|
||||||
this.onChannelTap,
|
this.onChannelTap,
|
||||||
this.onChannelLongPress,
|
this.onChannelLongPress,
|
||||||
this.padding,
|
this.padding,
|
||||||
@@ -51,21 +63,23 @@ class StreamChannelListView extends StatefulWidget {
|
|||||||
this.restorationId,
|
this.restorationId,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// The [StreamChannelListController] used to control the list of channels.
|
||||||
final StreamChannelListController controller;
|
final StreamChannelListController controller;
|
||||||
|
|
||||||
|
/// A builder that is called to build items in the [ListView].
|
||||||
|
///
|
||||||
|
/// The `index` parameter is the index of the list tile in the list and the
|
||||||
|
/// `channel` parameter is the [Channel] at that position.
|
||||||
final StreamChannelListViewItemBuilder? itemBuilder;
|
final StreamChannelListViewItemBuilder? itemBuilder;
|
||||||
|
|
||||||
|
/// A builder that is called to build the list separator.
|
||||||
final IndexedWidgetBuilder separatorBuilder;
|
final IndexedWidgetBuilder separatorBuilder;
|
||||||
|
|
||||||
/// Called when the user taps this list tile.
|
/// Called when the user taps this list tile.
|
||||||
///
|
final void Function(Channel)? onChannelTap;
|
||||||
/// Inoperative if [enabled] is false.
|
|
||||||
final StreamChannelTapCallback? onChannelTap;
|
|
||||||
|
|
||||||
/// Called when the user long-presses on this list tile.
|
/// Called when the user long-presses on this list tile.
|
||||||
///
|
final void Function(Channel)? onChannelLongPress;
|
||||||
/// Inoperative if [enabled] is false.
|
|
||||||
final StreamChannelTapCallback? onChannelLongPress;
|
|
||||||
|
|
||||||
/// The amount of space by which to inset the children.
|
/// The amount of space by which to inset the children.
|
||||||
final EdgeInsetsGeometry? padding;
|
final EdgeInsetsGeometry? padding;
|
||||||
@@ -254,11 +268,11 @@ class _StreamChannelListViewState extends State<StreamChannelListView> {
|
|||||||
final newPageRequestTriggerIndex = value.itemCount - 3;
|
final newPageRequestTriggerIndex = value.itemCount - 3;
|
||||||
final isBuildingTriggerIndexItem =
|
final isBuildingTriggerIndexItem =
|
||||||
index == newPageRequestTriggerIndex;
|
index == newPageRequestTriggerIndex;
|
||||||
if (value.hasNextPage && isBuildingTriggerIndexItem) {
|
if (nextPageKey != null && isBuildingTriggerIndexItem) {
|
||||||
// Schedules the request for the end of this frame.
|
// Schedules the request for the end of this frame.
|
||||||
WidgetsBinding.instance?.addPostFrameCallback((_) async {
|
WidgetsBinding.instance?.addPostFrameCallback((_) async {
|
||||||
if (!value.hasError) {
|
if (!value.hasError) {
|
||||||
await _controller.loadMore(nextPageKey!);
|
await _controller.loadMore(nextPageKey);
|
||||||
}
|
}
|
||||||
_hasRequestedNextPage = false;
|
_hasRequestedNextPage = false;
|
||||||
});
|
});
|
||||||
@@ -267,15 +281,15 @@ class _StreamChannelListViewState extends State<StreamChannelListView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (index == channels.length) {
|
if (index == channels.length) {
|
||||||
if (value.hasError) {
|
if (error != null) {
|
||||||
return _ChannelListLoadMoreError(
|
return ChannelListLoadMoreError(
|
||||||
onTap: _controller.retry,
|
onTap: _controller.retry,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return const Center(
|
return const Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(16),
|
padding: EdgeInsets.all(16),
|
||||||
child: _ChannelListLoadMoreIndicator(),
|
child: ChannelListLoadMoreIndicator(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -309,8 +323,12 @@ class _StreamChannelListViewState extends State<StreamChannelListView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelListLoadMoreIndicator extends StatelessWidget {
|
/// A [StreamChannelListTile] that can be used in a [ListView] to show a
|
||||||
const _ChannelListLoadMoreIndicator({Key? key}) : super(key: key);
|
/// loading tile while waiting for the [StreamChannelListController] to load
|
||||||
|
/// more channels.
|
||||||
|
class ChannelListLoadMoreIndicator extends StatelessWidget {
|
||||||
|
/// Creates a new instance of [ChannelListLoadMoreIndicator].
|
||||||
|
const ChannelListLoadMoreIndicator({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => const SizedBox(
|
Widget build(BuildContext context) => const SizedBox(
|
||||||
@@ -320,12 +338,16 @@ class _ChannelListLoadMoreIndicator extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelListLoadMoreError extends StatelessWidget {
|
/// A [StreamChannelListTile] that is used to display the error indicator when
|
||||||
const _ChannelListLoadMoreError({
|
/// loading more channels fails.
|
||||||
|
class ChannelListLoadMoreError extends StatelessWidget {
|
||||||
|
/// Creates a new instance of [ChannelListLoadMoreError].
|
||||||
|
const ChannelListLoadMoreError({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// The callback to invoke when the user taps on the error indicator.
|
||||||
final GestureTapCallback onTap;
|
final GestureTapCallback onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -355,8 +377,11 @@ class _ChannelListLoadMoreError extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelListSeparator extends StatelessWidget {
|
/// A widget that is used to display a separator between
|
||||||
const _ChannelListSeparator({Key? key}) : super(key: key);
|
/// [StreamChannelListTile] items.
|
||||||
|
class StreamChannelListSeparator extends StatelessWidget {
|
||||||
|
/// Creates a new instance of [StreamChannelListSeparator].
|
||||||
|
const StreamChannelListSeparator({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
Reference in New Issue
Block a user