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:
xsahil03x
2021-07-21 15:26:06 +05:30
46 changed files with 2552 additions and 301 deletions
@@ -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) {