Merge pull request #421 from GetStream/fix/ofCalls

fix: [CDS-262] move .of calls to didChangeDependencies
This commit is contained in:
Salvatore Giordano
2021-05-12 16:48:10 +02:00
committed by GitHub
49 changed files with 1840 additions and 1838 deletions
@@ -17,18 +17,22 @@ class ChannelBottomSheet extends StatefulWidget {
class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
bool _showActions = true;
late StreamChannelState _streamChannelState;
late StreamChatThemeData _streamChatThemeData;
late StreamChatState _streamChatState;
@override
Widget build(BuildContext context) {
final channel = StreamChannel.of(context).channel;
final channel = _streamChannelState.channel;
final members = channel.state?.members ?? [];
final userAsMember = members
.firstWhere((e) => e.user?.id == StreamChat.of(context).user?.id);
final userAsMember =
members.firstWhere((e) => e.user?.id == _streamChatState.user?.id);
final isOwner = userAsMember.role == 'owner';
return Material(
color: StreamChatTheme.of(context).colorTheme.white,
color: _streamChatThemeData.colorTheme.white,
clipBehavior: Clip.antiAlias,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
@@ -48,8 +52,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ChannelName(
textStyle:
StreamChatTheme.of(context).textTheme.headlineBold,
textStyle: _streamChatThemeData.textTheme.headlineBold,
),
),
),
@@ -59,10 +62,9 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
Center(
child: ChannelInfo(
showTypingIndicator: false,
channel: StreamChannel.of(context).channel,
textStyle: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle,
channel: _streamChannelState.channel,
textStyle:
_streamChatThemeData.channelPreviewTheme.subtitle,
),
),
const SizedBox(
@@ -94,8 +96,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
.user
?.name ??
'',
style:
StreamChatTheme.of(context).textTheme.footnoteBold,
style: _streamChatThemeData.textTheme.footnoteBold,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -128,9 +129,8 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
),
Text(
members[index].user?.name ?? '',
style: StreamChatTheme.of(context)
.textTheme
.footnoteBold,
style:
_streamChatThemeData.textTheme.footnoteBold,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -146,7 +146,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamSvgIcon.user(
color: StreamChatTheme.of(context).colorTheme.grey,
color: _streamChatThemeData.colorTheme.grey,
),
),
title: 'View Info',
@@ -157,7 +157,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamSvgIcon.userRemove(
color: StreamChatTheme.of(context).colorTheme.grey,
color: _streamChatThemeData.colorTheme.grey,
),
),
title: 'Leave Group',
@@ -176,12 +176,11 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed,
color: _streamChatThemeData.colorTheme.accentRed,
),
),
title: 'Delete Conversation',
titleColor:
StreamChatTheme.of(context).colorTheme.accentRed,
titleColor: _streamChatThemeData.colorTheme.accentRed,
onTap: () async {
setState(() {
_showActions = false;
@@ -196,7 +195,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamSvgIcon.closeSmall(
color: StreamChatTheme.of(context).colorTheme.grey,
color: _streamChatThemeData.colorTheme.grey,
),
),
title: 'Cancel',
@@ -209,6 +208,14 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
);
}
@override
void didChangeDependencies() {
_streamChannelState = StreamChannel.of(context);
_streamChatThemeData = StreamChatTheme.of(context);
_streamChatState = StreamChat.of(context);
super.didChangeDependencies();
}
Future<void> _showDeleteDialog() async {
final res = await showConfirmationDialog(
context,
@@ -217,10 +224,10 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
question: 'Are you sure you want to delete this conversation?',
cancelText: 'CANCEL',
icon: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed,
color: _streamChatThemeData.colorTheme.accentRed,
),
);
final channel = StreamChannel.of(context).channel;
final channel = _streamChannelState.channel;
if (res == true) {
await channel.delete();
Navigator.pop(context);
@@ -235,12 +242,12 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
question: 'Are you sure you want to leave this conversation?',
cancelText: 'CANCEL',
icon: StreamSvgIcon.userRemove(
color: StreamChatTheme.of(context).colorTheme.accentRed,
color: _streamChatThemeData.colorTheme.accentRed,
),
);
if (res == true) {
final channel = StreamChannel.of(context).channel;
final user = StreamChat.of(context).user;
final channel = _streamChannelState.channel;
final user = _streamChatState.user;
if (user != null) {
await channel.removeMembers([user.id]);
}
@@ -104,6 +104,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) {
final channel = StreamChannel.of(context).channel;
final chatThemeData = StreamChatTheme.of(context);
final leadingWidget = leading ??
(showBackButton
@@ -139,26 +140,18 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
brightness: Theme.of(context).brightness,
elevation: 1,
leading: leadingWidget,
backgroundColor: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.color,
backgroundColor:
chatThemeData.channelTheme.channelHeaderTheme.color,
actions: actions ??
<Widget>[
Padding(
padding: const EdgeInsets.only(right: 10),
child: Center(
child: ChannelImage(
borderRadius: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.avatarTheme
?.borderRadius,
constraints: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.avatarTheme
?.constraints,
borderRadius: chatThemeData.channelTheme
.channelHeaderTheme.avatarTheme?.borderRadius,
constraints: chatThemeData.channelTheme
.channelHeaderTheme.avatarTheme?.constraints,
onTap: onImageTap,
),
),
@@ -175,20 +168,16 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
children: <Widget>[
title ??
ChannelName(
textStyle: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.title,
textStyle: chatThemeData
.channelTheme.channelHeaderTheme.title,
),
const SizedBox(height: 2),
subtitle ??
ChannelInfo(
showTypingIndicator: showTypingIndicator,
channel: channel,
textStyle: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.subtitle,
textStyle: chatThemeData
.channelTheme.channelHeaderTheme.subtitle,
),
],
),
@@ -88,6 +88,7 @@ class ChannelImage extends StatelessWidget {
initialData: channel.extraData,
builder: (context, snapshot) {
String? image;
final chatThemeData = StreamChatTheme.of(context);
if (snapshot.data!.containsKey('image') == true) {
image = snapshot.data!['image'];
} else if (channel.state?.members.length == 2) {
@@ -99,20 +100,16 @@ class ChannelImage extends StatelessWidget {
initialData: otherMember!.user,
builder: (context, snapshot) => UserAvatar(
borderRadius: borderRadius ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
?.borderRadius,
chatThemeData
.channelPreviewTheme.avatarTheme?.borderRadius,
user: snapshot.data ?? otherMember.user!,
constraints: constraints ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
?.constraints,
chatThemeData
.channelPreviewTheme.avatarTheme?.constraints,
onTap: onTap != null ? (_) => onTap!() : null,
selected: selected,
selectionColor: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue,
selectionColor:
selectionColor ?? chatThemeData.colorTheme.accentBlue,
selectionThickness: selectionThickness,
));
} else {
@@ -127,37 +124,25 @@ class ChannelImage extends StatelessWidget {
return GroupImage(
images: images ?? [],
borderRadius: borderRadius ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
?.borderRadius,
chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius,
constraints: constraints ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
?.constraints,
chatThemeData.channelPreviewTheme.avatarTheme?.constraints,
onTap: onTap,
selected: selected,
selectionColor: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue,
selectionColor:
selectionColor ?? chatThemeData.colorTheme.accentBlue,
selectionThickness: selectionThickness,
);
}
Widget child = ClipRRect(
borderRadius: borderRadius ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
?.borderRadius,
chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius,
child: Container(
constraints: constraints ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
?.constraints,
chatThemeData.channelPreviewTheme.avatarTheme?.constraints,
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
color: chatThemeData.colorTheme.accentBlue,
),
child: Stack(
alignment: Alignment.center,
@@ -172,7 +157,7 @@ class ChannelImage extends StatelessWidget {
? snapshot.data!['name'][0]
: '',
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.white,
color: chatThemeData.colorTheme.white,
fontWeight: FontWeight.bold,
),
),
@@ -180,7 +165,7 @@ class ChannelImage extends StatelessWidget {
fit: BoxFit.cover,
)
else
StreamChatTheme.of(context).defaultChannelImage(
chatThemeData.defaultChannelImage(
context,
channel,
),
@@ -198,20 +183,13 @@ class ChannelImage extends StatelessWidget {
child = ClipRRect(
key: const Key('selectedImage'),
borderRadius: (borderRadius ??
StreamChatTheme.of(context)
.ownMessageTheme
.avatarTheme
?.borderRadius ??
chatThemeData.ownMessageTheme.avatarTheme?.borderRadius ??
BorderRadius.zero) +
BorderRadius.circular(selectionThickness),
child: Container(
constraints: constraints ??
StreamChatTheme.of(context)
.ownMessageTheme
.avatarTheme
?.constraints,
color: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue,
chatThemeData.ownMessageTheme.avatarTheme?.constraints,
color: selectionColor ?? chatThemeData.colorTheme.accentBlue,
child: Padding(
padding: EdgeInsets.all(selectionThickness),
child: child,
@@ -46,7 +46,9 @@ class ChannelInfo extends StatelessWidget {
}
Widget _buildConnectedTitleState(
BuildContext context, List<Member>? members) {
BuildContext context,
List<Member>? members,
) {
Widget? alternativeWidget;
if (channel.memberCount != null && channel.memberCount! > 2) {
@@ -61,8 +63,9 @@ class ChannelInfo extends StatelessWidget {
.subtitle,
);
} else {
final userId = StreamChat.of(context).user?.id;
final otherMember = members?.firstWhereOrNull(
(element) => element.userId != StreamChat.of(context).user?.id,
(element) => element.userId != userId,
);
if (otherMember != null) {
@@ -114,6 +114,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
break;
}
final chatThemeData = StreamChatTheme.of(context);
return InfoTile(
// ignore: avoid_bool_literals_in_conditional_expressions
showMessage: showConnectionStateTile ? showStatus : false,
@@ -121,8 +122,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
child: AppBar(
brightness: Theme.of(context).brightness,
elevation: 1,
backgroundColor:
StreamChatTheme.of(context).channelListHeaderTheme.color,
backgroundColor: chatThemeData.channelListHeaderTheme.color,
centerTitle: true,
leading: leading ??
Center(
@@ -137,14 +137,10 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
}
Scaffold.of(context).openDrawer();
},
borderRadius: StreamChatTheme.of(context)
.channelListHeaderTheme
.avatarTheme
?.borderRadius,
constraints: StreamChatTheme.of(context)
.channelListHeaderTheme
.avatarTheme
?.constraints,
borderRadius: chatThemeData
.channelListHeaderTheme.avatarTheme?.borderRadius,
constraints: chatThemeData
.channelListHeaderTheme.avatarTheme?.constraints,
)
: const Offstage(),
),
@@ -157,9 +153,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
Color? color;
switch (status) {
case ConnectionStatus.connected:
color = StreamChatTheme.of(context)
.colorTheme
.accentBlue;
color = chatThemeData.colorTheme.accentBlue;
break;
case ConnectionStatus.connecting:
color = Colors.grey;
@@ -209,12 +203,15 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
);
}
Widget _buildConnectedTitleState(BuildContext context) => Text(
'Stream Chat',
style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith(
color: StreamChatTheme.of(context).colorTheme.black,
),
);
Widget _buildConnectedTitleState(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return Text(
'Stream Chat',
style: chatThemeData.textTheme.headlineBold.copyWith(
color: chatThemeData.colorTheme.black,
),
);
}
Widget _buildConnectingTitleState(BuildContext context) => Row(
mainAxisAlignment: MainAxisAlignment.center,
@@ -243,39 +240,35 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
Widget _buildDisconnectedTitleState(
BuildContext context,
StreamChatClient client,
) =>
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Offline...',
style: StreamChatTheme.of(context)
.channelListHeaderTheme
.title
?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
),
) {
final chatThemeData = StreamChatTheme.of(context);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Offline...',
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
),
TextButton(
onPressed: () async {
await client.disconnect();
await client.connect();
},
child: Text(
'Try Again',
style: StreamChatTheme.of(context)
.channelListHeaderTheme
.title
?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
color: StreamChatTheme.of(context).colorTheme.accentBlue,
),
),
TextButton(
onPressed: () async {
await client.disconnect();
await client.connect();
},
child: Text(
'Try Again',
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
color: chatThemeData.colorTheme.accentBlue,
),
),
],
);
),
],
);
}
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
@@ -237,78 +237,70 @@ class _ChannelListViewState extends State<ChannelListView> {
}
Widget _buildEmptyWidget(BuildContext context) => LayoutBuilder(
builder: (context, viewportConstraints) => SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Stack(
children: [
ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
builder: (context, viewportConstraints) {
final chatThemeData = StreamChatTheme.of(context);
return SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Stack(
children: [
ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8),
child: StreamSvgIcon.message(
size: 136,
color: chatThemeData.colorTheme.greyGainsboro,
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Text(
'Lets start chatting!',
style: chatThemeData.textTheme.headline,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 52,
),
child: Text(
'How about sending your first message to a friend?',
textAlign: TextAlign.center,
style: chatThemeData.textTheme.body.copyWith(
color: chatThemeData.colorTheme.grey,
),
),
),
],
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8),
child: StreamSvgIcon.message(
size: 136,
color: StreamChatTheme.of(context)
.colorTheme
.greyGainsboro,
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Text(
'Lets start chatting!',
style: StreamChatTheme.of(context).textTheme.headline,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 52,
),
child: Text(
'How about sending your first message to a friend?',
textAlign: TextAlign.center,
style: StreamChatTheme.of(context)
.textTheme
.body
.copyWith(
color:
StreamChatTheme.of(context).colorTheme.grey,
),
),
),
],
),
),
if (widget.onStartChatPressed != null)
Positioned(
right: 0,
left: 0,
bottom: 32,
child: Center(
child: TextButton(
onPressed: widget.onStartChatPressed,
child: Text(
'Start a chat',
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
),
if (widget.onStartChatPressed != null)
Positioned(
right: 0,
left: 0,
bottom: 32,
child: Center(
child: TextButton(
onPressed: widget.onStartChatPressed,
child: Text(
'Start a chat',
style: chatThemeData.textTheme.bodyBold.copyWith(
color: chatThemeData.colorTheme.accentBlue,
),
),
),
),
),
),
],
),
),
],
),
);
},
);
Widget _buildLoadingWidget(BuildContext context) => ListView(
@@ -331,10 +323,11 @@ class _ChannelListViewState extends State<ChannelListView> {
);
Shimmer _buildLoadingItem(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
if (widget.crossAxisCount > 1) {
return Shimmer.fromColors(
baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro,
highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke,
baseColor: chatThemeData.colorTheme.greyGainsboro,
highlightColor: chatThemeData.colorTheme.whiteSmoke,
child: Column(
children: [
const SizedBox(height: 4),
@@ -362,12 +355,12 @@ class _ChannelListViewState extends State<ChannelListView> {
);
} else {
return Shimmer.fromColors(
baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro,
highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke,
baseColor: chatThemeData.colorTheme.greyGainsboro,
highlightColor: chatThemeData.colorTheme.whiteSmoke,
child: ListTile(
leading: Container(
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white,
color: chatThemeData.colorTheme.white,
shape: BoxShape.circle,
),
constraints: const BoxConstraints.tightFor(
@@ -383,7 +376,7 @@ class _ChannelListViewState extends State<ChannelListView> {
alignment: Alignment.centerLeft,
child: Container(
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white,
color: chatThemeData.colorTheme.white,
borderRadius: BorderRadius.circular(11),
),
constraints: const BoxConstraints.tightFor(
@@ -400,7 +393,7 @@ class _ChannelListViewState extends State<ChannelListView> {
alignment: Alignment.centerLeft,
child: Container(
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white,
color: chatThemeData.colorTheme.white,
borderRadius: BorderRadius.circular(11),
),
constraints: const BoxConstraints.expand(
@@ -412,7 +405,7 @@ class _ChannelListViewState extends State<ChannelListView> {
Container(
margin: const EdgeInsets.only(left: 16),
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white,
color: chatThemeData.colorTheme.white,
borderRadius: BorderRadius.circular(11),
),
constraints: const BoxConstraints.tightFor(
@@ -456,12 +449,13 @@ class _ChannelListViewState extends State<ChannelListView> {
);
Widget _listItemBuilder(BuildContext context, int i, List<Channel> channels) {
final channelsProvider = ChannelsBloc.of(context);
final channelsBloc = ChannelsBloc.of(context);
if (i < channels.length) {
final channel = channels[i];
final onTap = _getChannelTap(context);
final backgroundColor = StreamChatTheme.of(context).colorTheme.whiteSmoke;
final chatThemeData = StreamChatTheme.of(context);
final backgroundColor = chatThemeData.colorTheme.whiteSmoke;
return StreamChannel(
key: ValueKey<String>('CHANNEL-${channel.id}'),
channel: channel,
@@ -506,7 +500,7 @@ class _ChannelListViewState extends State<ChannelListView> {
IconSlideAction(
color: backgroundColor,
iconWidget: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed,
color: chatThemeData.colorTheme.accentRed,
),
onTap: () async {
final res = await showConfirmationDialog(
@@ -517,7 +511,7 @@ class _ChannelListViewState extends State<ChannelListView> {
'Are you sure you want to delete this conversation?',
cancelText: 'CANCEL',
icon: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed,
color: chatThemeData.colorTheme.accentRed,
),
);
if (res == true) {
@@ -527,7 +521,7 @@ class _ChannelListViewState extends State<ChannelListView> {
),
],
child: Container(
color: StreamChatTheme.of(context).colorTheme.whiteSnow,
color: chatThemeData.colorTheme.whiteSnow,
child: widget.channelPreviewBuilder?.call(context, channel) ??
ChannelPreview(
onLongPress: widget.onChannelLongPress,
@@ -540,7 +534,7 @@ class _ChannelListViewState extends State<ChannelListView> {
),
);
} else {
return _buildQueryProgressIndicator(context, channelsProvider);
return _buildQueryProgressIndicator(context, channelsBloc);
}
}
@@ -68,6 +68,8 @@ class ChannelPreview extends StatelessWidget {
@override
Widget build(BuildContext context) {
final channelPreviewTheme = StreamChatTheme.of(context).channelPreviewTheme;
final streamChatState = StreamChat.of(context);
return StreamBuilder<bool>(
stream: channel.isMutedStream,
initialData: channel.isMuted,
@@ -130,7 +132,7 @@ class ChannelPreview extends StatelessWidget {
(m) => !m.isDeleted && m.shadowed != true,
);
if (lastMessage?.user?.id ==
StreamChat.of(context).user?.id) {
streamChatState.user?.id) {
return Padding(
padding: const EdgeInsets.only(right: 4),
child: SendingIndicator(
@@ -194,6 +196,7 @@ class ChannelPreview extends StatelessWidget {
);
Widget _buildSubtitle(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
if (channel.isMuted) {
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
@@ -203,7 +206,7 @@ class ChannelPreview extends StatelessWidget {
),
Text(
' Channel is muted',
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle,
style: chatThemeData.channelPreviewTheme.subtitle,
),
],
);
@@ -211,7 +214,7 @@ class ChannelPreview extends StatelessWidget {
return TypingIndicator(
channel: channel,
alternativeWidget: _buildLastMessage(context),
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle,
style: chatThemeData.channelPreviewTheme.subtitle,
);
}
@@ -245,35 +248,24 @@ class ChannelPreview extends StatelessWidget {
text = parts.join(' ');
final chatThemeData = StreamChatTheme.of(context);
return Text.rich(
_getDisplayText(
text,
lastMessage.mentionedUsers,
lastMessage.attachments,
StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle
?.copyWith(
color: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle
?.color,
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
? FontStyle.italic
: FontStyle.normal),
StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle
?.copyWith(
color: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle
?.color,
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
? FontStyle.italic
: FontStyle.normal,
fontWeight: FontWeight.bold,
),
chatThemeData.channelPreviewTheme.subtitle?.copyWith(
color: chatThemeData.channelPreviewTheme.subtitle?.color,
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
? FontStyle.italic
: FontStyle.normal),
chatThemeData.channelPreviewTheme.subtitle?.copyWith(
color: chatThemeData.channelPreviewTheme.subtitle?.color,
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
? FontStyle.italic
: FontStyle.normal,
fontWeight: FontWeight.bold,
),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -44,18 +44,19 @@ class DateDivider extends StatelessWidget {
if (uppercase) dayInfo = dayInfo.toUpperCase();
final chatThemeData = StreamChatTheme.of(context);
return Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1),
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.overlayDark,
color: chatThemeData.colorTheme.overlayDark,
borderRadius: BorderRadius.circular(8),
),
child: Text(
dayInfo,
style: StreamChatTheme.of(context).textTheme.footnote.copyWith(
color: StreamChatTheme.of(context).colorTheme.white,
),
style: chatThemeData.textTheme.footnote.copyWith(
color: chatThemeData.colorTheme.white,
),
),
),
);
@@ -31,44 +31,41 @@ class DeletedMessage extends StatelessWidget {
final bool reverse;
@override
Widget build(BuildContext context) => Transform(
transform: Matrix4.rotationY(reverse ? pi : 0),
alignment: Alignment.center,
child: Material(
color: messageTheme.messageBackgroundColor,
shape: shape ??
RoundedRectangleBorder(
borderRadius: borderRadiusGeometry ?? BorderRadius.zero,
side: borderSide ??
BorderSide(
color: Theme.of(context).brightness == Brightness.dark
? StreamChatTheme.of(context)
.colorTheme
.white
.withAlpha(24)
: StreamChatTheme.of(context)
.colorTheme
.black
.withAlpha(24),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return Transform(
transform: Matrix4.rotationY(reverse ? pi : 0),
alignment: Alignment.center,
child: Material(
color: messageTheme.messageBackgroundColor,
shape: shape ??
RoundedRectangleBorder(
borderRadius: borderRadiusGeometry ?? BorderRadius.zero,
side: borderSide ??
BorderSide(
color: Theme.of(context).brightness == Brightness.dark
? chatThemeData.colorTheme.white.withAlpha(24)
: chatThemeData.colorTheme.black.withAlpha(24),
),
),
child: Transform(
transform: Matrix4.rotationY(reverse ? pi : 0),
alignment: Alignment.center,
child: Text(
'Message deleted',
style: messageTheme.messageText?.copyWith(
fontStyle: FontStyle.italic,
color: messageTheme.createdAt?.color,
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
),
child: Transform(
transform: Matrix4.rotationY(reverse ? pi : 0),
alignment: Alignment.center,
child: Text(
'Message deleted',
style: messageTheme.messageText?.copyWith(
fontStyle: FontStyle.italic,
color: messageTheme.createdAt?.color,
),
),
),
),
);
),
);
}
}
@@ -46,18 +46,12 @@ class GroupImage extends StatelessWidget {
onTap: onTap,
child: ClipRRect(
borderRadius: borderRadius ??
StreamChatTheme.of(context)
.ownMessageTheme
.avatarTheme
?.borderRadius,
streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius,
child: Container(
constraints: constraints ??
StreamChatTheme.of(context)
.ownMessageTheme
.avatarTheme
?.constraints,
streamChatTheme.ownMessageTheme.avatarTheme?.constraints,
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
color: streamChatTheme.colorTheme.accentBlue,
),
child: Flex(
direction: Axis.vertical,
@@ -125,8 +119,7 @@ class GroupImage extends StatelessWidget {
BorderRadius.zero) +
BorderRadius.circular(selectionThickness),
child: Container(
color: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue,
color: selectionColor ?? streamChatTheme.colorTheme.accentBlue,
height: 64,
width: 64,
child: Padding(
@@ -77,16 +77,18 @@ class _ImageFooterState extends State<ImageFooter> {
@override
Widget build(BuildContext context) {
final showShareButton = !kIsWeb;
final mediaQueryData = MediaQuery.of(context);
final chatThemeData = StreamChatTheme.of(context);
return SizedBox.fromSize(
size: Size(
MediaQuery.of(context).size.width,
MediaQuery.of(context).padding.bottom + widget.preferredSize.height,
mediaQueryData.size.width,
mediaQueryData.padding.bottom + widget.preferredSize.height,
),
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: BottomAppBar(
color: StreamChatTheme.of(context).colorTheme.white,
color: chatThemeData.colorTheme.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -98,7 +100,7 @@ class _ImageFooterState extends State<ImageFooter> {
IconButton(
icon: StreamSvgIcon.iconShare(
size: 24,
color: StreamChatTheme.of(context).colorTheme.black,
color: chatThemeData.colorTheme.black,
),
onPressed: () async {
final attachment =
@@ -134,8 +136,7 @@ class _ImageFooterState extends State<ImageFooter> {
children: <Widget>[
Text(
'${widget.currentPage + 1} of ${widget.totalPages}',
style:
StreamChatTheme.of(context).textTheme.headlineBold,
style: chatThemeData.textTheme.headlineBold,
),
],
),
@@ -143,7 +144,7 @@ class _ImageFooterState extends State<ImageFooter> {
),
IconButton(
icon: StreamSvgIcon.iconGrid(
color: StreamChatTheme.of(context).colorTheme.black,
color: chatThemeData.colorTheme.black,
),
onPressed: () => _showPhotosModal(context),
),
@@ -155,10 +156,11 @@ class _ImageFooterState extends State<ImageFooter> {
}
void _showPhotosModal(context) {
final chatThemeData = StreamChatTheme.of(context);
showModalBottomSheet(
context: context,
barrierColor: StreamChatTheme.of(context).colorTheme.overlay,
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
barrierColor: chatThemeData.colorTheme.overlay,
backgroundColor: chatThemeData.colorTheme.white,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
@@ -189,9 +191,7 @@ class _ImageFooterState extends State<ImageFooter> {
padding: const EdgeInsets.all(16),
child: Text(
'Photos',
style: StreamChatTheme.of(context)
.textTheme
.headlineBold,
style: chatThemeData.textTheme.headlineBold,
),
),
),
@@ -199,7 +199,7 @@ class _ImageFooterState extends State<ImageFooter> {
alignment: Alignment.centerRight,
child: IconButton(
icon: StreamSvgIcon.close(
color: StreamChatTheme.of(context).colorTheme.black,
color: chatThemeData.colorTheme.black,
),
onPressed: () => Navigator.maybePop(context),
),
@@ -262,9 +262,7 @@ class _ImageFooterState extends State<ImageFooter> {
boxShadow: [
BoxShadow(
blurRadius: 8,
color: StreamChatTheme.of(context)
.colorTheme
.black
color: chatThemeData.colorTheme.black
.withOpacity(0.3),
),
],
@@ -50,59 +50,58 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
final int currentIndex;
@override
Widget build(BuildContext context) => AppBar(
brightness: Theme.of(context).brightness,
elevation: 1,
leading: showBackButton
? IconButton(
icon: StreamSvgIcon.close(
color: StreamChatTheme.of(context).colorTheme.black,
size: 24,
),
onPressed: onBackPressed,
)
: const SizedBox(),
backgroundColor:
StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color,
actions: <Widget>[
if (message.type != 'ephemeral')
IconButton(
icon: StreamSvgIcon.iconMenuPoint(
color: StreamChatTheme.of(context).colorTheme.black,
Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return AppBar(
brightness: Theme.of(context).brightness,
elevation: 1,
leading: showBackButton
? IconButton(
icon: StreamSvgIcon.close(
color: chatThemeData.colorTheme.black,
size: 24,
),
onPressed: () {
_showMessageActionModalBottomSheet(context);
},
onPressed: onBackPressed,
)
: const SizedBox(),
backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color,
actions: <Widget>[
if (message.type != 'ephemeral')
IconButton(
icon: StreamSvgIcon.iconMenuPoint(
color: chatThemeData.colorTheme.black,
),
],
centerTitle: true,
title: message.type != 'ephemeral'
? InkWell(
onTap: onTitleTap,
child: SizedBox(
height: preferredSize.height,
width: preferredSize.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
userName,
style:
StreamChatTheme.of(context).textTheme.headlineBold,
),
Text(
sentAt,
style: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle,
),
],
),
onPressed: () {
_showMessageActionModalBottomSheet(context);
},
),
],
centerTitle: true,
title: message.type != 'ephemeral'
? InkWell(
onTap: onTitleTap,
child: SizedBox(
height: preferredSize.height,
width: preferredSize.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
userName,
style: chatThemeData.textTheme.headlineBold,
),
Text(
sentAt,
style: chatThemeData.channelPreviewTheme.subtitle,
),
],
),
)
: const SizedBox(),
);
),
)
: const SizedBox(),
);
}
@override
final Size preferredSize;
@@ -38,26 +38,29 @@ class InfoTile extends StatelessWidget {
final Color? backgroundColor;
@override
Widget build(BuildContext context) => PortalEntry(
visible: showMessage,
portalAnchor: tileAnchor ?? Alignment.topCenter,
childAnchor: childAnchor ?? Alignment.bottomCenter,
portal: Container(
height: 25,
color: backgroundColor ??
StreamChatTheme.of(context).colorTheme.grey.withOpacity(0.9),
child: Center(
child: Text(
message,
style: textStyle ??
StreamChatTheme.of(context).textTheme.body.copyWith(
color: Colors.white,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return PortalEntry(
visible: showMessage,
portalAnchor: tileAnchor ?? Alignment.topCenter,
childAnchor: childAnchor ?? Alignment.bottomCenter,
portal: Container(
height: 25,
color:
backgroundColor ?? chatThemeData.colorTheme.grey.withOpacity(0.9),
child: Center(
child: Text(
message,
style: textStyle ??
chatThemeData.textTheme.body.copyWith(
color: Colors.white,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
child: child,
);
),
child: child,
);
}
}
@@ -56,6 +56,7 @@ class _MediaListViewState extends State<MediaListView> {
position,
) {
final media = _media.elementAt(position);
final chatThemeData = StreamChatTheme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 1, vertical: 1),
child: InkWell(
@@ -89,10 +90,8 @@ class _MediaListViewState extends State<MediaListView> {
? 1.0
: 0.0,
child: Container(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.5),
color:
chatThemeData.colorTheme.black.withOpacity(0.5),
alignment: Alignment.topRight,
padding: const EdgeInsets.only(
top: 8,
@@ -100,13 +99,10 @@ class _MediaListViewState extends State<MediaListView> {
),
child: CircleAvatar(
radius: 12,
backgroundColor:
StreamChatTheme.of(context).colorTheme.white,
backgroundColor: chatThemeData.colorTheme.white,
child: StreamSvgIcon.check(
size: 24,
color: StreamChatTheme.of(context)
.colorTheme
.black,
color: chatThemeData.colorTheme.black,
),
),
),
@@ -128,7 +124,7 @@ class _MediaListViewState extends State<MediaListView> {
child: Text(
media.videoDuration.format(),
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.white,
color: chatThemeData.colorTheme.white,
),
),
),
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// This widget is used for showing user tiles for mentions
@@ -32,71 +31,70 @@ class MentionTile extends StatelessWidget {
final Widget? trailing;
@override
Widget build(BuildContext context) => SizedBox(
height: 56,
child: Row(
children: [
const SizedBox(
width: 16,
),
leading ??
UserAvatar(
constraints: BoxConstraints.tight(
const Size(
40,
40,
),
Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return SizedBox(
height: 56,
child: Row(
children: [
const SizedBox(
width: 16,
),
leading ??
UserAvatar(
constraints: BoxConstraints.tight(
const Size(
40,
40,
),
user: member.user!,
),
const SizedBox(
width: 8,
),
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title ??
Text(
member.user!.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: StreamChatTheme.of(context).textTheme.bodyBold,
user: member.user!,
),
const SizedBox(
width: 8,
),
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title ??
Text(
member.user!.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: chatThemeData.textTheme.bodyBold,
),
const SizedBox(
height: 2,
),
subtitle ??
Text(
'@${member.userId}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: chatThemeData.textTheme.footnoteBold.copyWith(
color: chatThemeData.colorTheme.grey,
),
const SizedBox(
height: 2,
),
subtitle ??
Text(
'@${member.userId}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: StreamChatTheme.of(context)
.textTheme
.footnoteBold
.copyWith(
color:
StreamChatTheme.of(context).colorTheme.grey,
),
),
],
),
),
],
),
),
trailing ??
Padding(
padding: const EdgeInsets.only(
right: 18,
left: 8,
),
child: StreamSvgIcon.mentions(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
),
),
trailing ??
Padding(
padding: const EdgeInsets.only(
right: 18,
left: 8,
),
],
),
);
child: StreamSvgIcon.mentions(
color: chatThemeData.colorTheme.accentBlue,
),
),
],
),
);
}
}
@@ -3,12 +3,12 @@ import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/message_action.dart';
import 'package:stream_chat_flutter/src/reaction_picker.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Constructs a modal with actions for a message
class MessageActionsModal extends StatefulWidget {
@@ -108,7 +108,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
Widget build(BuildContext context) => _showMessageOptionsModal();
Widget _showMessageOptionsModal() {
final size = MediaQuery.of(context).size;
final mediaQueryData = MediaQuery.of(context);
final size = mediaQueryData.size;
final user = StreamChat.of(context).user;
final roughMaxSize = 2 * size.width / 3;
@@ -133,6 +134,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
final hasFileAttachment =
widget.message.attachments.any((it) => it.type == 'file') == true;
final streamChatThemeData = StreamChatTheme.of(context);
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => Navigator.maybePop(context),
@@ -145,7 +147,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
sigmaY: 10,
),
child: Container(
color: StreamChatTheme.of(context).colorTheme.overlay,
color: streamChatThemeData.colorTheme.overlay,
),
),
),
@@ -226,11 +228,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
left: widget.reverse ? 0 : 40,
),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.75,
width: mediaQueryData.size.width * 0.75,
child: Material(
color: StreamChatTheme.of(context)
.colorTheme
.whiteSnow,
color: streamChatThemeData.colorTheme.whiteSnow,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
@@ -266,9 +266,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
].insertBetween(
Container(
height: 1,
color: StreamChatTheme.of(context)
.colorTheme
.greyWhisper,
color: streamChatThemeData
.colorTheme.greyWhisper,
),
),
),
@@ -310,11 +309,12 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
void _showFlagDialog() async {
final client = StreamChat.of(context).client;
final streamChatThemeData = StreamChatTheme.of(context);
final answer = await showConfirmationDialog(
context,
title: 'Flag Message',
icon: StreamSvgIcon.flag(
color: StreamChatTheme.of(context).colorTheme.accentRed,
color: streamChatThemeData.colorTheme.accentRed,
size: 24,
),
question:
@@ -324,7 +324,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
cancelText: 'CANCEL',
);
final theme = StreamChatTheme.of(context);
final theme = streamChatThemeData;
if (answer == true) {
try {
await client.flagMessage(widget.message.id);
@@ -400,48 +400,54 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
);
}
Widget _buildReplyButton(BuildContext context) => InkWell(
onTap: () {
Navigator.pop(context);
if (widget.onReplyTap != null) {
widget.onReplyTap!(widget.message);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
child: Row(
children: [
StreamSvgIcon.reply(
color: StreamChatTheme.of(context).primaryIconTheme.color,
),
const SizedBox(width: 16),
Text(
'Reply',
style: StreamChatTheme.of(context).textTheme.body,
),
],
),
Widget _buildReplyButton(BuildContext context) {
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell(
onTap: () {
Navigator.pop(context);
if (widget.onReplyTap != null) {
widget.onReplyTap!(widget.message);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
child: Row(
children: [
StreamSvgIcon.reply(
color: streamChatThemeData.primaryIconTheme.color,
),
const SizedBox(width: 16),
Text(
'Reply',
style: streamChatThemeData.textTheme.body,
),
],
),
);
),
);
}
Widget _buildFlagButton(BuildContext context) => InkWell(
onTap: _showFlagDialog,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
child: Row(
children: [
StreamSvgIcon.iconFlag(
color: StreamChatTheme.of(context).primaryIconTheme.color,
),
const SizedBox(width: 16),
Text(
'Flag Message',
style: StreamChatTheme.of(context).textTheme.body,
),
],
),
Widget _buildFlagButton(BuildContext context) {
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell(
onTap: _showFlagDialog,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
child: Row(
children: [
StreamSvgIcon.iconFlag(
color: streamChatThemeData.primaryIconTheme.color,
),
const SizedBox(width: 16),
Text(
'Flag Message',
style: streamChatThemeData.textTheme.body,
),
],
),
);
),
);
}
Widget _buildDeleteButton(BuildContext context) {
final isDeleteFailed =
@@ -469,54 +475,61 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
);
}
Widget _buildCopyButton(BuildContext context) => InkWell(
onTap: () async {
widget.onCopyTap?.call(widget.message);
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
child: Row(
children: [
StreamSvgIcon.copy(
size: 24,
color: StreamChatTheme.of(context).primaryIconTheme.color,
),
const SizedBox(width: 16),
Text(
'Copy Message',
style: StreamChatTheme.of(context).textTheme.body,
),
],
),
Widget _buildCopyButton(BuildContext context) {
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell(
onTap: () async {
widget.onCopyTap?.call(widget.message);
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
child: Row(
children: [
StreamSvgIcon.copy(
size: 24,
color: streamChatThemeData.primaryIconTheme.color,
),
const SizedBox(width: 16),
Text(
'Copy Message',
style: streamChatThemeData.textTheme.body,
),
],
),
);
),
);
}
Widget _buildEditMessage(BuildContext context) => InkWell(
onTap: () async {
Navigator.pop(context);
_showEditBottomSheet(context);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
child: Row(
children: [
StreamSvgIcon.edit(
color: StreamChatTheme.of(context).primaryIconTheme.color,
),
const SizedBox(width: 16),
Text(
'Edit Message',
style: StreamChatTheme.of(context).textTheme.body,
),
],
),
Widget _buildEditMessage(BuildContext context) {
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell(
onTap: () async {
Navigator.pop(context);
_showEditBottomSheet(context);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
child: Row(
children: [
StreamSvgIcon.edit(
color: streamChatThemeData.primaryIconTheme.color,
),
const SizedBox(width: 16),
Text(
'Edit Message',
style: streamChatThemeData.textTheme.body,
),
],
),
);
),
);
}
Widget _buildResendMessage(BuildContext context) {
final isUpdateFailed =
widget.message.status == MessageSendingStatus.failed_update;
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell(
onTap: () {
Navigator.pop(context);
@@ -532,12 +545,12 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
child: Row(
children: [
StreamSvgIcon.circleUp(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
color: streamChatThemeData.colorTheme.accentBlue,
),
const SizedBox(width: 16),
Text(
isUpdateFailed ? 'Resend Edited Message' : 'Resend',
style: StreamChatTheme.of(context).textTheme.body,
style: streamChatThemeData.textTheme.body,
),
],
),
@@ -547,13 +560,13 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
void _showEditBottomSheet(BuildContext context) {
final channel = StreamChannel.of(context).channel;
final streamChatThemeData = StreamChatTheme.of(context);
showModalBottomSheet(
context: context,
elevation: 2,
clipBehavior: Clip.hardEdge,
isScrollControlled: true,
backgroundColor:
StreamChatTheme.of(context).messageInputTheme.inputBackground,
backgroundColor: streamChatThemeData.messageInputTheme.inputBackground,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
@@ -575,8 +588,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
Padding(
padding: const EdgeInsets.all(8),
child: StreamSvgIcon.edit(
color:
StreamChatTheme.of(context).colorTheme.greyGainsboro,
color: streamChatThemeData.colorTheme.greyGainsboro,
),
),
const Text(
@@ -608,27 +620,30 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
);
}
Widget _buildThreadReplyButton(BuildContext context) => InkWell(
onTap: () {
Navigator.pop(context);
if (widget.onThreadReplyTap != null) {
widget.onThreadReplyTap!(widget.message);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
child: Row(
children: [
StreamSvgIcon.thread(
color: StreamChatTheme.of(context).primaryIconTheme.color,
),
const SizedBox(width: 16),
Text(
'Thread Reply',
style: StreamChatTheme.of(context).textTheme.body,
),
],
),
Widget _buildThreadReplyButton(BuildContext context) {
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell(
onTap: () {
Navigator.pop(context);
if (widget.onThreadReplyTap != null) {
widget.onThreadReplyTap!(widget.message);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
child: Row(
children: [
StreamSvgIcon.thread(
color: streamChatThemeData.primaryIconTheme.color,
),
const SizedBox(width: 16),
Text(
'Thread Reply',
style: streamChatThemeData.textTheme.body,
),
],
),
);
),
);
}
}
File diff suppressed because it is too large Load Diff
@@ -6,15 +6,15 @@ import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:rxdart/rxdart.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/message_widget.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/swipeable.dart';
import 'package:stream_chat_flutter/src/system_message.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:visibility_detector/visibility_detector.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/swipeable.dart';
/// Widget builder for message
typedef MessageBuilder = Widget Function(
@@ -316,46 +316,37 @@ class _MessageListViewState extends State<MessageListView> {
final MessageListController _messageListController = MessageListController();
@override
Widget build(BuildContext context) => MessageListCore(
messageFilter: widget.messageFilter,
loadingBuilder: widget.loadingBuilder ??
(context) => const Center(
child: CircularProgressIndicator(),
Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return MessageListCore(
messageFilter: widget.messageFilter,
loadingBuilder: widget.loadingBuilder ??
(context) => const Center(
child: CircularProgressIndicator(),
),
emptyBuilder: widget.emptyBuilder ??
(context) => Center(
child: Text(
'No chats here yet...',
style: chatThemeData.textTheme.footnote.copyWith(
color: chatThemeData.colorTheme.black.withOpacity(.5)),
),
emptyBuilder: widget.emptyBuilder ??
(context) => Center(
child: Text(
'No chats here yet...',
style: StreamChatTheme.of(context)
.textTheme
.footnote
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5)),
),
),
messageListBuilder:
widget.messageListBuilder ?? (context, list) => _buildListView(list),
messageListController: _messageListController,
parentMessage: widget.parentMessage,
showScrollToBottom: widget.showScrollToBottom,
errorWidgetBuilder: widget.errorWidgetBuilder ??
(BuildContext context, Object error) => Center(
child: Text(
'Something went wrong',
style: chatThemeData.textTheme.footnote.copyWith(
color: chatThemeData.colorTheme.black.withOpacity(.5)),
),
messageListBuilder: widget.messageListBuilder ??
(context, list) => _buildListView(list),
messageListController: _messageListController,
parentMessage: widget.parentMessage,
showScrollToBottom: widget.showScrollToBottom,
errorWidgetBuilder: widget.errorWidgetBuilder ??
(BuildContext context, Object error) => Center(
child: Text(
'Something went wrong',
style: StreamChatTheme.of(context)
.textTheme
.footnote
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5)),
),
),
);
),
);
}
Widget _buildListView(List<Message> data) {
messages = data;
@@ -448,10 +439,10 @@ class _MessageListViewState extends State<MessageListView> {
if (i == 0) return const SizedBox(height: 30);
if (i == messages.length + 1) {
final replyCount = widget.parentMessage!.replyCount;
final chatThemeData = StreamChatTheme.of(context);
return Container(
decoration: BoxDecoration(
gradient:
StreamChatTheme.of(context).colorTheme.bgGradient,
gradient: chatThemeData.colorTheme.bgGradient,
),
child: Padding(
padding: const EdgeInsets.all(8),
@@ -459,10 +450,8 @@ class _MessageListViewState extends State<MessageListView> {
// ignore: lines_longer_than_80_chars
'$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}',
textAlign: TextAlign.center,
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.subtitle,
style: chatThemeData
.channelTheme.channelHeaderTheme.subtitle,
),
),
);
@@ -517,13 +506,13 @@ class _MessageListViewState extends State<MessageListView> {
}
if (i == messages.length + 1) {
return _buildLoadingIndicator(
streamChannel,
streamChannel!,
QueryDirection.top,
);
}
if (i == 0) {
return _buildLoadingIndicator(
streamChannel,
streamChannel!,
QueryDirection.bottom,
);
}
@@ -536,7 +525,7 @@ class _MessageListViewState extends State<MessageListView> {
context,
message,
messages,
streamChannel,
streamChannel!,
);
} else if (i == messages.length - 1) {
messageWidget = _buildTopMessage(
@@ -635,6 +624,7 @@ class _MessageListViewState extends State<MessageListView> {
final showUnreadCount = unreadCount > 0 &&
streamChannel!.channel.state!.members.any((e) =>
e.userId == streamChannel!.channel.client.state.user!.id);
final chatThemeData = StreamChatTheme.of(context);
return Positioned(
bottom: 8,
right: 8,
@@ -644,7 +634,7 @@ class _MessageListViewState extends State<MessageListView> {
clipBehavior: Clip.none,
children: [
FloatingActionButton(
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
backgroundColor: chatThemeData.colorTheme.white,
onPressed: () {
if (unreadCount > 0) {
streamChannel!.channel.markRead();
@@ -663,7 +653,7 @@ class _MessageListViewState extends State<MessageListView> {
}
},
child: StreamSvgIcon.down(
color: StreamChatTheme.of(context).colorTheme.black,
color: chatThemeData.colorTheme.black,
),
),
if (showUnreadCount)
@@ -692,12 +682,12 @@ class _MessageListViewState extends State<MessageListView> {
);
Widget _buildLoadingIndicator(
StreamChannelState? streamChannel,
StreamChannelState streamChannel,
QueryDirection direction,
) {
final stream = direction == QueryDirection.top
? streamChannel!.queryTopMessages
: streamChannel!.queryBottomMessages;
? streamChannel.queryTopMessages
: streamChannel.queryBottomMessages;
return StreamBuilder<bool>(
key: const Key('LOADING-INDICATOR'),
stream: stream,
@@ -764,7 +754,7 @@ class _MessageListViewState extends State<MessageListView> {
BuildContext context,
Message message,
List<Message> messages,
StreamChannelState? streamChannel,
StreamChannelState streamChannel,
) {
Widget messageWidget;
if (widget.messageBuilder != null) {
@@ -790,7 +780,7 @@ class _MessageListViewState extends State<MessageListView> {
onVisibilityChanged: (visibility) {
final isVisible = visibility.visibleBounds != Rect.zero;
if (isVisible) {
final channel = streamChannel!.channel;
final channel = streamChannel.channel;
if (_upToDate &&
channel.config?.readEvents == true &&
channel.state!.unreadCount! > 0) {
@@ -811,6 +801,7 @@ class _MessageListViewState extends State<MessageListView> {
final isMyMessage = message.user!.id == StreamChat.of(context).user!.id;
final isOnlyEmoji = message.text!.isOnlyEmoji;
final chatThemeData = StreamChatTheme.of(context);
return MessageWidget(
showReplyMessage: false,
showResendMessage: false,
@@ -837,8 +828,8 @@ class _MessageListViewState extends State<MessageListView> {
borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null,
showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show,
messageTheme: isMyMessage
? StreamChatTheme.of(context).ownMessageTheme
: StreamChatTheme.of(context).otherMessageTheme,
? chatThemeData.ownMessageTheme
: chatThemeData.otherMessageTheme,
onShowMessage: widget.onShowMessage,
onReturnAction: (action) {
switch (action) {
@@ -946,6 +937,7 @@ class _MessageListViewState extends State<MessageListView> {
? BorderSide.none
: null;
final chatThemeData = StreamChatTheme.of(context);
Widget child = MessageWidget(
key: ValueKey<String>('MESSAGE-${message.id}'),
message: message,
@@ -1013,8 +1005,8 @@ class _MessageListViewState extends State<MessageListView> {
horizontal: isOnlyEmoji ? 0 : 16.0,
),
messageTheme: isMyMessage
? StreamChatTheme.of(context).ownMessageTheme
: StreamChatTheme.of(context).otherMessageTheme,
? chatThemeData.ownMessageTheme
: chatThemeData.otherMessageTheme,
readList: readList,
allRead: allRead,
onShowMessage: widget.onShowMessage,
@@ -1054,7 +1046,7 @@ class _MessageListViewState extends State<MessageListView> {
widget.onMessageSwiped?.call(message);
},
backgroundIcon: StreamSvgIcon.reply(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
color: chatThemeData.colorTheme.accentBlue,
),
child: child,
),
@@ -1064,7 +1056,7 @@ class _MessageListViewState extends State<MessageListView> {
if (!initialMessageHighlightComplete &&
widget.highlightInitialMessage &&
_isInitialMessage(message.id)) {
final colorTheme = StreamChatTheme.of(context).colorTheme;
final colorTheme = chatThemeData.colorTheme;
final highlightColor =
widget.messageHighlightColor ?? colorTheme.highlight;
child = TweenAnimationBuilder<Color?>(
@@ -1095,35 +1087,44 @@ class _MessageListViewState extends State<MessageListView> {
_itemPositionListener =
widget.itemPositionListener ?? ItemPositionsListener.create();
streamChannel = StreamChannel.of(context);
initialIndex = _initialIndex;
initialAlignment = _initialAlignment;
_messageNewListener =
streamChannel!.channel.on(EventType.messageNew).listen((event) {
if (_upToDate) {
_bottomPaginationActive = false;
_topPaginationActive = false;
}
if (event.message!.user!.id ==
streamChannel!.channel.client.state.user!.id) {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_scrollController?.jumpTo(
index: 0,
);
});
}
});
if (_isThreadConversation) {
streamChannel!.getReplies(widget.parentMessage!.id);
}
_getOnThreadTap();
super.initState();
}
@override
void didChangeDependencies() {
final newStreamChannel = StreamChannel.of(context);
if (newStreamChannel != streamChannel) {
streamChannel = newStreamChannel;
_messageNewListener?.cancel();
initialIndex = _initialIndex;
initialAlignment = _initialAlignment;
_messageNewListener =
streamChannel!.channel.on(EventType.messageNew).listen((event) {
if (_upToDate) {
_bottomPaginationActive = false;
_topPaginationActive = false;
}
if (event.message!.user!.id ==
streamChannel!.channel.client.state.user!.id) {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_scrollController?.jumpTo(
index: 0,
);
});
}
});
if (_isThreadConversation) {
streamChannel!.getReplies(widget.parentMessage!.id);
}
}
super.didChangeDependencies();
}
void _getOnThreadTap() {
if (widget.onThreadTap != null) {
_onThreadTap = (Message message) {
@@ -1,13 +1,13 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/src/reaction_picker.dart';
import 'package:stream_chat_flutter/src/stream_chat.dart';
import 'package:stream_chat_flutter/src/user_avatar.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/src/extension.dart';
/// Modal widget for displaying message reactions
class MessageReactionsModal extends StatelessWidget {
@@ -176,8 +176,9 @@ class MessageReactionsModal extends StatelessWidget {
Widget _buildReactionCard(BuildContext context) {
final currentUser = StreamChat.of(context).user;
final chatThemeData = StreamChatTheme.of(context);
return Card(
color: StreamChatTheme.of(context).colorTheme.white,
color: chatThemeData.colorTheme.white,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
@@ -190,7 +191,7 @@ class MessageReactionsModal extends StatelessWidget {
children: [
Text(
'Message Reactions',
style: StreamChatTheme.of(context).textTheme.headlineBold,
style: chatThemeData.textTheme.headlineBold,
),
const SizedBox(height: 16),
Flexible(
@@ -220,6 +221,7 @@ class MessageReactionsModal extends StatelessWidget {
BuildContext context,
) {
final isCurrentUser = reaction.user?.id == currentUser.id;
final chatThemeData = StreamChatTheme.of(context);
return ConstrainedBox(
constraints: BoxConstraints.loose(const Size(
64,
@@ -258,7 +260,7 @@ class MessageReactionsModal extends StatelessWidget {
messageTheme.reactionsBorderColor ?? Colors.transparent,
backgroundColor: messageTheme.reactionsBackgroundColor ??
Colors.transparent,
maskColor: StreamChatTheme.of(context).colorTheme.white,
maskColor: chatThemeData.colorTheme.white,
tailCirclesSpacing: 1,
highlightOwnReactions: false,
),
@@ -269,7 +271,7 @@ class MessageReactionsModal extends StatelessWidget {
const SizedBox(height: 8),
Text(
reaction.user!.name.split(' ')[0],
style: StreamChatTheme.of(context).textTheme.footnoteBold,
style: chatThemeData.textTheme.footnoteBold,
textAlign: TextAlign.center,
),
],
@@ -35,6 +35,7 @@ class MessageSearchItem extends StatelessWidget {
final channel = getMessageResponse.channel;
final channelName = channel?.extraData['name'];
final user = message.user!;
final chatThemeData = StreamChatTheme.of(context);
return ListTile(
onTap: onTap,
leading: UserAvatar(
@@ -49,21 +50,18 @@ class MessageSearchItem extends StatelessWidget {
children: [
Text(
user.id == StreamChat.of(context).user?.id ? 'You' : user.name,
style: StreamChatTheme.of(context).channelPreviewTheme.title,
style: chatThemeData.channelPreviewTheme.title,
),
if (channelName != null) ...[
Text(
' in ',
style: StreamChatTheme.of(context)
.channelPreviewTheme
.title
?.copyWith(
fontWeight: FontWeight.normal,
),
style: chatThemeData.channelPreviewTheme.title?.copyWith(
fontWeight: FontWeight.normal,
),
),
Text(
channelName as String,
style: StreamChatTheme.of(context).channelPreviewTheme.title,
style: chatThemeData.channelPreviewTheme.title,
),
],
],
@@ -121,22 +119,23 @@ class MessageSearchItem extends StatelessWidget {
text = parts.join(' ');
}
final chatThemeData = StreamChatTheme.of(context);
return Text.rich(
_getDisplayText(
text!,
message.mentionedUsers,
message.attachments,
StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith(
fontStyle: (message.isSystem || message.isDeleted)
? FontStyle.italic
: FontStyle.normal,
),
StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith(
fontStyle: (message.isSystem || message.isDeleted)
? FontStyle.italic
: FontStyle.normal,
fontWeight: FontWeight.bold,
),
chatThemeData.channelPreviewTheme.subtitle?.copyWith(
fontStyle: (message.isSystem || message.isDeleted)
? FontStyle.italic
: FontStyle.normal,
),
chatThemeData.channelPreviewTheme.subtitle?.copyWith(
fontStyle: (message.isSystem || message.isDeleted)
? FontStyle.italic
: FontStyle.normal,
fontWeight: FontWeight.bold,
),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/message_search_item.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Callback called when tapping on a user
typedef MessageSearchItemTapCallback = void Function(GetMessageResponse);
@@ -270,12 +270,13 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
);
if (widget.showResultCount) {
final chatThemeData = StreamChatTheme.of(context);
child = Column(
children: [
Container(
width: double.maxFinite,
decoration: BoxDecoration(
gradient: StreamChatTheme.of(context).colorTheme.bgGradient,
gradient: chatThemeData.colorTheme.bgGradient,
),
child: Padding(
padding: const EdgeInsets.symmetric(
@@ -285,7 +286,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
child: Text(
'${items.length} results',
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.grey,
color: chatThemeData.colorTheme.grey,
),
),
),
@@ -1,8 +1,8 @@
import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Text widget to display in message
class MessageText extends StatelessWidget {
@@ -31,6 +31,7 @@ class MessageText extends StatelessWidget {
Widget build(BuildContext context) {
final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\\\n');
final themeData = Theme.of(context);
return MarkdownBody(
data: text,
onTapLink: (
@@ -60,14 +61,14 @@ class MessageText extends StatelessWidget {
}
},
styleSheet: MarkdownStyleSheet.fromTheme(
Theme.of(context).copyWith(
textTheme: Theme.of(context).textTheme.apply(
bodyColor: messageTheme.messageText?.color,
decoration: messageTheme.messageText?.decoration,
decorationColor: messageTheme.messageText?.decorationColor,
decorationStyle: messageTheme.messageText?.decorationStyle,
fontFamily: messageTheme.messageText?.fontFamily,
),
themeData.copyWith(
textTheme: themeData.textTheme.apply(
bodyColor: messageTheme.messageText?.color,
decoration: messageTheme.messageText?.decoration,
decorationColor: messageTheme.messageText?.decorationColor,
decorationStyle: messageTheme.messageText?.decorationStyle,
fontFamily: messageTheme.messageText?.fontFamily,
),
),
).copyWith(
a: messageTheme.messageLinks,
@@ -8,6 +8,8 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/image_group.dart';
import 'package:stream_chat_flutter/src/message_action.dart';
import 'package:stream_chat_flutter/src/message_actions_modal.dart';
import 'package:stream_chat_flutter/src/message_reactions_modal.dart';
@@ -15,8 +17,6 @@ import 'package:stream_chat_flutter/src/quoted_message_widget.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/src/url_attachment.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/image_group.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget builder for building attachments
typedef AttachmentBuilder = Widget Function(
@@ -109,6 +109,7 @@ class MessageWidget extends StatefulWidget {
borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
);
final mediaQueryData = MediaQuery.of(context);
if (attachments.length > 1) {
return Padding(
padding: attachmentPadding,
@@ -118,8 +119,8 @@ class MessageWidget extends StatefulWidget {
color: messageTheme.messageBackgroundColor,
child: ImageGroup(
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
mediaQueryData.size.width * 0.8,
mediaQueryData.size.height * 0.3,
),
images: attachments,
message: message,
@@ -142,8 +143,8 @@ class MessageWidget extends StatefulWidget {
message: message,
messageTheme: messageTheme,
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
mediaQueryData.size.width * 0.8,
mediaQueryData.size.height * 0.3,
),
onShowMessage: onShowMessage,
onReturnAction: onReturnAction,
@@ -167,24 +168,25 @@ class MessageWidget extends StatefulWidget {
return wrapAttachmentWidget(
context,
Column(
children: attachments
.map((attachment) => VideoAttachment(
attachment: attachment,
messageTheme: messageTheme,
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
),
message: message,
onShowMessage: onShowMessage,
onReturnAction: onReturnAction,
onAttachmentTap: onAttachmentTap != null
? () {
onAttachmentTap(message, attachment);
}
: null,
))
.toList(),
children: attachments.map((attachment) {
final mediaQueryData = MediaQuery.of(context);
return VideoAttachment(
attachment: attachment,
messageTheme: messageTheme,
size: Size(
mediaQueryData.size.width * 0.8,
mediaQueryData.size.height * 0.3,
),
message: message,
onShowMessage: onShowMessage,
onReturnAction: onReturnAction,
onAttachmentTap: onAttachmentTap != null
? () {
onAttachmentTap(message, attachment);
}
: null,
);
}).toList(),
),
border,
reverse,
@@ -200,18 +202,19 @@ class MessageWidget extends StatefulWidget {
return wrapAttachmentWidget(
context,
Column(
children: attachments
.map((attachment) => GiphyAttachment(
attachment: attachment,
message: message,
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
),
onShowMessage: onShowMessage,
onReturnAction: onReturnAction,
))
.toList(),
children: attachments.map((attachment) {
final mediaQueryData = MediaQuery.of(context);
return GiphyAttachment(
attachment: attachment,
message: message,
size: Size(
mediaQueryData.size.width * 0.8,
mediaQueryData.size.height * 0.3,
),
onShowMessage: onShowMessage,
onReturnAction: onReturnAction,
);
}).toList(),
),
border,
reverse,
@@ -230,21 +233,24 @@ class MessageWidget extends StatefulWidget {
return Column(
children: attachments
.map<Widget>((attachment) => wrapAttachmentWidget(
context,
FileAttachment(
message: message,
attachment: attachment,
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
),
.map<Widget>((attachment) {
final mediaQueryData = MediaQuery.of(context);
return wrapAttachmentWidget(
context,
FileAttachment(
message: message,
attachment: attachment,
size: Size(
mediaQueryData.size.width * 0.8,
mediaQueryData.size.height * 0.3,
),
border,
reverse,
attachmentBorderRadiusGeometry as BorderRadius? ??
BorderRadius.zero,
))
),
border,
reverse,
attachmentBorderRadiusGeometry as BorderRadius? ??
BorderRadius.zero,
);
})
.insertBetween(SizedBox(
height: attachmentPadding.vertical / 2,
))
@@ -658,12 +664,13 @@ class _MessageWidgetState extends State<MessageWidget>
widget.onQuotedMessageTap != null
? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId)
: null;
final chatThemeData = StreamChatTheme.of(context);
return QuotedMessageWidget(
onTap: onTap,
message: widget.message.quotedMessage!,
messageTheme: isMyMessage
? StreamChatTheme.of(context).otherMessageTheme
: StreamChatTheme.of(context).ownMessageTheme,
? chatThemeData.otherMessageTheme
: chatThemeData.ownMessageTheme,
reverse: widget.reverse,
padding: EdgeInsets.only(
right: 8, left: 8, top: 8, bottom: hasNonUrlAttachments ? 8 : 0),
@@ -672,6 +679,7 @@ class _MessageWidgetState extends State<MessageWidget>
Widget get _bottomRow {
if (isDeleted) {
final chatThemeData = StreamChatTheme.of(context);
return Transform(
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
alignment: Alignment.center,
@@ -679,16 +687,14 @@ class _MessageWidgetState extends State<MessageWidget>
mainAxisSize: MainAxisSize.min,
children: [
StreamSvgIcon.eye(
color: StreamChatTheme.of(context).colorTheme.grey,
color: chatThemeData.colorTheme.grey,
size: 16,
),
const SizedBox(width: 8),
Text(
'Only visible to you',
style: StreamChatTheme.of(context)
.textTheme
.footnote
.copyWith(color: StreamChatTheme.of(context).colorTheme.grey),
style: chatThemeData.textTheme.footnote
.copyWith(color: chatThemeData.colorTheme.grey),
),
],
),
@@ -41,56 +41,55 @@ class OptionListTile extends StatelessWidget {
final TextStyle? titleTextStyle;
@override
Widget build(BuildContext context) => Column(
children: [
Container(
color: separatorColor ??
StreamChatTheme.of(context).colorTheme.greyGainsboro,
height: 1,
),
Material(
color: tileColor ?? StreamChatTheme.of(context).colorTheme.white,
child: SizedBox(
height: 63,
child: InkWell(
onTap: onTap,
child: Row(
children: [
if (leading != null) Center(child: leading),
if (leading == null)
const SizedBox(
width: 16,
),
Expanded(
flex: 4,
child: Text(
title!,
style: titleTextStyle ??
(titleColor == null
? StreamChatTheme.of(context).textTheme.bodyBold
: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: titleColor,
)),
Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return Column(
children: [
Container(
color: separatorColor ?? chatThemeData.colorTheme.greyGainsboro,
height: 1,
),
Material(
color: tileColor ?? chatThemeData.colorTheme.white,
child: SizedBox(
height: 63,
child: InkWell(
onTap: onTap,
child: Row(
children: [
if (leading != null) Center(child: leading),
if (leading == null)
const SizedBox(
width: 16,
),
Expanded(
flex: 4,
child: Text(
title!,
style: titleTextStyle ??
(titleColor == null
? chatThemeData.textTheme.bodyBold
: chatThemeData.textTheme.bodyBold.copyWith(
color: titleColor,
)),
),
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.only(right: 16),
child: Align(
alignment: Alignment.centerRight,
child: trailing ?? Container(),
),
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.only(right: 16),
child: Align(
alignment: Alignment.centerRight,
child: trailing ?? Container(),
),
),
),
],
),
),
],
),
),
),
],
);
),
],
);
}
}
@@ -126,6 +126,8 @@ class ReactionBubble extends StatelessWidget {
(r) => r.type == reaction.type,
);
final chatThemeData = StreamChatTheme.of(context);
final userId = StreamChat.of(context).user?.id;
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4,
@@ -135,24 +137,16 @@ class ReactionBubble extends StatelessWidget {
assetName: reactionIcon.assetName,
width: 16,
height: 16,
color: (!highlightOwnReactions ||
reaction.user?.id == StreamChat.of(context).user?.id)
? StreamChatTheme.of(context).colorTheme.accentBlue
: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
color: (!highlightOwnReactions || reaction.user?.id == userId)
? chatThemeData.colorTheme.accentBlue
: chatThemeData.colorTheme.black.withOpacity(.5),
)
: Icon(
Icons.help_outline_rounded,
size: 16,
color: (!highlightOwnReactions ||
reaction.user?.id == StreamChat.of(context).user?.id)
? StreamChatTheme.of(context).colorTheme.accentBlue
: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
color: (!highlightOwnReactions || reaction.user?.id == userId)
? chatThemeData.colorTheme.accentBlue
: chatThemeData.colorTheme.black.withOpacity(.5),
),
);
}
@@ -2,8 +2,8 @@ import 'dart:math';
import 'package:ezanimation/ezanimation.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png)
@@ -33,7 +33,8 @@ class _ReactionPickerState extends State<ReactionPicker>
@override
Widget build(BuildContext context) {
final reactionIcons = StreamChatTheme.of(context).reactionIcons;
final chatThemeData = StreamChatTheme.of(context);
final reactionIcons = chatThemeData.reactionIcons;
if (animations.isEmpty && reactionIcons.isNotEmpty) {
reactionIcons.forEach((element) {
@@ -57,7 +58,7 @@ class _ReactionPickerState extends State<ReactionPicker>
scale: val,
child: Material(
borderRadius: BorderRadius.circular(24),
color: StreamChatTheme.of(context).colorTheme.white,
color: chatThemeData.colorTheme.white,
clipBehavior: Clip.hardEdge,
child: Padding(
padding: const EdgeInsets.symmetric(
@@ -119,9 +120,8 @@ class _ReactionPickerState extends State<ReactionPicker>
animations[index].value * 24.0,
),
color: ownReactionIndex != -1
? StreamChatTheme.of(context)
.colorTheme
.accentBlue
? chatThemeData
.colorTheme.accentBlue
: Theme.of(context)
.iconTheme
.color!
@@ -97,65 +97,60 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
final List<Widget>? actions;
@override
Widget build(BuildContext context) => AppBar(
automaticallyImplyLeading: false,
brightness: Theme.of(context).brightness,
elevation: 1,
leading: leading ??
(showBackButton
? StreamBackButton(
cid: StreamChannel.of(context).channel.cid,
onPressed: onBackPressed,
showUnreads: true,
)
: const SizedBox()),
backgroundColor:
StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color,
centerTitle: true,
actions: actions,
title: InkWell(
onTap: onTitleTap,
child: SizedBox(
height: preferredSize.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
title ??
Text(
'Thread Reply',
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.title,
),
const SizedBox(height: 2),
subtitle ??
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'with ',
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.subtitle,
Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return AppBar(
automaticallyImplyLeading: false,
brightness: Theme.of(context).brightness,
elevation: 1,
leading: leading ??
(showBackButton
? StreamBackButton(
cid: StreamChannel.of(context).channel.cid,
onPressed: onBackPressed,
showUnreads: true,
)
: const SizedBox()),
backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color,
centerTitle: true,
actions: actions,
title: InkWell(
onTap: onTitleTap,
child: SizedBox(
height: preferredSize.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
title ??
Text(
'Thread Reply',
style: chatThemeData.channelTheme.channelHeaderTheme.title,
),
const SizedBox(height: 2),
subtitle ??
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'with ',
style: chatThemeData
.channelTheme.channelHeaderTheme.subtitle,
),
Flexible(
child: ChannelName(
textStyle: chatThemeData
.channelTheme.channelHeaderTheme.subtitle,
),
Flexible(
child: ChannelName(
textStyle: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.subtitle,
),
),
],
),
],
),
),
],
),
],
),
),
);
),
);
}
@override
final Size preferredSize;
@@ -26,91 +26,84 @@ class UrlAttachment extends StatelessWidget {
final EdgeInsets textPadding;
@override
Widget build(BuildContext context) => GestureDetector(
onTap: () {
launchURL(
context,
urlAttachment.ogScrapeUrl,
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (urlAttachment.imageUrl != null)
Container(
clipBehavior: Clip.antiAliasWithSaveLayer,
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
child: Stack(
children: [
CachedNetworkImage(
width: double.infinity,
imageUrl: urlAttachment.imageUrl!,
fit: BoxFit.cover,
),
Positioned(
left: 0,
bottom: -1,
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(16),
),
color:
StreamChatTheme.of(context).colorTheme.blueAlice,
Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return GestureDetector(
onTap: () {
launchURL(
context,
urlAttachment.ogScrapeUrl,
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (urlAttachment.imageUrl != null)
Container(
clipBehavior: Clip.antiAliasWithSaveLayer,
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
child: Stack(
children: [
CachedNetworkImage(
width: double.infinity,
imageUrl: urlAttachment.imageUrl!,
fit: BoxFit.cover,
),
Positioned(
left: 0,
bottom: -1,
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(16),
),
child: Padding(
padding: const EdgeInsets.only(
top: 8,
left: 8,
right: 8,
),
child: Text(
hostDisplayName,
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
),
color: chatThemeData.colorTheme.blueAlice,
),
child: Padding(
padding: const EdgeInsets.only(
top: 8,
left: 8,
right: 8,
),
child: Text(
hostDisplayName,
style: chatThemeData.textTheme.bodyBold.copyWith(
color: chatThemeData.colorTheme.accentBlue,
),
),
),
),
],
),
),
Padding(
padding: textPadding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (urlAttachment.title != null)
Text(
urlAttachment.title!.trim(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: StreamChatTheme.of(context)
.textTheme
.body
.copyWith(fontWeight: FontWeight.w700),
),
if (urlAttachment.text != null)
Text(
urlAttachment.text!,
style: StreamChatTheme.of(context)
.textTheme
.body
.copyWith(fontWeight: FontWeight.w400),
),
),
],
),
),
],
),
);
Padding(
padding: textPadding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (urlAttachment.title != null)
Text(
urlAttachment.title!.trim(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: chatThemeData.textTheme.body
.copyWith(fontWeight: FontWeight.w700),
),
if (urlAttachment.text != null)
Text(
urlAttachment.text!,
style: chatThemeData.textTheme.body
.copyWith(fontWeight: FontWeight.w400),
),
],
),
),
],
),
);
}
}
@@ -1,7 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Widget that displays a user avatar
class UserAvatar extends StatelessWidget {
@@ -95,8 +95,7 @@ class UserAvatar extends StatelessWidget {
child: Container(
constraints: constraints ??
streamChatTheme.ownMessageTheme.avatarTheme?.constraints,
color: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue,
color: selectionColor ?? streamChatTheme.colorTheme.accentBlue,
child: Padding(
padding: EdgeInsets.all(selectionThickness),
child: avatar,
@@ -48,47 +48,52 @@ class UserItem extends StatelessWidget {
final bool showLastOnline;
@override
Widget build(BuildContext context) => ListTile(
onTap: () {
if (onTap != null) {
onTap!(user);
Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return ListTile(
onTap: () {
if (onTap != null) {
onTap!(user);
}
},
onLongPress: () {
if (onLongPress != null) {
onLongPress!(user);
}
},
leading: UserAvatar(
user: user,
onTap: (user) {
if (onImageTap != null) {
onImageTap!(user);
}
},
onLongPress: () {
if (onLongPress != null) {
onLongPress!(user);
}
},
leading: UserAvatar(
user: user,
onTap: (user) {
if (onImageTap != null) {
onImageTap!(user);
}
},
constraints: const BoxConstraints.tightFor(
height: 40,
width: 40,
),
constraints: const BoxConstraints.tightFor(
height: 40,
width: 40,
),
trailing: selected
? StreamSvgIcon.checkSend(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
)
: null,
title: Text(
user.name,
style: StreamChatTheme.of(context).textTheme.bodyBold,
),
subtitle: showLastOnline ? _buildLastActive(context) : null,
);
),
trailing: selected
? StreamSvgIcon.checkSend(
color: chatThemeData.colorTheme.accentBlue,
)
: null,
title: Text(
user.name,
style: chatThemeData.textTheme.bodyBold,
),
subtitle: showLastOnline ? _buildLastActive(context) : null,
);
}
Widget _buildLastActive(context) => Text(
user.online == true
? 'Online'
: 'Last online ${Jiffy(user.lastActive).fromNow()}',
style: StreamChatTheme.of(context).textTheme.footnote.copyWith(
color:
StreamChatTheme.of(context).colorTheme.black.withOpacity(.5)),
);
Widget _buildLastActive(context) {
final chatTheme = StreamChatTheme.of(context);
return Text(
user.online == true
? 'Online'
: 'Last online ${Jiffy(user.lastActive).fromNow()}',
style: chatTheme.textTheme.footnote
.copyWith(color: chatTheme.colorTheme.black.withOpacity(.5)),
);
}
}
@@ -295,21 +295,24 @@ class _UserListViewState extends State<UserListView>
if (i < items.length) {
final item = items[i];
return item.when(
headerItem: (header) => Container(
key: ValueKey<String>('HEADER-$header'),
color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Text(
header,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14.5,
color: StreamChatTheme.of(context).colorTheme.grey,
headerItem: (header) {
final chatThemeData = StreamChatTheme.of(context);
return Container(
key: ValueKey<String>('HEADER-$header'),
color: chatThemeData.colorTheme.black.withOpacity(0.05),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Text(
header,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14.5,
color: chatThemeData.colorTheme.grey,
),
),
),
),
),
);
},
userItem: (user) {
final selected = widget.selectedUsers?.contains(user) ?? false;
return Container(
+117 -124
View File
@@ -1,9 +1,9 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Launch URL
Future<void> launchURL(BuildContext context, String? url) async {
@@ -27,89 +27,81 @@ Future<bool?> showConfirmationDialog(
Widget? icon,
String? question,
String? cancelText,
}) =>
showModalBottomSheet(
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
)),
builder: (context) {
final effect = StreamChatTheme.of(context).colorTheme.borderTop;
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 26),
if (icon != null) icon,
const SizedBox(height: 26),
}) {
final chatThemeData = StreamChatTheme.of(context);
return showModalBottomSheet(
backgroundColor: chatThemeData.colorTheme.white,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
)),
builder: (context) {
final effect = chatThemeData.colorTheme.borderTop;
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 26),
if (icon != null) icon,
const SizedBox(height: 26),
Text(
title,
style: chatThemeData.textTheme.headlineBold,
),
const SizedBox(height: 7),
if (question != null)
Text(
title,
style: StreamChatTheme.of(context).textTheme.headlineBold,
question,
textAlign: TextAlign.center,
),
const SizedBox(height: 7),
if (question != null)
Text(
question,
textAlign: TextAlign.center,
),
const SizedBox(height: 36),
Container(
color: effect.color!.withOpacity(effect.alpha ?? 1),
height: 1,
),
Row(
children: [
if (cancelText != null)
Flexible(
child: Container(
alignment: Alignment.center,
child: TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(
cancelText,
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.5)),
),
),
),
),
const SizedBox(height: 36),
Container(
color: effect.color!.withOpacity(effect.alpha ?? 1),
height: 1,
),
Row(
children: [
if (cancelText != null)
Flexible(
child: Container(
alignment: Alignment.center,
child: TextButton(
onPressed: () {
Navigator.pop(context, true);
Navigator.of(context).pop(false);
},
child: Text(
okText,
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentRed),
cancelText,
style: chatThemeData.textTheme.bodyBold.copyWith(
color: chatThemeData.colorTheme.black
.withOpacity(0.5)),
),
),
),
),
],
),
],
),
);
});
Flexible(
child: Container(
alignment: Alignment.center,
child: TextButton(
onPressed: () {
Navigator.pop(context, true);
},
child: Text(
okText,
style: chatThemeData.textTheme.bodyBold.copyWith(
color: chatThemeData.colorTheme.accentRed),
),
),
),
),
],
),
],
),
);
});
}
/// Shows info dialog
Future<bool?> showInfoDialog(
@@ -119,63 +111,64 @@ Future<bool?> showInfoDialog(
Widget? icon,
String? details,
StreamChatThemeData? theme,
}) =>
showModalBottomSheet(
backgroundColor: theme?.colorTheme.white ??
StreamChatTheme.of(context).colorTheme.white,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
)),
builder: (context) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
height: 26,
),
if (icon != null) icon,
const SizedBox(
height: 26,
),
Text(
title,
style: theme?.textTheme.headlineBold ??
StreamChatTheme.of(context).textTheme.headlineBold,
),
const SizedBox(
height: 7,
),
if (details != null) Text(details),
const SizedBox(
height: 36,
),
Container(
color: theme?.colorTheme.black.withOpacity(.08) ??
StreamChatTheme.of(context).colorTheme.black.withOpacity(.08),
height: 1,
),
Center(
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
okText,
style: TextStyle(
color: theme?.colorTheme.black.withOpacity(0.5) ??
StreamChatTheme.of(context).colorTheme.accentBlue,
fontWeight: FontWeight.w400,
),
}) {
final chatThemeData = StreamChatTheme.of(context);
return showModalBottomSheet(
backgroundColor: theme?.colorTheme.white ?? chatThemeData.colorTheme.white,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
)),
builder: (context) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
height: 26,
),
if (icon != null) icon,
const SizedBox(
height: 26,
),
Text(
title,
style: theme?.textTheme.headlineBold ??
chatThemeData.textTheme.headlineBold,
),
const SizedBox(
height: 7,
),
if (details != null) Text(details),
const SizedBox(
height: 36,
),
Container(
color: theme?.colorTheme.black.withOpacity(.08) ??
chatThemeData.colorTheme.black.withOpacity(.08),
height: 1,
),
Center(
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
okText,
style: TextStyle(
color: theme?.colorTheme.black.withOpacity(0.5) ??
chatThemeData.colorTheme.accentBlue,
fontWeight: FontWeight.w400,
),
),
),
],
),
),
],
),
);
),
);
}
/// Get random png with initials
String getRandomPicUrl(User user) =>
@@ -2,9 +2,9 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/src/video_service.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_thumbnail/video_thumbnail.dart';
import 'package:stream_chat_flutter/src/video_service.dart';
/// Widget for creating video thumbnail image
class VideoThumbnailImage extends StatefulWidget {
@@ -47,6 +47,7 @@ class VideoThumbnailImage extends StatefulWidget {
class _VideoThumbnailImageState extends State<VideoThumbnailImage> {
late Future<Uint8List?> thumbnailFuture;
late StreamChatThemeData _streamChatTheme;
@override
void initState() {
@@ -57,6 +58,12 @@ class _VideoThumbnailImageState extends State<VideoThumbnailImage> {
super.initState();
}
@override
void didChangeDependencies() {
_streamChatTheme = StreamChatTheme.of(context);
super.didChangeDependencies();
}
@override
void didUpdateWidget(covariant VideoThumbnailImage oldWidget) {
if (oldWidget.video != widget.video || oldWidget.format != widget.format) {
@@ -87,11 +94,8 @@ class _VideoThumbnailImageState extends State<VideoThumbnailImage> {
constraints: const BoxConstraints.expand(),
child: widget.placeholderBuilder?.call(context) ??
Shimmer.fromColors(
baseColor: StreamChatTheme.of(context)
.colorTheme
.greyGainsboro,
highlightColor:
StreamChatTheme.of(context).colorTheme.whiteSmoke,
baseColor: _streamChatTheme.colorTheme.greyGainsboro,
highlightColor: _streamChatTheme.colorTheme.whiteSmoke,
child: Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
@@ -119,12 +119,11 @@ class ChannelListCore extends StatefulWidget {
/// The current state of the [ChannelListCore].
class ChannelListCoreState extends State<ChannelListCore> {
@override
Widget build(BuildContext context) {
final channelsBloc = ChannelsBloc.of(context);
late ChannelsBlocState _channelsBloc;
StreamChatCoreState? _streamChatCoreState;
return _buildListView(channelsBloc);
}
@override
Widget build(BuildContext context) => _buildListView(_channelsBloc);
StreamBuilder<List<Channel>> _buildListView(
ChannelsBlocState channelsBlocState,
@@ -147,49 +146,52 @@ class ChannelListCoreState extends State<ChannelListCore> {
);
/// Fetches initial channels and updates the widget
Future<void> loadData() {
final channelsBloc = ChannelsBloc.of(context);
return channelsBloc.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
}
Future<void> loadData() => _channelsBloc.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
/// Fetches more channels with updated pagination and updates the widget
Future<void> paginateData() {
final channelsBloc = ChannelsBloc.of(context);
return channelsBloc.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination.copyWith(
offset: channelsBloc.channels?.length ?? 0,
),
options: widget.options,
);
}
Future<void> paginateData() => _channelsBloc.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination.copyWith(
offset: _channelsBloc.channels?.length ?? 0,
),
options: widget.options,
);
late StreamSubscription<Event> _subscription;
StreamSubscription<Event>? _subscription;
@override
void initState() {
super.initState();
loadData();
final client = StreamChatCore.of(context).client;
_subscription = client
.on(
EventType.connectionRecovered,
EventType.notificationAddedToChannel,
EventType.notificationMessageNew,
EventType.channelVisible,
)
.listen((event) => loadData());
_setupController();
}
if (widget.channelListController != null) {
widget.channelListController!.loadData = loadData;
widget.channelListController!.paginateData = paginateData;
@override
void didChangeDependencies() {
_channelsBloc = ChannelsBloc.of(context);
final newStreamChatCoreState = StreamChatCore.of(context);
if (newStreamChatCoreState != _streamChatCoreState) {
_streamChatCoreState = newStreamChatCoreState;
loadData();
final client = _streamChatCoreState!.client;
_subscription?.cancel();
_subscription = client
.on(
EventType.connectionRecovered,
EventType.notificationAddedToChannel,
EventType.notificationMessageNew,
EventType.channelVisible,
)
.listen((event) => loadData());
}
super.didChangeDependencies();
}
@override
@@ -203,11 +205,22 @@ class ChannelListCoreState extends State<ChannelListCore> {
oldWidget.pagination.toJson().toString()) {
loadData();
}
if (widget.channelListController != oldWidget.channelListController) {
_setupController();
}
}
void _setupController() {
if (widget.channelListController != null) {
widget.channelListController!.loadData = loadData;
widget.channelListController!.paginateData = paginateData;
}
}
@override
void dispose() {
_subscription.cancel();
_subscription?.cancel();
super.dispose();
}
}
@@ -50,17 +50,20 @@ class ChannelsBloc extends StatefulWidget {
streamChatState = context.findAncestorStateOfType<ChannelsBlocState>();
if (streamChatState == null) {
throw Exception('You must have a ChannelsBloc widget as ancestor');
}
assert(
streamChatState != null,
'You must have a ChannelsBloc widget as ancestor',
);
return streamChatState;
return streamChatState!;
}
}
/// The current state of the [ChannelsBloc].
class ChannelsBlocState extends State<ChannelsBloc>
with AutomaticKeepAliveClientMixin {
StreamChatCoreState? _streamChatCoreState;
@override
Widget build(BuildContext context) {
super.build(context);
@@ -86,6 +89,8 @@ class ChannelsBlocState extends State<ChannelsBloc>
bool _paginationEnded = false;
final List<StreamSubscription> _subscriptions = [];
/// Calls [client.queryChannels] updating [queryChannelsLoading] stream
Future<void> queryChannels({
Filter? filter,
@@ -93,7 +98,7 @@ class ChannelsBlocState extends State<ChannelsBloc>
PaginationParams paginationParams = const PaginationParams(limit: 30),
Map<String, dynamic>? options,
}) async {
final client = StreamChatCore.of(context).client;
final client = _streamChatCoreState!.client;
final clear = paginationParams.offset == 0;
@@ -139,77 +144,88 @@ class ChannelsBlocState extends State<ChannelsBloc>
}
}
final List<StreamSubscription> _subscriptions = [];
@override
void initState() {
super.initState();
void didChangeDependencies() {
final newStreamChatCoreState = StreamChatCore.of(context);
final client = StreamChatCore.of(context).client;
if (newStreamChatCoreState != _streamChatCoreState) {
_streamChatCoreState = newStreamChatCoreState;
final client = _streamChatCoreState!.client;
if (!widget.lockChannelsOrder) {
_subscriptions.add(client
.on(
EventType.messageNew,
)
.listen((e) {
final newChannels = List<Channel>.from(channels ?? []);
final index = newChannels.indexWhere((c) => c.cid == e.cid);
if (index != -1) {
if (index > 0) {
final channel = newChannels.removeAt(index);
newChannels.insert(0, channel);
}
} else if (widget.shouldAddChannel?.call(e) == true) {
final hiddenIndex = _hiddenChannels.indexWhere((c) => c.cid == e.cid);
if (hiddenIndex != -1) {
newChannels.insert(0, _hiddenChannels[hiddenIndex]);
_hiddenChannels.removeAt(hiddenIndex);
} else {
if (client.state.channels[e.cid] != null) {
newChannels.insert(0, client.state.channels[e.cid]!);
_cancelSubscriptions();
if (!widget.lockChannelsOrder) {
_subscriptions.add(client
.on(
EventType.messageNew,
)
.listen((e) {
final newChannels = List<Channel>.from(channels ?? []);
final index = newChannels.indexWhere((c) => c.cid == e.cid);
if (index != -1) {
if (index > 0) {
final channel = newChannels.removeAt(index);
newChannels.insert(0, channel);
}
} else if (widget.shouldAddChannel?.call(e) == true) {
final hiddenIndex =
_hiddenChannels.indexWhere((c) => c.cid == e.cid);
if (hiddenIndex != -1) {
newChannels.insert(0, _hiddenChannels[hiddenIndex]);
_hiddenChannels.removeAt(hiddenIndex);
} else {
if (client.state.channels[e.cid] != null) {
newChannels.insert(0, client.state.channels[e.cid]!);
}
}
}
}
if (widget.channelsComparator != null) {
newChannels.sort(widget.channelsComparator);
}
_channelsController.add(newChannels);
}));
if (widget.channelsComparator != null) {
newChannels.sort(widget.channelsComparator);
}
_channelsController.add(newChannels);
}));
}
_subscriptions
..add(client.on(EventType.channelHidden).listen((event) async {
final newChannels = List<Channel>.from(channels ?? []);
final channelIndex =
newChannels.indexWhere((c) => c.cid == event.cid);
if (channelIndex > -1) {
final channel = newChannels.removeAt(channelIndex);
_hiddenChannels.add(channel);
_channelsController.add(newChannels);
}
}))
..add(client
.on(
EventType.channelDeleted,
EventType.notificationRemovedFromChannel,
)
.listen((e) {
final channel = e.channel;
_channelsController.add(List.from(
(channels ?? [])..removeWhere((c) => c.cid == channel?.cid)));
}));
}
_subscriptions.add(client.on(EventType.channelHidden).listen((event) async {
final newChannels = List<Channel>.from(channels ?? []);
final channelIndex = newChannels.indexWhere((c) => c.cid == event.cid);
if (channelIndex > -1) {
final channel = newChannels.removeAt(channelIndex);
_hiddenChannels.add(channel);
_channelsController.add(newChannels);
}
}));
// ignore: cascade_invocations
_subscriptions.add(client
.on(
EventType.channelDeleted,
EventType.notificationRemovedFromChannel,
)
.listen((e) {
// ignore: cascade_invocations
final channel = e.channel;
_channelsController.add(List.from(
(channels ?? [])..removeWhere((c) => c.cid == channel?.cid)));
}));
super.didChangeDependencies();
}
@override
void dispose() {
_channelsController.close();
_queryChannelsLoadingController.close();
_subscriptions.forEach((s) => s.cancel());
_cancelSubscriptions();
super.dispose();
}
void _cancelSubscriptions() {
_subscriptions
..forEach((s) => s.cancel())
..clear();
}
@override
bool get wantKeepAlive => true;
}
@@ -109,23 +109,23 @@ class MessageListCore extends StatefulWidget {
/// The current state of the [MessageListCore].
class MessageListCoreState extends State<MessageListCore> {
late StreamChannelState _streamChannel;
StreamChannelState? _streamChannel;
bool get _upToDate => _streamChannel.channel.state?.isUpToDate ?? true;
bool get _upToDate => _streamChannel!.channel.state?.isUpToDate ?? true;
bool get _isThreadConversation => widget.parentMessage != null;
OwnUser? get _currentUser => _streamChannel.channel.client.state.user;
OwnUser? get _currentUser => _streamChannel!.channel.client.state.user;
var _messages = <Message>[];
@override
Widget build(BuildContext context) {
final messagesStream = _isThreadConversation
? _streamChannel.channel.state?.threadsStream
? _streamChannel!.channel.state?.threadsStream
.where((threads) => threads.containsKey(widget.parentMessage!.id))
.map((threads) => threads[widget.parentMessage!.id])
: _streamChannel.channel.state?.messagesStream;
: _streamChannel!.channel.state?.messagesStream;
bool defaultFilter(Message m) {
final isMyMessage = m.user?.id == _currentUser?.id;
@@ -167,31 +167,58 @@ class MessageListCoreState extends State<MessageListCore> {
QueryDirection direction = QueryDirection.top,
}) {
if (!_isThreadConversation) {
return _streamChannel.queryMessages(direction: direction);
return _streamChannel!.queryMessages(direction: direction);
} else {
return _streamChannel.getReplies(widget.parentMessage!.id);
return _streamChannel!.getReplies(widget.parentMessage!.id);
}
}
@override
void didChangeDependencies() {
final newStreamChannel = StreamChannel.of(context);
if (newStreamChannel != _streamChannel) {
if (_streamChannel == null /*only first time*/ && _isThreadConversation) {
newStreamChannel.getReplies(widget.parentMessage!.id);
}
_streamChannel = newStreamChannel;
}
super.didChangeDependencies();
}
@override
void didUpdateWidget(covariant MessageListCore oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.messageListController != oldWidget.messageListController) {
_setupController();
}
if (widget.parentMessage?.id != widget.parentMessage?.id) {
if (_isThreadConversation) {
_streamChannel!.getReplies(widget.parentMessage!.id);
}
}
}
@override
void initState() {
_streamChannel = StreamChannel.of(context);
_setupController();
if (_isThreadConversation) {
_streamChannel.getReplies(widget.parentMessage!.id);
}
super.initState();
}
void _setupController() {
if (widget.messageListController != null) {
widget.messageListController!.paginateData = paginateData;
}
super.initState();
}
@override
void dispose() {
if (!_upToDate) {
_streamChannel.reloadChannel();
_streamChannel!.reloadChannel();
}
super.dispose();
}
@@ -29,17 +29,20 @@ class MessageSearchBloc extends StatefulWidget {
state = context.findAncestorStateOfType<MessageSearchBlocState>();
if (state == null) {
throw Exception('You must have a MessageSearchBloc widget as ancestor');
}
assert(
state != null,
'You must have a MessageSearchBloc widget as ancestor',
);
return state;
return state!;
}
}
/// The current state of the [MessageSearchBloc]
class MessageSearchBlocState extends State<MessageSearchBloc>
with AutomaticKeepAliveClientMixin {
late StreamChatCoreState _streamChatCoreState;
/// The current messages list
List<GetMessageResponse>? get messageResponses => _messageResponses.value;
@@ -64,7 +67,7 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
String? query,
PaginationParams? pagination,
}) async {
final client = StreamChatCore.of(context).client;
final client = _streamChatCoreState.client;
if (_queryMessagesLoadingController.value == true) return;
@@ -109,6 +112,12 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
return widget.child;
}
@override
void didChangeDependencies() {
_streamChatCoreState = StreamChatCore.of(context);
super.didChangeDependencies();
}
@override
void dispose() {
_messageResponses.close();
@@ -105,21 +105,26 @@ class MessageSearchListCore extends StatefulWidget {
/// The current state of the [MessageSearchListCore].
class MessageSearchListCoreState extends State<MessageSearchListCore> {
MessageSearchBlocState? _messageSearchBloc;
@override
void didChangeDependencies() {
super.didChangeDependencies();
loadData();
if (widget.messageSearchListController != null) {
widget.messageSearchListController!.loadData = loadData;
widget.messageSearchListController!.paginateData = paginateData;
final newMessageSearchBloc = MessageSearchBloc.of(context);
if (newMessageSearchBloc != _messageSearchBloc) {
_messageSearchBloc = newMessageSearchBloc;
loadData();
if (widget.messageSearchListController != null) {
widget.messageSearchListController!.loadData = loadData;
widget.messageSearchListController!.paginateData = paginateData;
}
}
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
final messageSearchBloc = MessageSearchBloc.of(context);
return _buildListView(messageSearchBloc);
}
Widget build(BuildContext context) => _buildListView(_messageSearchBloc!);
Widget _buildListView(MessageSearchBlocState messageSearchBloc) =>
StreamBuilder<List<GetMessageResponse>>(
@@ -140,30 +145,24 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
);
/// Fetches initial messages and updates the widget
Future<void> loadData() {
final messageSearchBloc = MessageSearchBloc.of(context);
return messageSearchBloc.search(
filter: widget.filters,
sort: widget.sortOptions,
query: widget.messageQuery,
pagination: widget.paginationParams,
messageFilter: widget.messageFilters,
);
}
Future<void> loadData() => _messageSearchBloc!.search(
filter: widget.filters,
sort: widget.sortOptions,
query: widget.messageQuery,
pagination: widget.paginationParams,
messageFilter: widget.messageFilters,
);
/// Fetches more messages with updated pagination and updates the widget
Future<void> paginateData() {
final messageSearchBloc = MessageSearchBloc.of(context);
return messageSearchBloc.search(
filter: widget.filters,
sort: widget.sortOptions,
pagination: widget.paginationParams!.copyWith(
offset: messageSearchBloc.messageResponses?.length ?? 0,
),
query: widget.messageQuery,
messageFilter: widget.messageFilters,
);
}
Future<void> paginateData() => _messageSearchBloc!.search(
filter: widget.filters,
sort: widget.sortOptions,
pagination: widget.paginationParams!.copyWith(
offset: _messageSearchBloc!.messageResponses?.length ?? 0,
),
query: widget.messageQuery,
messageFilter: widget.messageFilters,
);
@override
void didUpdateWidget(MessageSearchListCore oldWidget) {
@@ -47,13 +47,12 @@ class StreamChannel extends StatefulWidget {
streamChannelState = context.findAncestorStateOfType<StreamChannelState>();
if (streamChannelState == null) {
throw Exception(
'You must have a StreamChannel widget at the top of your widget tree',
);
}
assert(
streamChannelState != null,
'You must have a StreamChannel widget at the top of your widget tree',
);
return streamChannelState;
return streamChannelState!;
}
@override
@@ -70,12 +70,12 @@ class StreamChatCore extends StatefulWidget {
streamChatState = context.findAncestorStateOfType<StreamChatCoreState>();
if (streamChatState == null) {
throw Exception(
'You must have a StreamChat widget at the top of your widget tree');
}
assert(
streamChatState != null,
'You must have a StreamChat widget at the top of your widget tree',
);
return streamChatState;
return streamChatState!;
}
}
@@ -122,29 +122,29 @@ class UserListCore extends StatefulWidget {
/// The current state of the [UserListCore].
class UserListCoreState extends State<UserListCore>
with WidgetsBindingObserver {
UsersBlocState? _usersBloc;
@override
void didChangeDependencies() {
super.didChangeDependencies();
loadData();
if (widget.userListController != null) {
widget.userListController!.loadData = loadData;
widget.userListController!.paginateData = paginateData;
final newUsersBloc = UsersBloc.of(context);
if (newUsersBloc != _usersBloc) {
_usersBloc = newUsersBloc;
loadData();
if (widget.userListController != null) {
widget.userListController!.loadData = loadData;
widget.userListController!.paginateData = paginateData;
}
}
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
final _usersBloc = UsersBloc.of(context);
return _buildListView(_usersBloc);
}
Widget build(BuildContext context) => _buildListView();
bool get _isListAlreadySorted =>
widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false;
Stream<List<ListItem>> _buildUserStream(
UsersBlocState usersBlocState,
) =>
usersBlocState.usersStream.map(
Stream<List<ListItem>> _buildUserStream() => _usersBloc!.usersStream.map(
(users) {
if (widget.groupAlphabetically) {
var temp = users;
@@ -169,11 +169,8 @@ class UserListCoreState extends State<UserListCore>
},
);
StreamBuilder<List<ListItem>> _buildListView(
UsersBlocState usersBlocState,
) =>
StreamBuilder(
stream: _buildUserStream(usersBlocState),
StreamBuilder<List<ListItem>> _buildListView() => StreamBuilder(
stream: _buildUserStream(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return widget.errorBuilder(snapshot.error!);
@@ -190,28 +187,22 @@ class UserListCoreState extends State<UserListCore>
);
// ignore: public_member_api_docs
Future<void> loadData() {
final _usersBloc = UsersBloc.of(context);
return _usersBloc.queryUsers(
filter: widget.filter,
sort: widget.sort,
pagination: widget.pagination,
options: widget.options,
);
}
Future<void> loadData() => _usersBloc!.queryUsers(
filter: widget.filter,
sort: widget.sort,
pagination: widget.pagination,
options: widget.options,
);
// ignore: public_member_api_docs
Future<void> paginateData() {
final _usersBloc = UsersBloc.of(context);
return _usersBloc.queryUsers(
filter: widget.filter,
sort: widget.sort,
pagination: widget.pagination!.copyWith(
offset: _usersBloc.users?.length ?? 0,
),
options: widget.options,
);
}
Future<void> paginateData() => _usersBloc!.queryUsers(
filter: widget.filter,
sort: widget.sort,
pagination: widget.pagination!.copyWith(
offset: _usersBloc!.users?.length ?? 0,
),
options: widget.options,
);
@override
void didUpdateWidget(UserListCore oldWidget) {
@@ -30,11 +30,12 @@ class UsersBloc extends StatefulWidget {
state = context.findAncestorStateOfType<UsersBlocState>();
if (state == null) {
throw Exception('You must have a UsersBloc widget as ancestor');
}
assert(
state != null,
'You must have a UsersBloc widget as ancestor',
);
return state;
return state!;
}
}
@@ -54,6 +55,8 @@ class UsersBlocState extends State<UsersBloc>
/// The stream notifying the state of queryUsers call
Stream<bool> get queryUsersLoading => _queryUsersLoadingController.stream;
late StreamChatCoreState _streamChatCore;
/// The Query Users method allows you to search for users and see if they are
/// online/offline.
/// [API Reference](https://getstream.io/chat/docs/flutter-dart/query_users/?language=dart)
@@ -63,7 +66,7 @@ class UsersBlocState extends State<UsersBloc>
Map<String, dynamic>? options,
PaginationParams? pagination,
}) async {
final client = StreamChatCore.of(context).client;
final client = _streamChatCore.client;
if (_queryUsersLoadingController.value == true) return;
@@ -101,6 +104,12 @@ class UsersBlocState extends State<UsersBloc>
}
}
@override
void didChangeDependencies() {
_streamChatCore = StreamChatCore.of(context);
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
super.build(context);
@@ -46,7 +46,7 @@ void main() {
await tester.pumpWidget(channelListCore);
expect(find.byKey(channelListCoreKey), findsNothing);
expect(tester.takeException(), isInstanceOf<Exception>());
expect(tester.takeException(), isInstanceOf<AssertionError>());
},
);
@@ -46,7 +46,7 @@ void main() {
expect(find.byKey(channelsBlocKey), findsNothing);
expect(find.byKey(childKey), findsNothing);
expect(tester.takeException(), isInstanceOf<Exception>());
expect(tester.takeException(), isInstanceOf<AssertionError>());
},
);
@@ -78,7 +78,7 @@ void main() {
await tester.pumpWidget(messageListCore);
expect(find.byKey(messageListCoreKey), findsNothing);
expect(tester.takeException(), isInstanceOf<Exception>());
expect(tester.takeException(), isInstanceOf<AssertionError>());
},
);
@@ -147,6 +147,56 @@ void main() {
},
);
testWidgets(
'should assign paginateData callback and paginate data correctly if a MessageListController is passed',
(tester) async {
const messageListCoreKey = Key('messageListCore');
final controller = MessageListController();
final messageListCore = MessageListCore(
key: messageListCoreKey,
messageListBuilder: (_, __) => Offstage(),
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(),
errorWidgetBuilder: (BuildContext context, Object error) => Offstage(),
messageListController: controller,
);
expect(controller.paginateData, isNull);
final mockChannel = MockChannel();
when(() => mockChannel.state.isUpToDate).thenReturn(true);
// when(() => mockChannel.query(
// messagesPagination: any(named: 'messagesPagination'),
// preferOffline: any(named: 'preferOffline'),
// )).thenAnswer((_) => mockChannel.state);
final messages = _generateMessages();
when(() => mockChannel.state.messages).thenReturn(messages);
when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.value(messages));
when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true));
await tester.pumpWidget(
StreamChannel(
channel: mockChannel,
child: messageListCore,
),
);
final finder = find.byKey(messageListCoreKey);
final coreState = tester.firstState<MessageListCoreState>(finder);
expect(finder, findsOneWidget);
expect(controller.paginateData, isNotNull);
await coreState.paginateData();
verify(() => mockChannel.query(
messagesPagination: any(named: 'messagesPagination'),
preferOffline: any(named: 'preferOffline'),
)).called(1);
},
);
testWidgets(
'should build error widget if messagesStream emits error',
(tester) async {
@@ -35,27 +35,12 @@ void main() {
'messageSearchBlocState.search() should throw if used where '
'StreamChat is not present in the widget tree',
(tester) async {
const messageSearchBlocKey = Key('messageSearchBloc');
const childKey = Key('child');
final messageSearchBloc = MessageSearchBloc(
key: messageSearchBlocKey,
child: Offstage(key: childKey),
child: Offstage(),
);
await tester.pumpWidget(messageSearchBloc);
expect(find.byKey(messageSearchBlocKey), findsOneWidget);
expect(find.byKey(childKey), findsOneWidget);
final usersBlocState = tester.state<MessageSearchBlocState>(
find.byKey(messageSearchBlocKey),
);
try {
await usersBlocState.search(filter: testFilter);
} catch (e) {
expect(e, isInstanceOf<Exception>());
}
expect(tester.takeException(), isInstanceOf<AssertionError>());
},
);
@@ -45,7 +45,7 @@ void main() {
await tester.pumpWidget(messageSearchListCore);
expect(find.byKey(messageSearchListCoreKey), findsNothing);
expect(tester.takeException(), isInstanceOf<Exception>());
expect(tester.takeException(), isInstanceOf<AssertionError>());
},
);
@@ -107,6 +107,7 @@ void main() {
),
),
);
await tester.pumpAndSettle();
expect(find.byKey(messageSearchListCoreKey), findsOneWidget);
expect(controller.loadData, isNotNull);
@@ -49,7 +49,7 @@ void main() {
await tester.pumpWidget(userListCore);
expect(find.byKey(userListCoreKey), findsNothing);
expect(tester.takeException(), isInstanceOf<Exception>());
expect(tester.takeException(), isInstanceOf<AssertionError>());
},
);
@@ -35,27 +35,12 @@ void main() {
'usersBlocState.queryUsers() should throw if used where '
'StreamChat is not present in the widget tree',
(tester) async {
const usersBlocKey = Key('usersBloc');
const childKey = Key('child');
final usersBloc = UsersBloc(
key: usersBlocKey,
child: Offstage(key: childKey),
child: Offstage(),
);
await tester.pumpWidget(usersBloc);
expect(find.byKey(usersBlocKey), findsOneWidget);
expect(find.byKey(childKey), findsOneWidget);
final usersBlocState = tester.state<UsersBlocState>(
find.byKey(usersBlocKey),
);
try {
await usersBlocState.queryUsers();
} catch (e) {
expect(e, isInstanceOf<Exception>());
}
expect(tester.takeException(), isInstanceOf<AssertionError>());
},
);