experiment with streambuilder

This commit is contained in:
Salvatore Giordano
2021-06-01 17:24:26 +02:00
parent 06bf2cdfe5
commit c10166dbff
13 changed files with 342 additions and 234 deletions
+1 -1
View File
@@ -213,7 +213,7 @@ class StreamChatClient {
_wsConnectionStatusController.add(status); _wsConnectionStatusController.add(status);
/// The current status value of the websocket connection /// The current status value of the websocket connection
ConnectionStatus? get wsConnectionStatus => ConnectionStatus get wsConnectionStatus =>
_wsConnectionStatusController.value; _wsConnectionStatusController.value;
/// This notifies the connection status of the websocket connection. /// This notifies the connection status of the websocket connection.
@@ -83,14 +83,14 @@ class ChannelImage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final streamChat = StreamChat.of(context); final streamChat = StreamChat.of(context);
final channel = this.channel ?? StreamChannel.of(context).channel; final channel = this.channel ?? StreamChannel.of(context).channel;
return StreamBuilder<Map<String, dynamic>>( return BetterStreamBuilder<Map<String, dynamic>>(
stream: channel.extraDataStream, stream: channel.extraDataStream,
initialData: channel.extraData, initialData: channel.extraData,
builder: (context, snapshot) { builder: (context, snapshot) {
String? image; String? image;
final chatThemeData = StreamChatTheme.of(context); final chatThemeData = StreamChatTheme.of(context);
if (snapshot.data!.containsKey('image') == true) { if (snapshot.containsKey('image') == true) {
image = snapshot.data!['image']; image = snapshot['image'];
} else if (channel.state?.members.length == 2) { } else if (channel.state?.members.length == 2) {
final otherMember = channel.state?.members final otherMember = channel.state?.members
.firstWhere((member) => member.user?.id != streamChat.user?.id); .firstWhere((member) => member.user?.id != streamChat.user?.id);
@@ -153,9 +153,7 @@ class ChannelImage extends StatelessWidget {
imageUrl: image, imageUrl: image,
errorWidget: (_, __, ___) => Center( errorWidget: (_, __, ___) => Center(
child: Text( child: Text(
snapshot.data?.containsKey('name') ?? false snapshot.containsKey('name') ? snapshot['name'][0] : '',
? snapshot.data!['name'][0]
: '',
style: TextStyle( style: TextStyle(
color: chatThemeData.colorTheme.white, color: chatThemeData.colorTheme.white,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -25,13 +25,14 @@ class ChannelInfo extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final client = StreamChat.of(context).client; final client = StreamChat.of(context).client;
return StreamBuilder<List<Member>>( return BetterStreamBuilder<List<Member>>(
stream: channel.state?.membersStream, stream: channel.state!.membersStream,
initialData: channel.state!.members,
builder: (context, snapshot) => ConnectionStatusBuilder( builder: (context, snapshot) => ConnectionStatusBuilder(
statusBuilder: (context, status) { statusBuilder: (context, status) {
switch (status) { switch (status) {
case ConnectionStatus.connected: case ConnectionStatus.connected:
return _buildConnectedTitleState(context, snapshot.data); return _buildConnectedTitleState(context, snapshot);
case ConnectionStatus.connecting: case ConnectionStatus.connecting:
return _buildConnectingTitleState(context); return _buildConnectingTitleState(context);
case ConnectionStatus.disconnected: case ConnectionStatus.disconnected:
@@ -26,11 +26,14 @@ class ChannelName extends StatelessWidget {
final client = StreamChat.of(context); final client = StreamChat.of(context);
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
return StreamBuilder<Map<String, dynamic>>( return BetterStreamBuilder<Map<String, Object?>>(
stream: channel.extraDataStream, stream: channel.extraDataStream,
initialData: channel.extraData, initialData: channel.extraData,
builder: (context, snapshot) => builder: (context, snapshot) => _buildName(
_buildName(snapshot.data!, channel.state?.members, client), snapshot,
channel.state?.members,
client,
),
); );
} }
@@ -13,14 +13,14 @@ class ConnectionStatusBuilder extends StatelessWidget {
const ConnectionStatusBuilder({ const ConnectionStatusBuilder({
Key? key, Key? key,
required this.statusBuilder, required this.statusBuilder,
this.initialStatus = ConnectionStatus.disconnected, this.initialStatus,
this.connectionStatusStream, this.connectionStatusStream,
this.errorBuilder, this.errorBuilder,
this.loadingBuilder, this.loadingBuilder,
}) : super(key: key); }) : super(key: key);
/// The connection status that will be used to create the initial snapshot. /// The connection status that will be used to create the initial snapshot.
final ConnectionStatus initialStatus; final ConnectionStatus? initialStatus;
/// The asynchronous computation to which this builder is currently connected. /// The asynchronous computation to which this builder is currently connected.
final Stream<ConnectionStatus>? connectionStatusStream; final Stream<ConnectionStatus>? connectionStatusStream;
@@ -37,23 +37,19 @@ class ConnectionStatusBuilder extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final stream = connectionStatusStream ?? final client = StreamChat.of(context).client;
StreamChat.of(context).client.wsConnectionStatusStream; final stream = connectionStatusStream ?? client.wsConnectionStatusStream;
return StreamBuilder<ConnectionStatus>( return BetterStreamBuilder<ConnectionStatus>(
initialData: initialStatus ?? client.wsConnectionStatus,
stream: stream, stream: stream,
builder: (context, snapshot) { loadingBuilder: loadingBuilder,
if (snapshot.hasError) { errorBuilder: (context, error) {
if (errorBuilder != null) { if (errorBuilder != null) {
return errorBuilder!(context, snapshot.error); return errorBuilder!(context, error);
}
return const Offstage();
} }
if (!snapshot.hasData) { return const Offstage();
if (loadingBuilder != null) return loadingBuilder!(context);
return const Offstage();
}
return statusBuilder(context, snapshot.data!);
}, },
builder: statusBuilder,
); );
} }
} }
@@ -606,8 +606,8 @@ class _MessageListViewState extends State<MessageListView> {
Widget _buildScrollToBottom() => StreamBuilder<Tuple2<bool, int>>( Widget _buildScrollToBottom() => StreamBuilder<Tuple2<bool, int>>(
stream: Rx.combineLatest2( stream: Rx.combineLatest2(
streamChannel!.channel.state!.isUpToDateStream, streamChannel!.channel.state!.isUpToDateStream.distinct(),
streamChannel!.channel.state!.unreadCountStream, streamChannel!.channel.state!.unreadCountStream.distinct(),
(bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount),
), ),
builder: (_, snapshot) { builder: (_, snapshot) {
@@ -689,23 +689,18 @@ class _MessageListViewState extends State<MessageListView> {
final stream = direction == QueryDirection.top final stream = direction == QueryDirection.top
? streamChannel.queryTopMessages ? streamChannel.queryTopMessages
: streamChannel.queryBottomMessages; : streamChannel.queryBottomMessages;
return StreamBuilder<bool>( return BetterStreamBuilder<bool>(
key: Key('LOADING-INDICATOR $direction'), key: Key('LOADING-INDICATOR $direction'),
stream: stream, stream: stream,
initialData: false, initialData: false,
errorBuilder: (context, error) => Container(
color: StreamChatTheme.of(context).colorTheme.accentRed.withOpacity(.2),
child: const Center(
child: Text('Error loading messages'),
),
),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { if (!snapshot) {
return Container(
color: StreamChatTheme.of(context)
.colorTheme
.accentRed
.withOpacity(.2),
child: const Center(
child: Text('Error loading messages'),
),
);
}
if (!snapshot.data!) {
if (!_isThreadConversation && direction == QueryDirection.top) { if (!_isThreadConversation && direction == QueryDirection.top) {
return const SizedBox( return const SizedBox(
height: 52, height: 52,
@@ -1163,14 +1158,14 @@ class _MessageListViewState extends State<MessageListView> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => StreamBuilder<Message>( builder: (_) => BetterStreamBuilder<Message>(
stream: streamChannel!.channel.state!.messagesStream.map( stream: streamChannel!.channel.state!.messagesStream.map(
(messages) => (messages) =>
messages!.firstWhere((m) => m.id == message.id)), messages!.firstWhere((m) => m.id == message.id)),
initialData: message, initialData: message,
builder: (_, snapshot) => StreamChannel( builder: (_, snapshot) => StreamChannel(
channel: streamChannel!.channel, channel: streamChannel!.channel,
child: widget.threadBuilder!(context, snapshot.data), child: widget.threadBuilder!(context, snapshot),
), ),
), ),
), ),
@@ -449,182 +449,189 @@ class _MessageWidgetState extends State<MessageWidget>
final leftPadding = final leftPadding =
widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5;
return Portal( return Material(
child: GestureDetector( type: MaterialType.transparency,
onTap: () { child: Portal(
widget.onMessageTap!(widget.message); child: InkWell(
}, onTap: () {
onLongPress: widget.message.isDeleted && !isFailedState widget.onMessageTap!(widget.message);
? null },
: () => onLongPress(context), onLongPress: widget.message.isDeleted && !isFailedState
child: Padding( ? null
padding: widget.padding ?? const EdgeInsets.all(8), : () => onLongPress(context),
child: FractionallySizedBox( child: Padding(
alignment: padding: widget.padding ?? const EdgeInsets.all(8),
widget.reverse ? Alignment.centerRight : Alignment.centerLeft, child: FractionallySizedBox(
widthFactor: 0.78, alignment:
child: Column( widget.reverse ? Alignment.centerRight : Alignment.centerLeft,
crossAxisAlignment: widget.reverse widthFactor: 0.78,
? CrossAxisAlignment.end child: Column(
: CrossAxisAlignment.start, crossAxisAlignment: widget.reverse
mainAxisSize: MainAxisSize.min, ? CrossAxisAlignment.end
children: <Widget>[ : CrossAxisAlignment.start,
Stack( mainAxisSize: MainAxisSize.min,
clipBehavior: Clip.none, children: <Widget>[
alignment: widget.reverse Stack(
? AlignmentDirectional.bottomEnd clipBehavior: Clip.none,
: AlignmentDirectional.bottomStart, alignment: widget.reverse
children: [ ? AlignmentDirectional.bottomEnd
Column( : AlignmentDirectional.bottomStart,
crossAxisAlignment: widget.reverse children: [
? CrossAxisAlignment.end Column(
: CrossAxisAlignment.start, crossAxisAlignment: widget.reverse
mainAxisSize: MainAxisSize.min, ? CrossAxisAlignment.end
children: [ : CrossAxisAlignment.start,
Row( mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end, children: [
mainAxisSize: MainAxisSize.min, Row(
children: <Widget>[ crossAxisAlignment: CrossAxisAlignment.end,
if (widget.showUserAvatar == DisplayWidget.show && mainAxisSize: MainAxisSize.min,
widget.message.user != null) ...[ children: <Widget>[
_buildUserAvatar(), if (widget.showUserAvatar == DisplayWidget.show &&
const SizedBox(width: 4), widget.message.user != null) ...[
], _buildUserAvatar(),
if (widget.showUserAvatar == DisplayWidget.hide) const SizedBox(width: 4),
SizedBox(width: avatarWidth + 4), ],
Flexible( if (widget.showUserAvatar == DisplayWidget.hide)
child: PortalEntry( SizedBox(width: avatarWidth + 4),
portal: Container( Flexible(
transform: Matrix4.translationValues( child: PortalEntry(
widget.reverse ? 12 : -12, 0, 0), portal: Container(
constraints: transform: Matrix4.translationValues(
const BoxConstraints(maxWidth: 22 * 6.0), widget.reverse ? 12 : -12, 0, 0),
child: _buildReactionIndicator(context), constraints: const BoxConstraints(
), maxWidth: 22 * 6.0),
portalAnchor: child: _buildReactionIndicator(context),
Alignment(widget.reverse ? 1 : -1, -1), ),
childAnchor: portalAnchor:
Alignment(widget.reverse ? -1 : 1, -1), Alignment(widget.reverse ? 1 : -1, -1),
child: Stack( childAnchor:
clipBehavior: Clip.none, Alignment(widget.reverse ? -1 : 1, -1),
children: [ child: Stack(
Padding( clipBehavior: Clip.none,
padding: widget.showReactions children: [
? EdgeInsets.only( Padding(
top: widget.message.reactionCounts padding: widget.showReactions
?.isNotEmpty == ? EdgeInsets.only(
true top: widget
? 18 .message
: 0, .reactionCounts
) ?.isNotEmpty ==
: EdgeInsets.zero, true
child: (widget.message.isDeleted && ? 18
!isFailedState) : 0,
? Container( )
// ignore: lines_longer_than_80_chars : EdgeInsets.zero,
margin: EdgeInsets.symmetric( child: (widget.message.isDeleted &&
horizontal: !isFailedState)
? Container(
// ignore: lines_longer_than_80_chars
margin: EdgeInsets.symmetric(
horizontal:
// ignore: lines_longer_than_80_chars
widget.showUserAvatar ==
// ignore: lines_longer_than_80_chars
DisplayWidget
.gone
? 0
: 4.0),
child: DeletedMessage(
borderRadiusGeometry: widget
.borderRadiusGeometry,
borderSide: widget.borderSide,
shape: widget.shape,
messageTheme:
widget.messageTheme,
),
)
: Card(
clipBehavior: Clip.antiAlias,
elevation: 0,
margin: EdgeInsets.symmetric(
horizontal: (isFailedState
? 15.0
: 0.0) +
// ignore: lines_longer_than_80_chars // ignore: lines_longer_than_80_chars
widget.showUserAvatar == (widget.showUserAvatar ==
// ignore: lines_longer_than_80_chars
DisplayWidget.gone DisplayWidget.gone
? 0 ? 0
: 4.0), : 4.0),
child: DeletedMessage( ),
borderRadiusGeometry: shape: widget.shape ??
widget.borderRadiusGeometry, RoundedRectangleBorder(
borderSide: widget.borderSide, side: widget.borderSide ??
shape: widget.shape, BorderSide(
messageTheme: color: widget
widget.messageTheme, // ignore: lines_longer_than_80_chars
), .messageTheme
) // ignore: lines_longer_than_80_chars
: Card( .messageBorderColor ??
clipBehavior: Clip.antiAlias, Colors.grey,
elevation: 0, ),
margin: EdgeInsets.symmetric( borderRadius: widget
horizontal: (isFailedState // ignore: lines_longer_than_80_chars
? 15.0 .borderRadiusGeometry ??
: 0.0) + BorderRadius.zero,
// ignore: lines_longer_than_80_chars ),
(widget.showUserAvatar == color: _getBackgroundColor(),
DisplayWidget.gone child: Column(
? 0 crossAxisAlignment:
: 4.0), CrossAxisAlignment.end,
), mainAxisSize:
shape: widget.shape ?? MainAxisSize.min,
RoundedRectangleBorder( children: <Widget>[
side: widget.borderSide ?? if (hasQuotedMessage)
BorderSide( _buildQuotedMessage(),
color: widget if (hasNonUrlAttachments)
// ignore: lines_longer_than_80_chars _parseAttachments(),
.messageTheme if (!isGiphy)
// ignore: lines_longer_than_80_chars _buildTextBubble(),
.messageBorderColor ?? ],
Colors.grey, ),
),
borderRadius: widget
// ignore: lines_longer_than_80_chars
.borderRadiusGeometry ??
BorderRadius.zero,
),
color: _getBackgroundColor(),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (hasQuotedMessage)
_buildQuotedMessage(),
if (hasNonUrlAttachments)
_parseAttachments(),
if (!isGiphy)
_buildTextBubble(),
],
), ),
),
if (widget.showReactionPickerIndicator)
Positioned(
right: widget.reverse ? null : 4,
left: widget.reverse ? 4 : null,
top: -8,
child: CustomPaint(
painter: ReactionBubblePainter(
StreamChatTheme.of(context)
.colorTheme
.white,
Colors.transparent,
Colors.transparent,
tailCirclesSpace: 1,
), ),
),
if (widget.showReactionPickerIndicator)
Positioned(
right: widget.reverse ? null : 4,
left: widget.reverse ? 4 : null,
top: -8,
child: CustomPaint(
painter: ReactionBubblePainter(
StreamChatTheme.of(context)
.colorTheme
.white,
Colors.transparent,
Colors.transparent,
tailCirclesSpace: 1,
), ),
), ),
), ],
], ),
), ),
), ),
), ],
], ),
if (showBottomRow)
SizedBox(height: context.textScaleFactor * 18.0),
],
),
if (showBottomRow)
Padding(
padding: EdgeInsets.only(left: leftPadding),
child: _bottomRow,
), ),
if (showBottomRow) if (isFailedState)
SizedBox(height: context.textScaleFactor * 18.0), Positioned(
], left: widget.reverse ? 0 : null,
), right: widget.reverse ? null : 0,
if (showBottomRow) bottom: showBottomRow ? 18 : -2,
Padding( child: StreamSvgIcon.error(size: 20),
padding: EdgeInsets.only(left: leftPadding), ),
child: _bottomRow, ],
), ),
if (isFailedState) ],
Positioned( ),
left: widget.reverse ? 0 : null,
right: widget.reverse ? null : 0,
bottom: showBottomRow ? 18 : -2,
child: StreamSvgIcon.error(size: 20),
),
],
),
],
), ),
), ),
), ),
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart'; import 'package:lottie/lottie.dart';
import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Widget to show the current list of typing users /// Widget to show the current list of typing users
@@ -41,11 +42,12 @@ class TypingIndicator extends StatelessWidget {
child: alternativeWidget ?? const Offstage(), child: alternativeWidget ?? const Offstage(),
), ),
); );
return StreamBuilder<List<User>>( return BetterStreamBuilder<List<User>>(
initialData: channelState.typingEvents,
stream: channelState.typingEventsStream, stream: channelState.typingEventsStream,
builder: (context, snapshot) => AnimatedSwitcher( builder: (context, snapshot) => AnimatedSwitcher(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: snapshot.data?.isNotEmpty == true child: snapshot.isNotEmpty == true
? Padding( ? Padding(
padding: padding, padding: padding,
child: Align( child: Align(
@@ -61,7 +63,7 @@ class TypingIndicator extends StatelessWidget {
), ),
Text( Text(
// ignore: lines_longer_than_80_chars // ignore: lines_longer_than_80_chars
' ${snapshot.data![0].name}${snapshot.data!.length == 1 ? '' : ' and ${snapshot.data!.length - 1} more'} ${snapshot.data!.length == 1 ? 'is' : 'are'} typing', ' ${snapshot[0].name}${snapshot.length == 1 ? '' : ' and ${snapshot.length - 1} more'} ${snapshot.length == 1 ? 'is' : 'are'} typing',
maxLines: 1, maxLines: 1,
style: style, style: style,
), ),
@@ -17,7 +17,7 @@ class UnreadIndicator extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final client = StreamChat.of(context).client; final client = StreamChat.of(context).client;
return IgnorePointer( return IgnorePointer(
child: StreamBuilder<int?>( child: BetterStreamBuilder<int?>(
stream: cid != null stream: cid != null
? client.state.channels[cid]?.state?.unreadCountStream ? client.state.channels[cid]?.state?.unreadCountStream
: client.state.totalUnreadCountStream, : client.state.totalUnreadCountStream,
@@ -25,8 +25,8 @@ class UnreadIndicator extends StatelessWidget {
? client.state.channels[cid]?.state?.unreadCount ? client.state.channels[cid]?.state?.unreadCount
: client.state.totalUnreadCount, : client.state.totalUnreadCount,
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == 0) { if (snapshot == null || snapshot == 0) {
return const SizedBox(); return const Offstage();
} }
return Material( return Material(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -42,7 +42,7 @@ class UnreadIndicator extends StatelessWidget {
), ),
child: Center( child: Center(
child: Text( child: Text(
'${snapshot.data! > 99 ? '99+' : snapshot.data}', '${snapshot > 99 ? '99+' : snapshot}',
style: const TextStyle( style: const TextStyle(
fontSize: 11, fontSize: 11,
color: Colors.white, color: Colors.white,
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -0,0 +1,100 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
class BetterStreamBuilder<T> extends StatefulWidget {
const BetterStreamBuilder({
required this.stream,
required this.initialData,
required this.builder,
this.loadingBuilder,
this.errorBuilder,
this.comparator,
Key? key,
}) : super(key: key);
final Stream<T>? stream;
final T initialData;
final bool Function(T?, T)? comparator;
final Widget Function(BuildContext context, T data) builder;
final Widget Function(BuildContext context)? loadingBuilder;
final Widget Function(BuildContext context, Object error)? errorBuilder;
@override
_BetterStreamBuilderState createState() => _BetterStreamBuilderState<T>();
}
class _BetterStreamBuilderState<T> extends State<BetterStreamBuilder<T>> {
Widget? _child;
T? _lastEvent;
StreamSubscription? _subscription;
Object? _lastError;
@override
Widget build(BuildContext context) => _child ?? const Offstage();
bool _firstTime = true;
@override
void didChangeDependencies() {
if (_firstTime) {
if (widget.initialData == null && widget.loadingBuilder != null) {
_child = widget.loadingBuilder!(context);
} else {
_onEvent(widget.initialData);
}
_lastEvent = widget.initialData;
_subscription = widget.stream?.listen(
_onEvent,
onError: _onError,
);
_firstTime = false;
}
super.didChangeDependencies();
}
@override
void didUpdateWidget(covariant BetterStreamBuilder<T> oldWidget) {
if (_lastError != null && oldWidget.errorBuilder != widget.errorBuilder) {
_onError(_lastError);
} else if (oldWidget.builder != widget.builder) {
_onEvent(_lastEvent);
}
if (oldWidget.stream != widget.stream) {
_subscription?.cancel();
_subscription = widget.stream?.listen(
_onEvent,
onError: _onError,
);
}
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
void _onError(error) {
if (widget.errorBuilder != null && error != _lastError) {
setState(() {
_child = widget.errorBuilder!(context, error);
});
_lastError = error;
}
}
void _onEvent(event) {
_lastError = null;
if (widget.comparator != null
? widget.comparator!(_lastEvent, event)
: event != _lastEvent) {
setState(() {
_child = widget.builder(context, event);
});
_lastEvent = event;
}
}
}
@@ -1,9 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/src/better_stream_builder.dart';
import 'package:stream_chat_flutter_core/src/stream_channel.dart'; import 'package:stream_chat_flutter_core/src/stream_channel.dart';
import 'package:stream_chat_flutter_core/src/typedef.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart';
@@ -127,6 +129,10 @@ class MessageListCoreState extends State<MessageListCore> {
.map((threads) => threads[widget.parentMessage!.id]) .map((threads) => threads[widget.parentMessage!.id])
: _streamChannel!.channel.state?.messagesStream; : _streamChannel!.channel.state?.messagesStream;
final initialData = _isThreadConversation
? _streamChannel!.channel.state?.threads[widget.parentMessage!.id]
: _streamChannel!.channel.state?.messages;
bool defaultFilter(Message m) { bool defaultFilter(Message m) {
final isMyMessage = m.user?.id == _currentUser?.id; final isMyMessage = m.user?.id == _currentUser?.id;
final isDeletedOrShadowed = m.isDeleted == true || m.shadowed == true; final isDeletedOrShadowed = m.isDeleted == true || m.shadowed == true;
@@ -134,30 +140,27 @@ class MessageListCoreState extends State<MessageListCore> {
return true; return true;
} }
return StreamBuilder<List<Message>?>( return BetterStreamBuilder<List<Message>?>(
stream: messagesStream?.map( initialData: initialData,
comparator: const ListEquality().equals,
stream: messagesStream!.map(
(messages) => (messages) =>
messages?.where(widget.messageFilter ?? defaultFilter).toList( messages?.where(widget.messageFilter ?? defaultFilter).toList(
growable: false, growable: false,
), ),
), ),
errorBuilder: widget.errorWidgetBuilder,
loadingBuilder: widget.loadingBuilder,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { final messageList = snapshot?.reversed.toList(growable: false) ?? [];
return widget.errorWidgetBuilder(context, snapshot.error!); if (messageList.isEmpty && !_isThreadConversation) {
} else if (!snapshot.hasData) { if (_upToDate) {
return widget.loadingBuilder(context); return widget.emptyBuilder(context);
} else {
final messageList =
snapshot.data?.reversed.toList(growable: false) ?? [];
if (messageList.isEmpty && !_isThreadConversation) {
if (_upToDate) {
return widget.emptyBuilder(context);
}
} else {
_messages = messageList;
} }
return widget.messageListBuilder(context, _messages); } else {
_messages = messageList;
} }
return widget.messageListBuilder(context, _messages);
}, },
); );
} }
@@ -2,6 +2,7 @@ library stream_chat_flutter_core;
export 'package:stream_chat/stream_chat.dart'; export 'package:stream_chat/stream_chat.dart';
export 'src/better_stream_builder.dart';
export 'src/channel_list_core.dart' hide ChannelListCoreState; export 'src/channel_list_core.dart' hide ChannelListCoreState;
export 'src/channels_bloc.dart'; export 'src/channels_bloc.dart';
export 'src/lazy_load_scroll_view.dart'; export 'src/lazy_load_scroll_view.dart';