chore: move controllers from ui to core
Signed-off-by: xsahil03x <[email protected]>
This commit is contained in:
@@ -1,307 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// A value listenable builder related to a [Message].
|
||||
///
|
||||
/// Pass in a [MessageInputController] as the `valueListenable`.
|
||||
typedef MessageValueListenableBuilder = ValueListenableBuilder<Message>;
|
||||
|
||||
/// Controller for storing and mutating a [Message] value.
|
||||
class MessageInputController extends ValueNotifier<Message> {
|
||||
/// Creates a controller for an editable text field.
|
||||
///
|
||||
/// This constructor treats a null [message] argument as if it were the empty
|
||||
/// message.
|
||||
factory MessageInputController({
|
||||
Message? message,
|
||||
Map<RegExp, TextStyleBuilder>? textPatternStyle,
|
||||
}) =>
|
||||
MessageInputController._(
|
||||
initialMessage: message ?? Message(),
|
||||
textPatternStyle: textPatternStyle,
|
||||
);
|
||||
|
||||
/// Creates a controller for an editable text field from an initial [text].
|
||||
factory MessageInputController.fromText(
|
||||
String? text, {
|
||||
Map<RegExp, TextStyleBuilder>? textPatternStyle,
|
||||
}) =>
|
||||
MessageInputController._(
|
||||
initialMessage: Message(text: text),
|
||||
textPatternStyle: textPatternStyle,
|
||||
);
|
||||
|
||||
/// Creates a controller for an editable text field from initial
|
||||
/// [attachments].
|
||||
factory MessageInputController.fromAttachments(
|
||||
List<Attachment> attachments, {
|
||||
Map<RegExp, TextStyleBuilder>? textPatternStyle,
|
||||
}) =>
|
||||
MessageInputController._(
|
||||
initialMessage: Message(attachments: attachments),
|
||||
textPatternStyle: textPatternStyle,
|
||||
);
|
||||
|
||||
MessageInputController._({
|
||||
required Message initialMessage,
|
||||
Map<RegExp, TextStyleBuilder>? textPatternStyle,
|
||||
}) : _textEditingController = MessageTextFieldController.fromValue(
|
||||
initialMessage.text == null
|
||||
? const TextEditingValue()
|
||||
: TextEditingValue(
|
||||
text: initialMessage.text!,
|
||||
composing: TextRange.collapsed(initialMessage.text!.length),
|
||||
),
|
||||
textPatternStyle: textPatternStyle,
|
||||
),
|
||||
_initialMessage = initialMessage,
|
||||
super(initialMessage) {
|
||||
addListener(_textEditingSyncer);
|
||||
}
|
||||
|
||||
void _textEditingSyncer() {
|
||||
final cleanText = value.command == null
|
||||
? value.text
|
||||
: value.text?.replaceFirst('/${value.command} ', '');
|
||||
|
||||
if (cleanText != _textEditingController.text) {
|
||||
final previousOffset = _textEditingController.value.selection.start;
|
||||
final previousText = _textEditingController.text;
|
||||
final diff = (cleanText?.length ?? 0) - previousText.length;
|
||||
_textEditingController
|
||||
..text = cleanText ?? ''
|
||||
..selection = TextSelection.collapsed(
|
||||
offset: previousOffset + diff,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current message associated with this controller.
|
||||
Message get message => value;
|
||||
|
||||
/// Returns the controller of the text field linked to this controller.
|
||||
MessageTextFieldController get textEditingController =>
|
||||
_textEditingController;
|
||||
final MessageTextFieldController _textEditingController;
|
||||
|
||||
/// Returns the text of the message.
|
||||
String get text => _textEditingController.text;
|
||||
|
||||
Message _initialMessage;
|
||||
|
||||
/// Sets the message.
|
||||
set message(Message message) {
|
||||
value = message;
|
||||
}
|
||||
|
||||
/// Sets the message that's being quoted.
|
||||
set quotedMessage(Message message) {
|
||||
value = value.copyWith(
|
||||
quotedMessage: message,
|
||||
quotedMessageId: message.id,
|
||||
);
|
||||
}
|
||||
|
||||
/// Clears the quoted message.
|
||||
void clearQuotedMessage() {
|
||||
value = value.copyWith(
|
||||
quotedMessageId: null,
|
||||
quotedMessage: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// Sets a command for the message.
|
||||
set command(Command command) {
|
||||
value = value.copyWith(
|
||||
command: command.name,
|
||||
text: '/${command.name} ',
|
||||
);
|
||||
}
|
||||
|
||||
/// Sets the text of the message.
|
||||
set text(String newText) {
|
||||
var newTextWithCommand = newText;
|
||||
if (value.command != null) {
|
||||
if (!newText.startsWith('/${value.command}')) {
|
||||
newTextWithCommand = '/${value.command} $newText';
|
||||
}
|
||||
}
|
||||
value = value.copyWith(text: newTextWithCommand);
|
||||
}
|
||||
|
||||
/// Returns the baseOffset of the text field.
|
||||
int get baseOffset => textEditingController.selection.baseOffset;
|
||||
|
||||
/// Returns the start of the selection of the text field.
|
||||
int get selectionStart => textEditingController.selection.start;
|
||||
|
||||
/// Sets the [showInChannel] flag of the message.
|
||||
set showInChannel(bool newValue) {
|
||||
value = value.copyWith(showInChannel: newValue);
|
||||
}
|
||||
|
||||
/// Returns true if the message is in a thread and
|
||||
/// should be shown in the main channel as well.
|
||||
bool get showInChannel => value.showInChannel ?? false;
|
||||
|
||||
/// Returns the attachments of the message.
|
||||
List<Attachment> get attachments => value.attachments;
|
||||
|
||||
/// Sets the list of [attachments] for the message.
|
||||
set attachments(List<Attachment> attachments) {
|
||||
value = value.copyWith(attachments: attachments);
|
||||
}
|
||||
|
||||
/// Adds a new attachment to the message.
|
||||
void addAttachment(Attachment attachment) {
|
||||
attachments = [...attachments, attachment];
|
||||
}
|
||||
|
||||
/// Adds a new attachment at the specified [index].
|
||||
void addAttachmentAt(int index, Attachment attachment) {
|
||||
attachments = [...attachments]..insert(index, attachment);
|
||||
}
|
||||
|
||||
/// Removes the specified [attachment] from the message.
|
||||
void removeAttachment(Attachment attachment) {
|
||||
attachments = [...attachments]..remove(attachment);
|
||||
}
|
||||
|
||||
/// Remove the attachment with the given [attachmentId].
|
||||
void removeAttachmentById(String attachmentId) {
|
||||
attachments = [...attachments]..removeWhere((it) => it.id == attachmentId);
|
||||
}
|
||||
|
||||
/// Removes the attachment at the given [index].
|
||||
void removeAttachmentAt(int index) {
|
||||
attachments = [...attachments]..removeAt(index);
|
||||
}
|
||||
|
||||
/// Clears the message attachments.
|
||||
void clearAttachments() {
|
||||
attachments = [];
|
||||
}
|
||||
|
||||
// Only used to store the value locally in order to remove it if we call
|
||||
// [clearOGAttachment] or [setOGAttachment] again.
|
||||
Attachment? _ogAttachment;
|
||||
|
||||
/// Returns the og attachment of the message if set
|
||||
Attachment? get ogAttachment =>
|
||||
attachments.firstWhereOrNull((it) => it.id == _ogAttachment?.id);
|
||||
|
||||
/// Sets the og attachment in the message.
|
||||
void setOGAttachment(Attachment attachment) {
|
||||
attachments = [...attachments]
|
||||
..remove(_ogAttachment)
|
||||
..insert(0, attachment);
|
||||
_ogAttachment = attachment;
|
||||
}
|
||||
|
||||
/// Removes the og attachment.
|
||||
void clearOGAttachment() {
|
||||
if (_ogAttachment != null) {
|
||||
removeAttachment(_ogAttachment!);
|
||||
}
|
||||
_ogAttachment = null;
|
||||
}
|
||||
|
||||
/// Returns the list of mentioned users in the message.
|
||||
List<User> get mentionedUsers => value.mentionedUsers;
|
||||
|
||||
/// Sets the mentioned users.
|
||||
set mentionedUsers(List<User> users) {
|
||||
value = value.copyWith(mentionedUsers: users);
|
||||
}
|
||||
|
||||
/// Adds a user to the list of mentioned users.
|
||||
void addMentionedUser(User user) {
|
||||
mentionedUsers = [...mentionedUsers, user];
|
||||
}
|
||||
|
||||
/// Removes the specified [user] from the mentioned users list.
|
||||
void removeMentionedUser(User user) {
|
||||
mentionedUsers = [...mentionedUsers]..remove(user);
|
||||
}
|
||||
|
||||
/// Removes the mentioned user with the given [userId].
|
||||
void removeMentionedUserById(String userId) {
|
||||
mentionedUsers = [...mentionedUsers]..removeWhere((it) => it.id == userId);
|
||||
}
|
||||
|
||||
/// Removes all mentioned users from the message.
|
||||
void clearMentionedUsers() {
|
||||
mentionedUsers = [];
|
||||
}
|
||||
|
||||
/// Sets the [message], or [value], to empty.
|
||||
///
|
||||
/// After calling this function, [text], [attachments] and [mentionedUsers]
|
||||
/// will all be empty.
|
||||
///
|
||||
/// Calling this will notify all the listeners of this
|
||||
/// [MessageInputController] that they need to update
|
||||
/// (calls [notifyListeners]). For this reason,
|
||||
/// this method should only be called between frames, e.g. in response to user
|
||||
/// actions, not during the build, layout, or paint phases.
|
||||
void clear() {
|
||||
value = Message();
|
||||
_textEditingController.clear();
|
||||
}
|
||||
|
||||
/// Sets the [value] to the initial [Message] value.
|
||||
void reset({bool resetId = true}) {
|
||||
if (resetId) {
|
||||
final newId = const Uuid().v4();
|
||||
_initialMessage = _initialMessage.copyWith(id: newId);
|
||||
}
|
||||
value = _initialMessage;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
removeListener(_textEditingSyncer);
|
||||
_textEditingController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// A [RestorableProperty] that knows how to store and restore a
|
||||
/// [MessageInputController].
|
||||
///
|
||||
/// The [MessageInputController] is accessible via the [value] getter. During
|
||||
/// state restoration, the property will restore [MessageInputController.value]
|
||||
/// to the value it had when the restoration data it is getting restored from
|
||||
/// was collected.
|
||||
class RestorableMessageInputController
|
||||
extends RestorableChangeNotifier<MessageInputController> {
|
||||
/// Creates a [RestorableMessageInputController].
|
||||
///
|
||||
/// This constructor creates a default [Message] when no `message` argument
|
||||
/// is supplied.
|
||||
RestorableMessageInputController({Message? message})
|
||||
: _initialValue = message ?? Message();
|
||||
|
||||
/// Creates a [RestorableMessageInputController] from an initial
|
||||
/// [text] value.
|
||||
factory RestorableMessageInputController.fromText(String? text) =>
|
||||
RestorableMessageInputController(message: Message(text: text));
|
||||
|
||||
final Message _initialValue;
|
||||
|
||||
@override
|
||||
MessageInputController createDefaultValue() =>
|
||||
MessageInputController(message: _initialValue);
|
||||
|
||||
@override
|
||||
MessageInputController fromPrimitives(Object? data) {
|
||||
final message = Message.fromJson(json.decode(data! as String));
|
||||
return MessageInputController(message: message);
|
||||
}
|
||||
|
||||
@override
|
||||
String toPrimitives() => json.encode(value.value);
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/message_input/tld.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// A function that takes a [BuildContext] and returns a [TextStyle].
|
||||
typedef TextStyleBuilder = TextStyle? Function(
|
||||
BuildContext context,
|
||||
String text,
|
||||
);
|
||||
|
||||
/// Controller for the [StreamTextField] widget.
|
||||
class MessageTextFieldController extends TextEditingController {
|
||||
/// Returns a new MessageTextFieldController
|
||||
MessageTextFieldController({
|
||||
String? text,
|
||||
this.textPatternStyle,
|
||||
}) : super(text: text);
|
||||
|
||||
/// Returns a new MessageTextFieldController with the given text [value].
|
||||
MessageTextFieldController.fromValue(
|
||||
TextEditingValue? value, {
|
||||
this.textPatternStyle,
|
||||
}) : super.fromValue(value);
|
||||
|
||||
/// A map of style to apply to the text matching the RegExp patterns.
|
||||
final Map<RegExp, TextStyleBuilder>? textPatternStyle;
|
||||
|
||||
/// Builds a [TextSpan] from the current text,
|
||||
/// highlighting the matches for [textPatternStyle].
|
||||
@override
|
||||
TextSpan buildTextSpan({
|
||||
required BuildContext context,
|
||||
TextStyle? style,
|
||||
required bool withComposing,
|
||||
}) {
|
||||
final pattern = textPatternStyle ??
|
||||
{
|
||||
RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+'):
|
||||
(context, text) {
|
||||
if (!text.split('.').last.isValidTLD()) return null;
|
||||
return TextStyle(
|
||||
color: MessageInputTheme.of(context).linkHighlightColor,
|
||||
);
|
||||
},
|
||||
};
|
||||
if (pattern.isEmpty) {
|
||||
return super.buildTextSpan(
|
||||
context: context,
|
||||
style: style,
|
||||
withComposing: withComposing,
|
||||
);
|
||||
}
|
||||
|
||||
return TextSpan(text: text, style: style).splitMapJoin(
|
||||
RegExp(pattern.keys.map((it) => it.pattern).join('|')),
|
||||
onMatch: (match) {
|
||||
final text = match[0]!;
|
||||
final key = pattern.keys.firstWhere((it) => it.hasMatch(text));
|
||||
return TextSpan(
|
||||
text: text,
|
||||
style: pattern[key]?.call(
|
||||
context,
|
||||
text,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension _TextSpanX on TextSpan {
|
||||
TextSpan splitMapJoin(
|
||||
Pattern pattern, {
|
||||
TextSpan Function(Match)? onMatch,
|
||||
TextSpan Function(TextSpan)? onNonMatch,
|
||||
}) {
|
||||
final children = <TextSpan>[];
|
||||
|
||||
toPlainText().splitMapJoin(
|
||||
pattern,
|
||||
onMatch: (match) {
|
||||
final span = TextSpan(text: match.group(0), style: style);
|
||||
final updated = onMatch?.call(match);
|
||||
children.add(updated ?? span);
|
||||
return span.toPlainText();
|
||||
},
|
||||
onNonMatch: (text) {
|
||||
final span = TextSpan(text: text, style: style);
|
||||
final updatedSpan = onNonMatch?.call(span);
|
||||
children.add(updatedSpan ?? span);
|
||||
return span.toPlainText();
|
||||
},
|
||||
);
|
||||
|
||||
return TextSpan(style: style, children: children);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A [SafeArea] with an enabled toggle
|
||||
class SimpleSafeArea extends StatefulWidget {
|
||||
class SimpleSafeArea extends StatelessWidget {
|
||||
/// Constructor for [SimpleSafeArea]
|
||||
const SimpleSafeArea({
|
||||
Key? key,
|
||||
@@ -15,17 +15,12 @@ class SimpleSafeArea extends StatefulWidget {
|
||||
/// Child widget to wrap
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
_SimpleSafeAreaState createState() => _SimpleSafeAreaState();
|
||||
}
|
||||
|
||||
class _SimpleSafeAreaState extends State<SimpleSafeArea> {
|
||||
@override
|
||||
Widget build(BuildContext context) => SafeArea(
|
||||
left: widget.enabled,
|
||||
top: widget.enabled,
|
||||
right: widget.enabled,
|
||||
bottom: widget.enabled,
|
||||
child: widget.child,
|
||||
left: enabled,
|
||||
top: enabled,
|
||||
right: enabled,
|
||||
bottom: enabled,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:stream_chat/stream_chat.dart' show StreamChatError;
|
||||
|
||||
part 'paged_value_notifier.freezed.dart';
|
||||
|
||||
/// Default initial page size multiplier.
|
||||
const defaultInitialPagedLimitMultiplier = 3;
|
||||
|
||||
/// Value listenable for paged data.
|
||||
typedef PagedValueListenableBuilder<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>
|
||||
extends ValueNotifier<PagedValue<Key, Value>> {
|
||||
/// Creates a [PagedValueNotifier]
|
||||
PagedValueNotifier(this._initialValue) : super(_initialValue);
|
||||
|
||||
/// Stores initialValue in case we need to call [refresh].
|
||||
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.
|
||||
///
|
||||
/// Unlike [refresh], this does not resets the whole [value],
|
||||
/// it only retries the last failed load request.
|
||||
Future<void> retry() {
|
||||
final lastValue = value.asSuccess;
|
||||
assert(lastValue.hasError, '');
|
||||
|
||||
final nextPageKey = lastValue.nextPageKey;
|
||||
// resetting the error
|
||||
value = lastValue.copyWith(error: null);
|
||||
// ignore: null_check_on_nullable_type_parameter
|
||||
return loadMore(nextPageKey!);
|
||||
}
|
||||
|
||||
/// Refresh the data presented by this [PagedValueNotifier].
|
||||
///
|
||||
/// Resets the [value] to the initial value in case [resetValue] is true.
|
||||
///
|
||||
/// Note: This API is intended for UI-driven refresh signals,
|
||||
/// such as swipe-to-refresh.
|
||||
Future<void> refresh({bool resetValue = true}) {
|
||||
if (resetValue) value = _initialValue;
|
||||
return doInitialLoad();
|
||||
}
|
||||
|
||||
/// Load initial data from the server.
|
||||
Future<void> doInitialLoad();
|
||||
|
||||
/// Load more data from the server using [nextPageKey].
|
||||
Future<void> loadMore(Key nextPageKey);
|
||||
}
|
||||
|
||||
/// Paged value that can be used with [PagedValueNotifier].
|
||||
@freezed
|
||||
abstract class PagedValue<Key, Value> with _$PagedValue<Key, Value> {
|
||||
/// Represents the success state of the [PagedValue]
|
||||
// @Assert(
|
||||
// 'nextPageKey != null',
|
||||
// 'Cannot set an error if all the pages are already fetched',
|
||||
// )
|
||||
const factory PagedValue({
|
||||
/// List with all items loaded so far.
|
||||
required List<Value> items,
|
||||
|
||||
/// The key for the next page to be fetched.
|
||||
Key? nextPageKey,
|
||||
|
||||
/// The current error, if any.
|
||||
StreamChatError? error,
|
||||
}) = Success<Key, Value>;
|
||||
|
||||
const PagedValue._();
|
||||
|
||||
/// Represents the loading state of the [PagedValue].
|
||||
const factory PagedValue.loading() = Loading;
|
||||
|
||||
/// 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>;
|
||||
}
|
||||
|
||||
/// Returns `true` if the [PagedValue] is [Success]
|
||||
/// and has more items to load.
|
||||
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 {
|
||||
final count = asSuccess.items.length;
|
||||
if (hasNextPage || hasError) return count + 1;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -1,590 +0,0 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
|
||||
|
||||
part of 'paged_value_notifier.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
/// @nodoc
|
||||
class _$PagedValueTearOff {
|
||||
const _$PagedValueTearOff();
|
||||
|
||||
Success<Key, Value> call<Key, Value>(
|
||||
{required List<Value> items, Key? nextPageKey, StreamChatError? error}) {
|
||||
return Success<Key, Value>(
|
||||
items: items,
|
||||
nextPageKey: nextPageKey,
|
||||
error: error,
|
||||
);
|
||||
}
|
||||
|
||||
Loading<Key, Value> loading<Key, Value>() {
|
||||
return Loading<Key, Value>();
|
||||
}
|
||||
|
||||
Error<Key, Value> error<Key, Value>(StreamChatError error) {
|
||||
return Error<Key, Value>(
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
const $PagedValue = _$PagedValueTearOff();
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PagedValue<Key, Value> {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function(
|
||||
List<Value> items, Key? nextPageKey, StreamChatError? error)
|
||||
$default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(StreamChatError error) error,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult Function(
|
||||
List<Value> items, Key? nextPageKey, StreamChatError? error)?
|
||||
$default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(StreamChatError error)? error,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function(
|
||||
List<Value> items, Key? nextPageKey, StreamChatError? error)?
|
||||
$default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(StreamChatError error)? error,
|
||||
required TResult orElse(),
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(Success<Key, Value> value) $default, {
|
||||
required TResult Function(Loading<Key, Value> value) loading,
|
||||
required TResult Function(Error<Key, Value> value) error,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult Function(Success<Key, Value> value)? $default, {
|
||||
TResult Function(Loading<Key, Value> value)? loading,
|
||||
TResult Function(Error<Key, Value> value)? error,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(Success<Key, Value> value)? $default, {
|
||||
TResult Function(Loading<Key, Value> value)? loading,
|
||||
TResult Function(Error<Key, Value> value)? error,
|
||||
required TResult orElse(),
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $PagedValueCopyWith<Key, Value, $Res> {
|
||||
factory $PagedValueCopyWith(PagedValue<Key, Value> value,
|
||||
$Res Function(PagedValue<Key, Value>) then) =
|
||||
_$PagedValueCopyWithImpl<Key, Value, $Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$PagedValueCopyWithImpl<Key, Value, $Res>
|
||||
implements $PagedValueCopyWith<Key, Value, $Res> {
|
||||
_$PagedValueCopyWithImpl(this._value, this._then);
|
||||
|
||||
final PagedValue<Key, Value> _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function(PagedValue<Key, Value>) _then;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $SuccessCopyWith<Key, Value, $Res> {
|
||||
factory $SuccessCopyWith(
|
||||
Success<Key, Value> value, $Res Function(Success<Key, Value>) then) =
|
||||
_$SuccessCopyWithImpl<Key, Value, $Res>;
|
||||
$Res call({List<Value> items, Key? nextPageKey, StreamChatError? error});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$SuccessCopyWithImpl<Key, Value, $Res>
|
||||
extends _$PagedValueCopyWithImpl<Key, Value, $Res>
|
||||
implements $SuccessCopyWith<Key, Value, $Res> {
|
||||
_$SuccessCopyWithImpl(
|
||||
Success<Key, Value> _value, $Res Function(Success<Key, Value>) _then)
|
||||
: super(_value, (v) => _then(v as Success<Key, Value>));
|
||||
|
||||
@override
|
||||
Success<Key, Value> get _value => super._value as Success<Key, Value>;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object? items = freezed,
|
||||
Object? nextPageKey = freezed,
|
||||
Object? error = freezed,
|
||||
}) {
|
||||
return _then(Success<Key, Value>(
|
||||
items: items == freezed
|
||||
? _value.items
|
||||
: items // ignore: cast_nullable_to_non_nullable
|
||||
as List<Value>,
|
||||
nextPageKey: nextPageKey == freezed
|
||||
? _value.nextPageKey
|
||||
: nextPageKey // ignore: cast_nullable_to_non_nullable
|
||||
as Key?,
|
||||
error: error == freezed
|
||||
? _value.error
|
||||
: error // ignore: cast_nullable_to_non_nullable
|
||||
as StreamChatError?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$Success<Key, Value> extends Success<Key, Value>
|
||||
with DiagnosticableTreeMixin {
|
||||
const _$Success({required this.items, this.nextPageKey, this.error})
|
||||
: super._();
|
||||
|
||||
@override
|
||||
|
||||
/// List with all items loaded so far.
|
||||
final List<Value> items;
|
||||
@override
|
||||
|
||||
/// The key for the next page to be fetched.
|
||||
final Key? nextPageKey;
|
||||
@override
|
||||
|
||||
/// The current error, if any.
|
||||
final StreamChatError? error;
|
||||
|
||||
@override
|
||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||
return 'PagedValue<$Key, $Value>(items: $items, nextPageKey: $nextPageKey, error: $error)';
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'PagedValue<$Key, $Value>'))
|
||||
..add(DiagnosticsProperty('items', items))
|
||||
..add(DiagnosticsProperty('nextPageKey', nextPageKey))
|
||||
..add(DiagnosticsProperty('error', error));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other is Success<Key, Value> &&
|
||||
(identical(other.items, items) ||
|
||||
const DeepCollectionEquality().equals(other.items, items)) &&
|
||||
(identical(other.nextPageKey, nextPageKey) ||
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.nextPageKey, nextPageKey)) &&
|
||||
(identical(other.error, error) ||
|
||||
const DeepCollectionEquality().equals(other.error, error)));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
runtimeType.hashCode ^
|
||||
const DeepCollectionEquality().hash(items) ^
|
||||
const DeepCollectionEquality().hash(nextPageKey) ^
|
||||
const DeepCollectionEquality().hash(error);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
$SuccessCopyWith<Key, Value, Success<Key, Value>> get copyWith =>
|
||||
_$SuccessCopyWithImpl<Key, Value, Success<Key, Value>>(this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function(
|
||||
List<Value> items, Key? nextPageKey, StreamChatError? error)
|
||||
$default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(StreamChatError error) error,
|
||||
}) {
|
||||
return $default(items, nextPageKey, this.error);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult Function(
|
||||
List<Value> items, Key? nextPageKey, StreamChatError? error)?
|
||||
$default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(StreamChatError error)? error,
|
||||
}) {
|
||||
return $default?.call(items, nextPageKey, this.error);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function(
|
||||
List<Value> items, Key? nextPageKey, StreamChatError? error)?
|
||||
$default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(StreamChatError error)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if ($default != null) {
|
||||
return $default(items, nextPageKey, this.error);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(Success<Key, Value> value) $default, {
|
||||
required TResult Function(Loading<Key, Value> value) loading,
|
||||
required TResult Function(Error<Key, Value> value) error,
|
||||
}) {
|
||||
return $default(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult Function(Success<Key, Value> value)? $default, {
|
||||
TResult Function(Loading<Key, Value> value)? loading,
|
||||
TResult Function(Error<Key, Value> value)? error,
|
||||
}) {
|
||||
return $default?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(Success<Key, Value> value)? $default, {
|
||||
TResult Function(Loading<Key, Value> value)? loading,
|
||||
TResult Function(Error<Key, Value> value)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if ($default != null) {
|
||||
return $default(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Success<Key, Value> extends PagedValue<Key, Value> {
|
||||
const factory Success(
|
||||
{required List<Value> items,
|
||||
Key? nextPageKey,
|
||||
StreamChatError? error}) = _$Success<Key, Value>;
|
||||
const Success._() : super._();
|
||||
|
||||
/// List with all items loaded so far.
|
||||
List<Value> get items => throw _privateConstructorUsedError;
|
||||
|
||||
/// The key for the next page to be fetched.
|
||||
Key? get nextPageKey => throw _privateConstructorUsedError;
|
||||
|
||||
/// The current error, if any.
|
||||
StreamChatError? get error => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$SuccessCopyWith<Key, Value, Success<Key, Value>> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LoadingCopyWith<Key, Value, $Res> {
|
||||
factory $LoadingCopyWith(
|
||||
Loading<Key, Value> value, $Res Function(Loading<Key, Value>) then) =
|
||||
_$LoadingCopyWithImpl<Key, Value, $Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LoadingCopyWithImpl<Key, Value, $Res>
|
||||
extends _$PagedValueCopyWithImpl<Key, Value, $Res>
|
||||
implements $LoadingCopyWith<Key, Value, $Res> {
|
||||
_$LoadingCopyWithImpl(
|
||||
Loading<Key, Value> _value, $Res Function(Loading<Key, Value>) _then)
|
||||
: super(_value, (v) => _then(v as Loading<Key, Value>));
|
||||
|
||||
@override
|
||||
Loading<Key, Value> get _value => super._value as Loading<Key, Value>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$Loading<Key, Value> extends Loading<Key, Value>
|
||||
with DiagnosticableTreeMixin {
|
||||
const _$Loading() : super._();
|
||||
|
||||
@override
|
||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||
return 'PagedValue<$Key, $Value>.loading()';
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'PagedValue<$Key, $Value>.loading'));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) || (other is Loading<Key, Value>);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function(
|
||||
List<Value> items, Key? nextPageKey, StreamChatError? error)
|
||||
$default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(StreamChatError error) error,
|
||||
}) {
|
||||
return loading();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult Function(
|
||||
List<Value> items, Key? nextPageKey, StreamChatError? error)?
|
||||
$default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(StreamChatError error)? error,
|
||||
}) {
|
||||
return loading?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function(
|
||||
List<Value> items, Key? nextPageKey, StreamChatError? error)?
|
||||
$default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(StreamChatError error)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (loading != null) {
|
||||
return loading();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(Success<Key, Value> value) $default, {
|
||||
required TResult Function(Loading<Key, Value> value) loading,
|
||||
required TResult Function(Error<Key, Value> value) error,
|
||||
}) {
|
||||
return loading(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult Function(Success<Key, Value> value)? $default, {
|
||||
TResult Function(Loading<Key, Value> value)? loading,
|
||||
TResult Function(Error<Key, Value> value)? error,
|
||||
}) {
|
||||
return loading?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(Success<Key, Value> value)? $default, {
|
||||
TResult Function(Loading<Key, Value> value)? loading,
|
||||
TResult Function(Error<Key, Value> value)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (loading != null) {
|
||||
return loading(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Loading<Key, Value> extends PagedValue<Key, Value> {
|
||||
const factory Loading() = _$Loading<Key, Value>;
|
||||
const Loading._() : super._();
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ErrorCopyWith<Key, Value, $Res> {
|
||||
factory $ErrorCopyWith(
|
||||
Error<Key, Value> value, $Res Function(Error<Key, Value>) then) =
|
||||
_$ErrorCopyWithImpl<Key, Value, $Res>;
|
||||
$Res call({StreamChatError error});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ErrorCopyWithImpl<Key, Value, $Res>
|
||||
extends _$PagedValueCopyWithImpl<Key, Value, $Res>
|
||||
implements $ErrorCopyWith<Key, Value, $Res> {
|
||||
_$ErrorCopyWithImpl(
|
||||
Error<Key, Value> _value, $Res Function(Error<Key, Value>) _then)
|
||||
: super(_value, (v) => _then(v as Error<Key, Value>));
|
||||
|
||||
@override
|
||||
Error<Key, Value> get _value => super._value as Error<Key, Value>;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object? error = freezed,
|
||||
}) {
|
||||
return _then(Error<Key, Value>(
|
||||
error == freezed
|
||||
? _value.error
|
||||
: error // ignore: cast_nullable_to_non_nullable
|
||||
as StreamChatError,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$Error<Key, Value> extends Error<Key, Value>
|
||||
with DiagnosticableTreeMixin {
|
||||
const _$Error(this.error) : super._();
|
||||
|
||||
@override
|
||||
final StreamChatError error;
|
||||
|
||||
@override
|
||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||
return 'PagedValue<$Key, $Value>.error(error: $error)';
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'PagedValue<$Key, $Value>.error'))
|
||||
..add(DiagnosticsProperty('error', error));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other is Error<Key, Value> &&
|
||||
(identical(other.error, error) ||
|
||||
const DeepCollectionEquality().equals(other.error, error)));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
runtimeType.hashCode ^ const DeepCollectionEquality().hash(error);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
$ErrorCopyWith<Key, Value, Error<Key, Value>> get copyWith =>
|
||||
_$ErrorCopyWithImpl<Key, Value, Error<Key, Value>>(this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function(
|
||||
List<Value> items, Key? nextPageKey, StreamChatError? error)
|
||||
$default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(StreamChatError error) error,
|
||||
}) {
|
||||
return error(this.error);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult Function(
|
||||
List<Value> items, Key? nextPageKey, StreamChatError? error)?
|
||||
$default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(StreamChatError error)? error,
|
||||
}) {
|
||||
return error?.call(this.error);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function(
|
||||
List<Value> items, Key? nextPageKey, StreamChatError? error)?
|
||||
$default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(StreamChatError error)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (error != null) {
|
||||
return error(this.error);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(Success<Key, Value> value) $default, {
|
||||
required TResult Function(Loading<Key, Value> value) loading,
|
||||
required TResult Function(Error<Key, Value> value) error,
|
||||
}) {
|
||||
return error(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult Function(Success<Key, Value> value)? $default, {
|
||||
TResult Function(Loading<Key, Value> value)? loading,
|
||||
TResult Function(Error<Key, Value> value)? error,
|
||||
}) {
|
||||
return error?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(Success<Key, Value> value)? $default, {
|
||||
TResult Function(Loading<Key, Value> value)? loading,
|
||||
TResult Function(Error<Key, Value> value)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (error != null) {
|
||||
return error(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Error<Key, Value> extends PagedValue<Key, Value> {
|
||||
const factory Error(StreamChatError error) = _$Error<Key, Value>;
|
||||
const Error._() : super._();
|
||||
|
||||
StreamChatError get error => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$ErrorCopyWith<Key, Value, Error<Key, Value>> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
-281
@@ -1,281 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
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/v4/channel_list_view/stream_channel_list_event_handler.dart';
|
||||
|
||||
/// The default channel page limit to load.
|
||||
const defaultChannelPagedLimit = 10;
|
||||
|
||||
const _kDefaultBackendPaginationLimit = 30;
|
||||
|
||||
/// A controller for a Channel list.
|
||||
///
|
||||
/// This class lets you perform tasks such as:
|
||||
/// * Load initial data.
|
||||
/// * Use channel events handlers.
|
||||
/// * Load more data using [loadMore].
|
||||
/// * Replace the previously loaded channels.
|
||||
/// * Return/Create a new channel and start watching it.
|
||||
/// * Pause and Resume all subscriptions added to this composite.
|
||||
class StreamChannelListController extends PagedValueNotifier<int, Channel> {
|
||||
/// Creates a Stream channel list controller.
|
||||
///
|
||||
/// * `client` is the Stream chat client to use for the channels list.
|
||||
///
|
||||
/// * `channelEventHandlers` is the channel events to use for the channels
|
||||
/// list. This class can be mixed in or extended to create custom overrides.
|
||||
/// See [StreamChannelListEventHandler] for advice.
|
||||
///
|
||||
/// * `filter` is the query filters to use.
|
||||
///
|
||||
/// * `sort` is the sorting used for the channels matching the filters.
|
||||
///
|
||||
/// * `presence` sets whether you'll receive user presence updates via the
|
||||
/// websocket events.
|
||||
///
|
||||
/// * `limit` is the limit to apply to the channel list.
|
||||
///
|
||||
/// * `messageLimit` is the number of messages to fetch in each channel.
|
||||
///
|
||||
/// * `memberLimit` is the number of members to fetch in each channel.
|
||||
StreamChannelListController({
|
||||
required this.client,
|
||||
StreamChannelListEventHandler? eventHandler,
|
||||
this.filter,
|
||||
this.sort,
|
||||
this.presence = true,
|
||||
this.limit = defaultChannelPagedLimit,
|
||||
this.messageLimit,
|
||||
this.memberLimit,
|
||||
}) : _eventHandler = eventHandler ?? StreamChannelListEventHandler(),
|
||||
super(const PagedValue.loading());
|
||||
|
||||
/// Creates a [StreamChannelListController] from the passed [value].
|
||||
StreamChannelListController.fromValue(
|
||||
PagedValue<int, Channel> value, {
|
||||
required this.client,
|
||||
StreamChannelListEventHandler? eventHandler,
|
||||
this.filter,
|
||||
this.sort,
|
||||
this.presence = true,
|
||||
this.limit = defaultChannelPagedLimit,
|
||||
this.messageLimit,
|
||||
this.memberLimit,
|
||||
}) : _eventHandler = eventHandler ?? StreamChannelListEventHandler(),
|
||||
super(value);
|
||||
|
||||
/// The client to use for the channels list.
|
||||
final StreamChatClient client;
|
||||
|
||||
/// The channel event handlers to use for the channels list.
|
||||
final StreamChannelListEventHandler _eventHandler;
|
||||
|
||||
/// The query filters to use.
|
||||
///
|
||||
/// You can query on any of the custom fields you've defined on the [Channel].
|
||||
///
|
||||
/// You can also filter other built-in channel fields.
|
||||
final Filter? filter;
|
||||
|
||||
/// The sorting used for the channels matching the filters.
|
||||
///
|
||||
/// Sorting is based on field and direction, multiple sorting options
|
||||
/// can be provided.
|
||||
///
|
||||
/// You can sort based on last_updated, last_message_at, updated_at,
|
||||
/// created_at or member_count.
|
||||
///
|
||||
/// Direction can be ascending or descending.
|
||||
final List<SortOption<ChannelModel>>? sort;
|
||||
|
||||
/// If true you’ll receive user presence updates via the websocket events
|
||||
final bool presence;
|
||||
|
||||
/// The limit to apply to the channel list. The default is set to
|
||||
/// [defaultChannelPagedLimit].
|
||||
final int limit;
|
||||
|
||||
/// Number of messages to fetch in each channel.
|
||||
final int? messageLimit;
|
||||
|
||||
/// Number of members to fetch in each channel.
|
||||
final int? memberLimit;
|
||||
|
||||
@override
|
||||
Future<void> doInitialLoad() async {
|
||||
final limit = min(
|
||||
this.limit * defaultInitialPagedLimitMultiplier,
|
||||
_kDefaultBackendPaginationLimit,
|
||||
);
|
||||
try {
|
||||
await for (final channels in client.queryChannels(
|
||||
filter: filter,
|
||||
sort: sort,
|
||||
memberLimit: memberLimit,
|
||||
messageLimit: messageLimit,
|
||||
presence: presence,
|
||||
paginationParams: PaginationParams(limit: limit),
|
||||
)) {
|
||||
final nextKey = channels.length < limit ? null : channels.length;
|
||||
value = PagedValue(
|
||||
items: channels,
|
||||
nextPageKey: nextKey,
|
||||
);
|
||||
}
|
||||
// start listening to events
|
||||
_subscribeToChannelListEvents();
|
||||
} on StreamChatError catch (error) {
|
||||
value = PagedValue.error(error);
|
||||
} catch (error) {
|
||||
final chatError = StreamChatError(error.toString());
|
||||
value = PagedValue.error(chatError);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> loadMore(int nextPageKey) async {
|
||||
final previousValue = value.asSuccess;
|
||||
|
||||
try {
|
||||
await for (final channels in client.queryChannels(
|
||||
filter: filter,
|
||||
sort: sort,
|
||||
memberLimit: memberLimit,
|
||||
messageLimit: messageLimit,
|
||||
presence: presence,
|
||||
paginationParams: PaginationParams(limit: limit, offset: nextPageKey),
|
||||
)) {
|
||||
final previousItems = previousValue.items;
|
||||
final newItems = previousItems + channels;
|
||||
final nextKey = channels.length < limit ? null : newItems.length;
|
||||
value = PagedValue(
|
||||
items: newItems,
|
||||
nextPageKey: nextKey,
|
||||
);
|
||||
}
|
||||
} on StreamChatError catch (error) {
|
||||
value = previousValue.copyWith(error: error);
|
||||
} catch (error) {
|
||||
final chatError = StreamChatError(error.toString());
|
||||
value = previousValue.copyWith(error: chatError);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// Leaves the [channel] and updates the list.
|
||||
Future<void> leaveChannel(Channel channel) async {
|
||||
final user = client.state.currentUser;
|
||||
assert(user != null, 'You must be logged in to leave a channel.');
|
||||
await channel.removeMembers([user!.id]);
|
||||
}
|
||||
|
||||
/// Deletes the [channel] and updates the list.
|
||||
Future<void> deleteChannel(Channel channel) async {
|
||||
await channel.delete();
|
||||
}
|
||||
|
||||
/// Mutes the [channel] and updates the list.
|
||||
Future<void> muteChannel(Channel channel) async {
|
||||
await channel.mute();
|
||||
}
|
||||
|
||||
/// Un-mutes the [channel] and updates the list.
|
||||
Future<void> unmuteChannel(Channel channel) async {
|
||||
await channel.unmute();
|
||||
}
|
||||
|
||||
/// Event listener, which can be set in order to listen
|
||||
/// [client] web-socket events.
|
||||
///
|
||||
/// Return `true` if the event is handled. Return `false` to
|
||||
/// allow the event to be handled internally.
|
||||
bool Function(Event event)? eventListener;
|
||||
|
||||
StreamSubscription<Event>? _channelEventSubscription;
|
||||
|
||||
// Subscribes to the channel list events.
|
||||
void _subscribeToChannelListEvents() {
|
||||
if (_channelEventSubscription != null) {
|
||||
_unsubscribeFromChannelListEvents();
|
||||
}
|
||||
|
||||
_channelEventSubscription = client.on().listen((event) {
|
||||
// Returns early if the event is already handled by the listener.
|
||||
if (eventListener?.call(event) ?? false) return;
|
||||
|
||||
final eventType = event.type;
|
||||
if (eventType == EventType.channelDeleted) {
|
||||
_eventHandler.onChannelDeleted(event, this);
|
||||
} else if (eventType == EventType.channelHidden) {
|
||||
_eventHandler.onChannelHidden(event, this);
|
||||
} else if (eventType == EventType.channelTruncated) {
|
||||
_eventHandler.onChannelTruncated(event, this);
|
||||
} else if (eventType == EventType.channelUpdated) {
|
||||
_eventHandler.onChannelUpdated(event, this);
|
||||
} else if (eventType == EventType.channelVisible) {
|
||||
_eventHandler.onChannelVisible(event, this);
|
||||
} else if (eventType == EventType.connectionRecovered) {
|
||||
_eventHandler.onConnectionRecovered(event, this);
|
||||
} else if (eventType == EventType.connectionChanged) {
|
||||
if (event.online != null) {
|
||||
_eventHandler.onConnectionRecovered(event, this);
|
||||
}
|
||||
} else if (eventType == EventType.messageNew) {
|
||||
_eventHandler.onMessageNew(event, this);
|
||||
} else if (eventType == EventType.notificationAddedToChannel) {
|
||||
_eventHandler.onNotificationAddedToChannel(event, this);
|
||||
} else if (eventType == EventType.notificationMessageNew) {
|
||||
_eventHandler.onNotificationMessageNew(event, this);
|
||||
} else if (eventType == EventType.notificationRemovedFromChannel) {
|
||||
_eventHandler.onNotificationRemovedFromChannel(event, this);
|
||||
} else if (eventType == 'user.presence.changed' ||
|
||||
eventType == EventType.userUpdated) {
|
||||
_eventHandler.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();
|
||||
}
|
||||
}
|
||||
-213
@@ -1,213 +0,0 @@
|
||||
import 'package:stream_chat/stream_chat.dart' show ChannelState, Event;
|
||||
import 'package:stream_chat_flutter/src/v4/channel_list_view/stream_channel_list_controller.dart';
|
||||
|
||||
/// Contains handlers that are called from [StreamChannelListController] for
|
||||
/// certain [Event]s.
|
||||
///
|
||||
/// This class can be mixed in or extended to create custom overrides.
|
||||
class StreamChannelListEventHandler {
|
||||
/// Function which gets called for the event
|
||||
/// [EventType.channelDeleted].
|
||||
///
|
||||
/// This event is fired when a channel is deleted.
|
||||
///
|
||||
/// By default, this removes the channel from the list of channels.
|
||||
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;
|
||||
}
|
||||
|
||||
/// Function which gets called for the event
|
||||
/// [EventType.channelHidden].
|
||||
///
|
||||
/// This event is fired when a channel is hidden.
|
||||
///
|
||||
/// By default, this removes the channel from the list of channels.
|
||||
void onChannelHidden(Event event, StreamChannelListController controller) {
|
||||
onChannelDeleted(event, controller);
|
||||
}
|
||||
|
||||
/// Function which gets called for the event
|
||||
/// [EventType.channelTruncated].
|
||||
///
|
||||
/// This event is fired when a channel is truncated.
|
||||
///
|
||||
/// By default, this refreshes the whole channel list.
|
||||
void onChannelTruncated(Event event, StreamChannelListController controller) {
|
||||
controller.refresh();
|
||||
}
|
||||
|
||||
/// Function which gets called for the event
|
||||
/// [EventType.channelUpdated].
|
||||
///
|
||||
/// This event is fired when a channel is updated.
|
||||
///
|
||||
/// By default, this updates the channel received in the event.
|
||||
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;
|
||||
}
|
||||
|
||||
/// Function which gets called for the event
|
||||
/// [EventType.channelVisible].
|
||||
///
|
||||
/// This event is fired when a channel is made visible.
|
||||
///
|
||||
/// By default, this adds the channel to the list of channels.
|
||||
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;
|
||||
}
|
||||
|
||||
/// Function which gets called for the event
|
||||
/// [EventType.connectionRecovered].
|
||||
///
|
||||
/// This event is fired when the client web-socket connection recovers.
|
||||
///
|
||||
/// By default, this refreshes the whole channel list.
|
||||
void onConnectionRecovered(
|
||||
Event event,
|
||||
StreamChannelListController controller,
|
||||
) {
|
||||
controller.refresh();
|
||||
}
|
||||
|
||||
/// Function which gets called for the event [EventType.messageNew].
|
||||
///
|
||||
/// 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.
|
||||
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];
|
||||
}
|
||||
|
||||
/// Function which gets called for the event
|
||||
/// [EventType.notificationAddedToChannel].
|
||||
///
|
||||
/// 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.
|
||||
void onNotificationAddedToChannel(
|
||||
Event event,
|
||||
StreamChannelListController controller,
|
||||
) {
|
||||
onChannelVisible(event, controller);
|
||||
}
|
||||
|
||||
/// Function which gets called for the event
|
||||
/// [EventType.notificationMessageNew].
|
||||
///
|
||||
/// 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.
|
||||
void onNotificationMessageNew(
|
||||
Event event,
|
||||
StreamChannelListController controller,
|
||||
) {
|
||||
onChannelVisible(event, controller);
|
||||
}
|
||||
|
||||
/// Function which gets called for the event
|
||||
/// [EventType.notificationRemovedFromChannel].
|
||||
///
|
||||
/// 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.
|
||||
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];
|
||||
}
|
||||
|
||||
/// Function which gets called for the event
|
||||
/// 'user.presence.changed' and [EventType.userUpdated].
|
||||
///
|
||||
/// This event is fired when a user's presence changes or gets updated.
|
||||
///
|
||||
/// By default, this updates the channel member with the event user.
|
||||
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];
|
||||
}
|
||||
}
|
||||
-3
@@ -1,11 +1,8 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/src/paged_value_notifier.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||
import 'package:stream_chat_flutter/src/v4/channel_list_view/stream_channel_list_controller.dart';
|
||||
import 'package:stream_chat_flutter/src/v4/channel_list_view/stream_channel_list_loading_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';
|
||||
|
||||
@@ -25,8 +25,6 @@ export 'src/mention_tile.dart';
|
||||
export 'src/message_action.dart';
|
||||
export 'src/message_input/countdown_button.dart';
|
||||
export 'src/message_input/message_input.dart';
|
||||
export 'src/message_input/message_input_controller.dart';
|
||||
export 'src/message_input/message_text_field_controller.dart';
|
||||
export 'src/message_input/stream_attachment_picker.dart';
|
||||
export 'src/message_input/stream_message_send_button.dart';
|
||||
export 'src/message_input/stream_message_text_field.dart';
|
||||
@@ -55,8 +53,6 @@ export 'src/user_mention_tile.dart';
|
||||
export 'src/utils.dart';
|
||||
|
||||
// v4
|
||||
export 'src/v4/channel_list_view/stream_channel_list_controller.dart';
|
||||
export 'src/v4/channel_list_view/stream_channel_list_event_handler.dart';
|
||||
export 'src/v4/channel_list_view/stream_channel_list_loading_tile.dart';
|
||||
export 'src/v4/channel_list_view/stream_channel_list_tile.dart';
|
||||
export 'src/v4/channel_list_view/stream_channel_list_view.dart';
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets(
|
||||
'should instantiate a new MessageInputController with empty message',
|
||||
(tester) async {
|
||||
final controller = MessageInputController()..text = 'test';
|
||||
|
||||
expect(controller.text, 'test');
|
||||
expect(controller.message.text, 'test');
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user