Merge remote-tracking branch 'origin/develop' into feat/localization
# Conflicts: # packages/stream_chat_flutter/lib/src/message_list_view.dart # packages/stream_chat_flutter/lib/src/message_search_list_view.dart
This commit is contained in:
@@ -232,9 +232,12 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
);
|
||||
}
|
||||
|
||||
return LazyLoadScrollView(
|
||||
onEndOfPage: () => _channelListController.paginateData!(),
|
||||
child: child,
|
||||
return ColoredBox(
|
||||
color: ChannelListViewTheme.of(context).backgroundColor!,
|
||||
child: LazyLoadScrollView(
|
||||
onEndOfPage: () => _channelListController.paginateData!(),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -571,18 +574,13 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
},
|
||||
),
|
||||
],
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: chatThemeData.colorTheme.appBg,
|
||||
),
|
||||
child: widget.channelPreviewBuilder?.call(context, channel) ??
|
||||
ChannelPreview(
|
||||
onLongPress: widget.onChannelLongPress,
|
||||
channel: channel,
|
||||
onImageTap: () => widget.onImageTap?.call(channel),
|
||||
onTap: (channel) => onTap(channel, widget.channelWidget),
|
||||
),
|
||||
),
|
||||
child: widget.channelPreviewBuilder?.call(context, channel) ??
|
||||
ChannelPreview(
|
||||
onLongPress: widget.onChannelLongPress,
|
||||
channel: channel,
|
||||
onImageTap: () => widget.onImageTap?.call(channel),
|
||||
onTap: (channel) => onTap(channel, widget.channelWidget),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -387,203 +387,209 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
1 // parent message
|
||||
;
|
||||
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
ConnectionStatusBuilder(
|
||||
statusBuilder: (context, status) {
|
||||
var statusString = '';
|
||||
var showStatus = true;
|
||||
switch (status) {
|
||||
case ConnectionStatus.connected:
|
||||
statusString = context.translations.connectedLabel;
|
||||
showStatus = false;
|
||||
break;
|
||||
case ConnectionStatus.connecting:
|
||||
statusString = context.translations.reconnectingLabel;
|
||||
break;
|
||||
case ConnectionStatus.disconnected:
|
||||
statusString = context.translations.disconnectedLabel;
|
||||
break;
|
||||
}
|
||||
return ColoredBox(
|
||||
color: MessageListViewTheme.of(context).backgroundColor!,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
ConnectionStatusBuilder(
|
||||
statusBuilder: (context, status) {
|
||||
var statusString = '';
|
||||
var showStatus = true;
|
||||
switch (status) {
|
||||
case ConnectionStatus.connected:
|
||||
statusString = context.translations.connectedLabel;
|
||||
showStatus = false;
|
||||
break;
|
||||
case ConnectionStatus.connecting:
|
||||
statusString = context.translations.reconnectingLabel;
|
||||
break;
|
||||
case ConnectionStatus.disconnected:
|
||||
statusString = context.translations.disconnectedLabel;
|
||||
break;
|
||||
}
|
||||
|
||||
return InfoTile(
|
||||
showMessage: widget.showConnectionStateTile && showStatus,
|
||||
tileAnchor: Alignment.topCenter,
|
||||
childAnchor: Alignment.topCenter,
|
||||
message: statusString,
|
||||
child: LazyLoadScrollView(
|
||||
onPageScrollStart: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
onStartOfPage: () async {
|
||||
_inBetweenList = false;
|
||||
if (!_upToDate) {
|
||||
_topPaginationActive = false;
|
||||
_bottomPaginationActive = true;
|
||||
return _paginateData(
|
||||
streamChannel,
|
||||
QueryDirection.bottom,
|
||||
);
|
||||
}
|
||||
},
|
||||
onEndOfPage: () async {
|
||||
_inBetweenList = false;
|
||||
_topPaginationActive = true;
|
||||
_bottomPaginationActive = false;
|
||||
return _paginateData(
|
||||
streamChannel,
|
||||
QueryDirection.top,
|
||||
);
|
||||
},
|
||||
onInBetweenOfPage: () {
|
||||
_inBetweenList = true;
|
||||
},
|
||||
child: ScrollablePositionedList.separated(
|
||||
key: ValueKey(initialIndex! + initialAlignment!),
|
||||
itemPositionsListener: _itemPositionListener,
|
||||
initialScrollIndex: initialIndex ?? 0,
|
||||
initialAlignment: initialAlignment ?? 0,
|
||||
physics: widget.scrollPhysics,
|
||||
itemScrollController: _scrollController,
|
||||
reverse: true,
|
||||
addAutomaticKeepAlives: false,
|
||||
itemCount: itemCount,
|
||||
|
||||
// Item Count -> 8 (1 parent, 2 header+footer, 2 top+bottom, 3 messages)
|
||||
// eg: |Type| rev(|Index(item)|) rev(|Index(separator)|) |Index(item)| |Index(separator)|
|
||||
// ParentMessage -> 7 (count-1)
|
||||
// Separator(ThreadSeparator) -> 6 (count-2)
|
||||
// Header -> 6 (count-2)
|
||||
// Separator(Header -> 8??T -> 0||52) -> 5 (count-3)
|
||||
// TopLoader -> 5 (count-3)
|
||||
// Separator(0) -> 4 (count-4)
|
||||
// Message -> 4 (count-4)
|
||||
// Separator(2||8) -> 3 (count-5)
|
||||
// Message -> 3 (count-5)
|
||||
// Separator(2||8) -> 2 (count-6)
|
||||
// Message -> 2 (count-6)
|
||||
// Separator(0) -> 1 (count-7)
|
||||
// BottomLoader -> 1 (count-7)
|
||||
// Separator(Footer -> 8??30) -> 0 (count-8)
|
||||
// Footer -> 0 (count-8)
|
||||
|
||||
separatorBuilder: (context, i) {
|
||||
if (i == itemCount - 2) {
|
||||
if (widget.parentMessage == null) {
|
||||
return const Offstage();
|
||||
}
|
||||
return _buildThreadSeparator();
|
||||
}
|
||||
if (i == itemCount - 3) {
|
||||
if (widget.headerBuilder == null) {
|
||||
if (_isThreadConversation) return const Offstage();
|
||||
return const SizedBox(height: 52);
|
||||
}
|
||||
return const SizedBox(height: 8);
|
||||
}
|
||||
if (i == 0) {
|
||||
if (widget.footerBuilder == null) {
|
||||
return const SizedBox(height: 30);
|
||||
}
|
||||
return const SizedBox(height: 8);
|
||||
}
|
||||
|
||||
if (i == 1 || i == itemCount - 4) return const Offstage();
|
||||
|
||||
final message = messages[i - 1];
|
||||
final nextMessage = messages[i - 2];
|
||||
if (!Jiffy(message.createdAt.toLocal()).isSame(
|
||||
nextMessage.createdAt.toLocal(),
|
||||
Units.DAY,
|
||||
)) {
|
||||
final divider = widget.dateDividerBuilder != null
|
||||
? widget.dateDividerBuilder!(
|
||||
nextMessage.createdAt.toLocal(),
|
||||
)
|
||||
: DateDivider(
|
||||
dateTime: nextMessage.createdAt.toLocal(),
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: divider,
|
||||
);
|
||||
}
|
||||
final timeDiff =
|
||||
Jiffy(nextMessage.createdAt.toLocal()).diff(
|
||||
message.createdAt.toLocal(),
|
||||
Units.MINUTE,
|
||||
);
|
||||
|
||||
final isNextUserSame =
|
||||
message.user!.id == nextMessage.user?.id;
|
||||
final isThread = message.replyCount! > 0;
|
||||
final isDeleted = message.isDeleted;
|
||||
if (timeDiff >= 1 ||
|
||||
!isNextUserSame ||
|
||||
isThread ||
|
||||
isDeleted) {
|
||||
return const SizedBox(height: 8);
|
||||
}
|
||||
return const SizedBox(height: 2);
|
||||
return InfoTile(
|
||||
showMessage: widget.showConnectionStateTile && showStatus,
|
||||
tileAnchor: Alignment.topCenter,
|
||||
childAnchor: Alignment.topCenter,
|
||||
message: statusString,
|
||||
child: LazyLoadScrollView(
|
||||
onPageScrollStart: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
itemBuilder: (context, i) {
|
||||
if (i == itemCount - 1) {
|
||||
if (widget.parentMessage == null) return const Offstage();
|
||||
return buildParentMessage(widget.parentMessage!);
|
||||
}
|
||||
|
||||
if (i == itemCount - 2) {
|
||||
return widget.headerBuilder?.call(context) ??
|
||||
const Offstage();
|
||||
}
|
||||
|
||||
if (i == itemCount - 3) {
|
||||
return _buildLoadingIndicator(
|
||||
streamChannel!,
|
||||
QueryDirection.top,
|
||||
);
|
||||
}
|
||||
|
||||
if (i == 1) {
|
||||
return _buildLoadingIndicator(
|
||||
streamChannel!,
|
||||
onStartOfPage: () async {
|
||||
_inBetweenList = false;
|
||||
if (!_upToDate) {
|
||||
_topPaginationActive = false;
|
||||
_bottomPaginationActive = true;
|
||||
return _paginateData(
|
||||
streamChannel,
|
||||
QueryDirection.bottom,
|
||||
);
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
return widget.footerBuilder?.call(context) ??
|
||||
const Offstage();
|
||||
}
|
||||
|
||||
const bottomMessageIndex = 2; // 1 -> loader // 0 -> footer
|
||||
|
||||
final message = messages[i - 2];
|
||||
Widget messageWidget;
|
||||
|
||||
if (i == bottomMessageIndex) {
|
||||
messageWidget = _buildBottomMessage(
|
||||
context,
|
||||
message,
|
||||
messages,
|
||||
streamChannel!,
|
||||
i - 2,
|
||||
);
|
||||
} else {
|
||||
messageWidget = buildMessage(message, messages, i - 2);
|
||||
}
|
||||
return messageWidget;
|
||||
},
|
||||
onEndOfPage: () async {
|
||||
_inBetweenList = false;
|
||||
_topPaginationActive = true;
|
||||
_bottomPaginationActive = false;
|
||||
return _paginateData(
|
||||
streamChannel,
|
||||
QueryDirection.top,
|
||||
);
|
||||
},
|
||||
onInBetweenOfPage: () {
|
||||
_inBetweenList = true;
|
||||
},
|
||||
child: ScrollablePositionedList.separated(
|
||||
key: ValueKey(initialIndex! + initialAlignment!),
|
||||
itemPositionsListener: _itemPositionListener,
|
||||
initialScrollIndex: initialIndex ?? 0,
|
||||
initialAlignment: initialAlignment ?? 0,
|
||||
physics: widget.scrollPhysics,
|
||||
itemScrollController: _scrollController,
|
||||
reverse: true,
|
||||
addAutomaticKeepAlives: false,
|
||||
itemCount: itemCount,
|
||||
|
||||
// Item Count -> 8 (1 parent, 2 header+footer, 2 top+bottom, 3 messages)
|
||||
// eg: |Type| rev(|Index(item)|) rev(|Index(separator)|) |Index(item)| |Index(separator)|
|
||||
// ParentMessage -> 7 (count-1)
|
||||
// Separator(ThreadSeparator) -> 6 (count-2)
|
||||
// Header -> 6 (count-2)
|
||||
// Separator(Header -> 8??T -> 0||52) -> 5 (count-3)
|
||||
// TopLoader -> 5 (count-3)
|
||||
// Separator(0) -> 4 (count-4)
|
||||
// Message -> 4 (count-4)
|
||||
// Separator(2||8) -> 3 (count-5)
|
||||
// Message -> 3 (count-5)
|
||||
// Separator(2||8) -> 2 (count-6)
|
||||
// Message -> 2 (count-6)
|
||||
// Separator(0) -> 1 (count-7)
|
||||
// BottomLoader -> 1 (count-7)
|
||||
// Separator(Footer -> 8??30) -> 0 (count-8)
|
||||
// Footer -> 0 (count-8)
|
||||
|
||||
separatorBuilder: (context, i) {
|
||||
if (i == itemCount - 2) {
|
||||
if (widget.parentMessage == null) {
|
||||
return const Offstage();
|
||||
}
|
||||
return _buildThreadSeparator();
|
||||
}
|
||||
if (i == itemCount - 3) {
|
||||
if (widget.headerBuilder == null) {
|
||||
if (_isThreadConversation) return const Offstage();
|
||||
return const SizedBox(height: 52);
|
||||
}
|
||||
return const SizedBox(height: 8);
|
||||
}
|
||||
if (i == 0) {
|
||||
if (widget.footerBuilder == null) {
|
||||
return const SizedBox(height: 30);
|
||||
}
|
||||
return const SizedBox(height: 8);
|
||||
}
|
||||
|
||||
if (i == 1 || i == itemCount - 4) return const Offstage();
|
||||
|
||||
final message = messages[i - 1];
|
||||
final nextMessage = messages[i - 2];
|
||||
if (!Jiffy(message.createdAt.toLocal()).isSame(
|
||||
nextMessage.createdAt.toLocal(),
|
||||
Units.DAY,
|
||||
)) {
|
||||
final divider = widget.dateDividerBuilder != null
|
||||
? widget.dateDividerBuilder!(
|
||||
nextMessage.createdAt.toLocal(),
|
||||
)
|
||||
: DateDivider(
|
||||
dateTime: nextMessage.createdAt.toLocal(),
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: divider,
|
||||
);
|
||||
}
|
||||
final timeDiff =
|
||||
Jiffy(nextMessage.createdAt.toLocal()).diff(
|
||||
message.createdAt.toLocal(),
|
||||
Units.MINUTE,
|
||||
);
|
||||
|
||||
final isNextUserSame =
|
||||
message.user!.id == nextMessage.user?.id;
|
||||
final isThread = message.replyCount! > 0;
|
||||
final isDeleted = message.isDeleted;
|
||||
if (timeDiff >= 1 ||
|
||||
!isNextUserSame ||
|
||||
isThread ||
|
||||
isDeleted) {
|
||||
return const SizedBox(height: 8);
|
||||
}
|
||||
return const SizedBox(height: 2);
|
||||
},
|
||||
itemBuilder: (context, i) {
|
||||
if (i == itemCount - 1) {
|
||||
if (widget.parentMessage == null) {
|
||||
return const Offstage();
|
||||
}
|
||||
return buildParentMessage(widget.parentMessage!);
|
||||
}
|
||||
|
||||
if (i == itemCount - 2) {
|
||||
return widget.headerBuilder?.call(context) ??
|
||||
const Offstage();
|
||||
}
|
||||
|
||||
if (i == itemCount - 3) {
|
||||
return _buildLoadingIndicator(
|
||||
streamChannel!,
|
||||
QueryDirection.top,
|
||||
);
|
||||
}
|
||||
|
||||
if (i == 1) {
|
||||
return _buildLoadingIndicator(
|
||||
streamChannel!,
|
||||
QueryDirection.bottom,
|
||||
);
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
return widget.footerBuilder?.call(context) ??
|
||||
const Offstage();
|
||||
}
|
||||
|
||||
const bottomMessageIndex =
|
||||
2; // 1 -> loader // 0 -> footer
|
||||
|
||||
final message = messages[i - 2];
|
||||
Widget messageWidget;
|
||||
|
||||
if (i == bottomMessageIndex) {
|
||||
messageWidget = _buildBottomMessage(
|
||||
context,
|
||||
message,
|
||||
messages,
|
||||
streamChannel!,
|
||||
i - 2,
|
||||
);
|
||||
} else {
|
||||
messageWidget = buildMessage(message, messages, i - 2);
|
||||
}
|
||||
return messageWidget;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (widget.showScrollToBottom) _buildScrollToBottom(),
|
||||
if (widget.showFloatingDateDivider)
|
||||
_buildFloatingDateDivider(itemCount),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
if (widget.showScrollToBottom) _buildScrollToBottom(),
|
||||
if (widget.showFloatingDateDivider)
|
||||
_buildFloatingDateDivider(itemCount),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -610,6 +616,8 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
|
||||
Positioned _buildFloatingDateDivider(int itemCount) => Positioned(
|
||||
top: 20,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: BetterStreamBuilder<Iterable<ItemPosition>>(
|
||||
initialData: _itemPositionListener.itemPositions.value,
|
||||
stream: _itemPositionStream,
|
||||
|
||||
@@ -146,57 +146,60 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
||||
widget.messageSearchListController ?? _defaultController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => MessageSearchListCore(
|
||||
filters: widget.filters,
|
||||
sortOptions: widget.sortOptions,
|
||||
messageQuery: widget.messageQuery,
|
||||
paginationParams: widget.paginationParams,
|
||||
messageFilters: widget.messageFilters,
|
||||
messageSearchListController: _messageSearchListController,
|
||||
emptyBuilder: widget.emptyBuilder ??
|
||||
(context) => LayoutBuilder(
|
||||
builder: (context, viewportConstraints) =>
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(context.translations.emptyMessagesText),
|
||||
Widget build(BuildContext context) => ColoredBox(
|
||||
color: MessageSearchListViewTheme.of(context).backgroundColor!,
|
||||
child: MessageSearchListCore(
|
||||
filters: widget.filters,
|
||||
sortOptions: widget.sortOptions,
|
||||
messageQuery: widget.messageQuery,
|
||||
paginationParams: widget.paginationParams,
|
||||
messageFilters: widget.messageFilters,
|
||||
messageSearchListController: _messageSearchListController,
|
||||
emptyBuilder: widget.emptyBuilder ??
|
||||
(context) => LayoutBuilder(
|
||||
builder: (context, viewportConstraints) =>
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(context.translations.emptyMessagesText),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
errorBuilder: widget.errorBuilder ??
|
||||
(BuildContext context, dynamic error) {
|
||||
if (error is Error) {
|
||||
print(error.stackTrace);
|
||||
}
|
||||
return InfoTile(
|
||||
showMessage: widget.showErrorTile,
|
||||
tileAnchor: Alignment.topCenter,
|
||||
childAnchor: Alignment.topCenter,
|
||||
message: context.translations.genericErrorText,
|
||||
child: Container(),
|
||||
);
|
||||
},
|
||||
loadingBuilder: widget.loadingBuilder ??
|
||||
(context) => LayoutBuilder(
|
||||
builder: (context, viewportConstraints) =>
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
errorBuilder: widget.errorBuilder ??
|
||||
(BuildContext context, dynamic error) {
|
||||
if (error is Error) {
|
||||
print(error.stackTrace);
|
||||
}
|
||||
return InfoTile(
|
||||
showMessage: widget.showErrorTile,
|
||||
tileAnchor: Alignment.topCenter,
|
||||
childAnchor: Alignment.topCenter,
|
||||
message: context.translations.genericErrorText,
|
||||
child: Container(),
|
||||
);
|
||||
},
|
||||
loadingBuilder: widget.loadingBuilder ??
|
||||
(context) => LayoutBuilder(
|
||||
builder: (context, viewportConstraints) =>
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
childBuilder: widget.childBuilder ?? _buildListView,
|
||||
childBuilder: widget.childBuilder ?? _buildListView,
|
||||
),
|
||||
);
|
||||
|
||||
Widget _separatorBuilder(BuildContext context, int index) => Container(
|
||||
|
||||
@@ -102,7 +102,6 @@ class StreamChatState extends State<StreamChat> {
|
||||
data: materialTheme.copyWith(
|
||||
primaryIconTheme: streamTheme.primaryIconTheme,
|
||||
accentColor: streamTheme.colorTheme.accentPrimary,
|
||||
scaffoldBackgroundColor: streamTheme.colorTheme.barsBg,
|
||||
),
|
||||
child: StreamChatCore(
|
||||
client: client,
|
||||
|
||||
@@ -60,6 +60,10 @@ class StreamChatThemeData {
|
||||
List<ReactionIcon>? reactionIcons,
|
||||
GalleryHeaderThemeData? imageHeaderTheme,
|
||||
GalleryFooterThemeData? imageFooterTheme,
|
||||
MessageListViewThemeData? messageListViewTheme,
|
||||
ChannelListViewThemeData? channelListViewTheme,
|
||||
UserListViewThemeData? userListViewTheme,
|
||||
MessageSearchListViewThemeData? messageSearchListViewTheme,
|
||||
}) {
|
||||
brightness ??= colorTheme?.brightness ?? Brightness.light;
|
||||
final isDark = brightness == Brightness.dark;
|
||||
@@ -83,6 +87,10 @@ class StreamChatThemeData {
|
||||
reactionIcons: reactionIcons,
|
||||
galleryHeaderTheme: imageHeaderTheme,
|
||||
galleryFooterTheme: imageFooterTheme,
|
||||
messageListViewTheme: messageListViewTheme,
|
||||
channelListViewTheme: channelListViewTheme,
|
||||
userListViewTheme: userListViewTheme,
|
||||
messageSearchListViewTheme: messageSearchListViewTheme,
|
||||
);
|
||||
|
||||
return defaultData.merge(customizedData);
|
||||
@@ -111,6 +119,10 @@ class StreamChatThemeData {
|
||||
required this.reactionIcons,
|
||||
required this.galleryHeaderTheme,
|
||||
required this.galleryFooterTheme,
|
||||
required this.messageListViewTheme,
|
||||
required this.channelListViewTheme,
|
||||
required this.userListViewTheme,
|
||||
required this.messageSearchListViewTheme,
|
||||
});
|
||||
|
||||
/// Create a theme from a Material [Theme]
|
||||
@@ -166,6 +178,18 @@ class StreamChatThemeData {
|
||||
/// Assets used for rendering reactions
|
||||
final List<ReactionIcon> reactionIcons;
|
||||
|
||||
/// Theme configuration for the [MessageListView] widget.
|
||||
final MessageListViewThemeData messageListViewTheme;
|
||||
|
||||
/// Theme configuration for the [ChannelListView] widget.
|
||||
final ChannelListViewThemeData channelListViewTheme;
|
||||
|
||||
/// Theme configuration for the [UserListView] widget.
|
||||
final UserListViewThemeData userListViewTheme;
|
||||
|
||||
/// Theme configuration for the [] widget.
|
||||
final MessageSearchListViewThemeData messageSearchListViewTheme;
|
||||
|
||||
/// Creates a copy of [StreamChatThemeData] with specified attributes
|
||||
/// overridden.
|
||||
StreamChatThemeData copyWith({
|
||||
@@ -182,6 +206,10 @@ class StreamChatThemeData {
|
||||
List<ReactionIcon>? reactionIcons,
|
||||
GalleryHeaderThemeData? galleryHeaderTheme,
|
||||
GalleryFooterThemeData? galleryFooterTheme,
|
||||
MessageListViewThemeData? messageListViewTheme,
|
||||
ChannelListViewThemeData? channelListViewTheme,
|
||||
UserListViewThemeData? userListViewTheme,
|
||||
MessageSearchListViewThemeData? messageSearchListViewTheme,
|
||||
}) =>
|
||||
StreamChatThemeData.raw(
|
||||
channelListHeaderTheme:
|
||||
@@ -199,6 +227,11 @@ class StreamChatThemeData {
|
||||
reactionIcons: reactionIcons ?? this.reactionIcons,
|
||||
galleryHeaderTheme: galleryHeaderTheme ?? this.galleryHeaderTheme,
|
||||
galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme,
|
||||
messageListViewTheme: messageListViewTheme ?? this.messageListViewTheme,
|
||||
channelListViewTheme: channelListViewTheme ?? this.channelListViewTheme,
|
||||
userListViewTheme: userListViewTheme ?? this.userListViewTheme,
|
||||
messageSearchListViewTheme:
|
||||
messageSearchListViewTheme ?? this.messageSearchListViewTheme,
|
||||
);
|
||||
|
||||
/// Merge themes
|
||||
@@ -219,6 +252,13 @@ class StreamChatThemeData {
|
||||
reactionIcons: other.reactionIcons,
|
||||
galleryHeaderTheme: galleryHeaderTheme.merge(other.galleryHeaderTheme),
|
||||
galleryFooterTheme: galleryFooterTheme.merge(other.galleryFooterTheme),
|
||||
messageListViewTheme:
|
||||
messageListViewTheme.merge(other.messageListViewTheme),
|
||||
channelListViewTheme:
|
||||
channelListViewTheme.merge(other.channelListViewTheme),
|
||||
userListViewTheme: userListViewTheme.merge(other.userListViewTheme),
|
||||
messageSearchListViewTheme:
|
||||
messageSearchListViewTheme.merge(other.messageSearchListViewTheme),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -438,6 +478,18 @@ class StreamChatThemeData {
|
||||
bottomSheetPhotosTextStyle: textTheme.headlineBold,
|
||||
bottomSheetCloseIconColor: colorTheme.textHighEmphasis,
|
||||
),
|
||||
messageListViewTheme: MessageListViewThemeData(
|
||||
backgroundColor: colorTheme.barsBg,
|
||||
),
|
||||
channelListViewTheme: ChannelListViewThemeData(
|
||||
backgroundColor: colorTheme.appBg,
|
||||
),
|
||||
userListViewTheme: UserListViewThemeData(
|
||||
backgroundColor: colorTheme.appBg,
|
||||
),
|
||||
messageSearchListViewTheme: MessageSearchListViewThemeData(
|
||||
backgroundColor: colorTheme.appBg,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1651,7 +1703,7 @@ class GalleryFooterThemeData with Diagnosticable {
|
||||
a.bottomSheetCloseIconColor, b.bottomSheetCloseIconColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [GalleryFooterThemeData] with the another
|
||||
/// Merges one [GalleryFooterThemeData] with another.
|
||||
GalleryFooterThemeData merge(GalleryFooterThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
@@ -1708,3 +1760,436 @@ class GalleryFooterThemeData with Diagnosticable {
|
||||
'bottomSheetCloseIconColor', bottomSheetCloseIconColor));
|
||||
}
|
||||
}
|
||||
|
||||
/// Overrides the default style of [MessageListView] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MessageListViewThemeData], which is used to configure this theme.
|
||||
class MessageListViewTheme extends InheritedTheme {
|
||||
/// Creates a [MessageListViewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const MessageListViewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final MessageListViewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [MessageListViewTheme] widget, then
|
||||
/// [StreamChatThemeData.messageListViewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// MessageListViewTheme theme = MessageListViewTheme.of(context);
|
||||
/// ```
|
||||
static MessageListViewThemeData of(BuildContext context) {
|
||||
final messageListViewTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<MessageListViewTheme>();
|
||||
return messageListViewTheme?.data ??
|
||||
StreamChatTheme.of(context).messageListViewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
MessageListViewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(MessageListViewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// A style that overrides the default appearance of [MessageListView]s when
|
||||
/// used with [MessageListViewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.messageListViewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MessageListViewTheme], the theme which is configured with this class.
|
||||
/// * [StreamChatThemeData.messageListViewTheme], which can be used to override
|
||||
/// the default style for [MessageListView]s below the overall
|
||||
/// [StreamChatTheme].
|
||||
class MessageListViewThemeData with Diagnosticable {
|
||||
/// Creates a [MessageListViewThemeData].
|
||||
const MessageListViewThemeData({
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// The color of the [MessageListView] background.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Copies this [MessageListViewThemeData] to another.
|
||||
MessageListViewThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
}) =>
|
||||
MessageListViewThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [MessageListView] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
MessageListViewThemeData lerp(
|
||||
MessageListViewThemeData a,
|
||||
MessageListViewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
MessageListViewThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [MessageListViewThemeData] with another.
|
||||
MessageListViewThemeData merge(MessageListViewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MessageListViewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor;
|
||||
|
||||
@override
|
||||
int get hashCode => backgroundColor.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('backgroundColor', backgroundColor));
|
||||
}
|
||||
}
|
||||
|
||||
/// Overrides the default style of [ChannelListView] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelListViewThemeData], which is used to configure this theme.
|
||||
class ChannelListViewTheme extends InheritedTheme {
|
||||
/// Creates a [ChannelListViewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const ChannelListViewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final ChannelListViewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [ChannelListViewTheme] widget, then
|
||||
/// [StreamChatThemeData.channelListViewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// ChannelListViewTheme theme = ChannelListViewTheme.of(context);
|
||||
/// ```
|
||||
static ChannelListViewThemeData of(BuildContext context) {
|
||||
final channelListViewTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<ChannelListViewTheme>();
|
||||
return channelListViewTheme?.data ??
|
||||
StreamChatTheme.of(context).channelListViewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
ChannelListViewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ChannelListViewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// A style that overrides the default appearance of [ChannelListView]s when
|
||||
/// used with [ChannelListViewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.channelListViewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelListViewTheme], the theme which is configured with this class.
|
||||
/// * [StreamChatThemeData.channelListViewTheme], which can be used to override
|
||||
/// the default style for [ChannelListView]s below the overall
|
||||
/// [StreamChatTheme].
|
||||
class ChannelListViewThemeData with Diagnosticable {
|
||||
/// Creates a [ChannelListViewThemeData].
|
||||
const ChannelListViewThemeData({
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// The color of the [ChannelListView] background.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Copies this [ChannelListViewThemeData] to another.
|
||||
ChannelListViewThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
}) =>
|
||||
ChannelListViewThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [ChannelListViewThemeData] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
ChannelListViewThemeData lerp(
|
||||
ChannelListViewThemeData a,
|
||||
ChannelListViewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
ChannelListViewThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [ChannelListViewThemeData] with another.
|
||||
ChannelListViewThemeData merge(ChannelListViewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ChannelListViewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor;
|
||||
|
||||
@override
|
||||
int get hashCode => backgroundColor.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('backgroundColor', backgroundColor));
|
||||
}
|
||||
}
|
||||
|
||||
/// Overrides the default style of [UserListView] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [UserListViewThemeData], which is used to configure this theme.
|
||||
class UserListViewTheme extends InheritedTheme {
|
||||
/// Creates a [UserListViewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const UserListViewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final UserListViewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [UserListViewTheme] widget, then
|
||||
/// [StreamChatThemeData.userListViewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// UserListViewTheme theme = UserListViewTheme.of(context);
|
||||
/// ```
|
||||
static UserListViewThemeData of(BuildContext context) {
|
||||
final userListViewTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<UserListViewTheme>();
|
||||
return userListViewTheme?.data ??
|
||||
StreamChatTheme.of(context).userListViewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
UserListViewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(UserListViewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// A style that overrides the default appearance of [UserListView]s when
|
||||
/// used with [UserListViewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.userListViewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [UserListViewTheme], the theme which is configured with this class.
|
||||
/// * [StreamChatThemeData.userListViewTheme], which can be used to override
|
||||
/// the default style for [UserListView]s below the overall
|
||||
/// [StreamChatTheme].
|
||||
class UserListViewThemeData with Diagnosticable {
|
||||
/// Creates a [UserListViewThemeData].
|
||||
const UserListViewThemeData({
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// The color of the [ChannelListView] background.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Copies this [ChannelListViewThemeData] to another.
|
||||
UserListViewThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
}) =>
|
||||
UserListViewThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [UserListViewThemeData] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
UserListViewThemeData lerp(
|
||||
UserListViewThemeData a,
|
||||
UserListViewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
UserListViewThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [UserListViewThemeData] with another.
|
||||
UserListViewThemeData merge(UserListViewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is UserListViewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor;
|
||||
|
||||
@override
|
||||
int get hashCode => backgroundColor.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('backgroundColor', backgroundColor));
|
||||
}
|
||||
}
|
||||
|
||||
/// Overrides the default style of [MessageSearchListView] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [UserListViewThemeData], which is used to configure this theme.
|
||||
class MessageSearchListViewTheme extends InheritedTheme {
|
||||
/// Creates a [UserListViewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const MessageSearchListViewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final MessageSearchListViewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [MessageSearchListView] widget, then
|
||||
/// [StreamChatThemeData.messageSearchListViewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// MessageSearchListViewTheme theme = MessageSearchListViewTheme.of(context);
|
||||
/// ```
|
||||
static MessageSearchListViewThemeData of(BuildContext context) {
|
||||
final messageSearchListViewTheme = context
|
||||
.dependOnInheritedWidgetOfExactType<MessageSearchListViewTheme>();
|
||||
return messageSearchListViewTheme?.data ??
|
||||
StreamChatTheme.of(context).messageSearchListViewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
MessageSearchListViewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(MessageSearchListViewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// A style that overrides the default appearance of [MessageSearchListView]s
|
||||
/// when used with [MessageSearchListView] or with the overall
|
||||
/// [StreamChatTheme]'s [StreamChatThemeData.messageSearchListViewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MessageSearchListViewTheme], the theme which is configured with this
|
||||
/// class.
|
||||
/// * [StreamChatThemeData.messageSearchListViewTheme], which can be used to
|
||||
/// override the default style for [UserListView]s below the overall
|
||||
/// [StreamChatTheme].
|
||||
class MessageSearchListViewThemeData with Diagnosticable {
|
||||
/// Creates a [MessageSearchListViewThemeData].
|
||||
const MessageSearchListViewThemeData({
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// The color of the [MessageSearchListView] background.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Copies this [MessageSearchListViewThemeData] to another.
|
||||
MessageSearchListViewThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
}) =>
|
||||
MessageSearchListViewThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [UserListViewThemeData] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
MessageSearchListViewThemeData lerp(
|
||||
MessageSearchListViewThemeData a,
|
||||
MessageSearchListViewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
MessageSearchListViewThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [MessageSearchListViewThemeData] with another.
|
||||
MessageSearchListViewThemeData merge(MessageSearchListViewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MessageSearchListViewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor;
|
||||
|
||||
@override
|
||||
int get hashCode => backgroundColor.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('backgroundColor', backgroundColor));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,33 +162,36 @@ class _UserListViewState extends State<UserListView>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final child = UserListCore(
|
||||
errorBuilder: widget.errorBuilder ??
|
||||
(BuildContext context, Object err) => _buildError(err),
|
||||
emptyBuilder: widget.emptyBuilder ?? (context) => _buildEmpty(),
|
||||
loadingBuilder: widget.loadingBuilder ??
|
||||
(context) => LayoutBuilder(
|
||||
builder: (context, viewportConstraints) =>
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
final child = ColoredBox(
|
||||
color: UserListViewTheme.of(context).backgroundColor!,
|
||||
child: UserListCore(
|
||||
errorBuilder: widget.errorBuilder ??
|
||||
(BuildContext context, Object err) => _buildError(err),
|
||||
emptyBuilder: widget.emptyBuilder ?? (context) => _buildEmpty(),
|
||||
loadingBuilder: widget.loadingBuilder ??
|
||||
(context) => LayoutBuilder(
|
||||
builder: (context, viewportConstraints) =>
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
listBuilder:
|
||||
widget.listBuilder ?? (context, list) => _buildListView(list),
|
||||
pagination: widget.pagination,
|
||||
sort: widget.sort,
|
||||
filter: widget.filter,
|
||||
presence: widget.presence,
|
||||
groupAlphabetically: widget.groupAlphabetically,
|
||||
userListController: _userListController,
|
||||
listBuilder:
|
||||
widget.listBuilder ?? (context, list) => _buildListView(list),
|
||||
pagination: widget.pagination,
|
||||
sort: widget.sort,
|
||||
filter: widget.filter,
|
||||
presence: widget.presence,
|
||||
groupAlphabetically: widget.groupAlphabetically,
|
||||
userListController: _userListController,
|
||||
),
|
||||
);
|
||||
|
||||
if (!widget.pullToRefresh) {
|
||||
|
||||
Reference in New Issue
Block a user