Merge branches 'develop' and 'rfac/transform' of https://github.com/GetStream/stream-chat-flutter into rfac/transform

 Conflicts:
	packages/stream_chat_flutter/lib/src/deleted_message.dart
	packages/stream_chat_flutter/lib/src/message_widget.dart
This commit is contained in:
Deven Joshi
2021-05-14 12:33:42 +05:30
51 changed files with 1843 additions and 1839 deletions
+4 -4
View File
@@ -79,21 +79,21 @@ class HomeScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final messages = channel.state!.channelStateStream; final messages = channel.state!.messagesStream;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Channel: ${channel.id}'), title: Text('Channel: ${channel.id}'),
), ),
body: SafeArea( body: SafeArea(
child: StreamBuilder<ChannelState?>( child: StreamBuilder<List<Message>?>(
stream: messages, stream: messages,
builder: ( builder: (
BuildContext context, BuildContext context,
AsyncSnapshot<ChannelState?> snapshot, AsyncSnapshot<List<Message>?> snapshot,
) { ) {
if (snapshot.hasData && snapshot.data != null) { if (snapshot.hasData && snapshot.data != null) {
return MessageView( return MessageView(
messages: snapshot.data!.messages.reversed.toList(), messages: snapshot.data!.reversed.toList(),
channel: channel, channel: channel,
); );
} else if (snapshot.hasError) { } else if (snapshot.hasError) {
@@ -2,7 +2,8 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:collection/collection.dart' show IterableExtension; import 'package:collection/collection.dart'
show IterableExtension, ListEquality;
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
@@ -1601,8 +1602,9 @@ class ChannelClientState {
List<Message> get messages => _channelState.messages; List<Message> get messages => _channelState.messages;
/// Channel message list as a stream /// Channel message list as a stream
Stream<List<Message>?> get messagesStream => Stream<List<Message>?> get messagesStream => channelStateStream
channelStateStream.map((cs) => cs.messages); .map((cs) => cs.messages)
.distinct((prev, next) => const ListEquality().equals(prev, next));
/// Channel pinned message list /// Channel pinned message list
List<Message>? get pinnedMessages => _channelState.pinnedMessages.toList(); List<Message>? get pinnedMessages => _channelState.pinnedMessages.toList();
@@ -17,18 +17,22 @@ class ChannelBottomSheet extends StatefulWidget {
class _ChannelBottomSheetState extends State<ChannelBottomSheet> { class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
bool _showActions = true; bool _showActions = true;
late StreamChannelState _streamChannelState;
late StreamChatThemeData _streamChatThemeData;
late StreamChatState _streamChatState;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final channel = StreamChannel.of(context).channel; final channel = _streamChannelState.channel;
final members = channel.state?.members ?? []; final members = channel.state?.members ?? [];
final userAsMember = members final userAsMember =
.firstWhere((e) => e.user?.id == StreamChat.of(context).user?.id); members.firstWhere((e) => e.user?.id == _streamChatState.user?.id);
final isOwner = userAsMember.role == 'owner'; final isOwner = userAsMember.role == 'owner';
return Material( return Material(
color: StreamChatTheme.of(context).colorTheme.white, color: _streamChatThemeData.colorTheme.white,
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
@@ -48,8 +52,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: ChannelName( child: ChannelName(
textStyle: textStyle: _streamChatThemeData.textTheme.headlineBold,
StreamChatTheme.of(context).textTheme.headlineBold,
), ),
), ),
), ),
@@ -59,10 +62,9 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
Center( Center(
child: ChannelInfo( child: ChannelInfo(
showTypingIndicator: false, showTypingIndicator: false,
channel: StreamChannel.of(context).channel, channel: _streamChannelState.channel,
textStyle: StreamChatTheme.of(context) textStyle:
.channelPreviewTheme _streamChatThemeData.channelPreviewTheme.subtitle,
.subtitle,
), ),
), ),
const SizedBox( const SizedBox(
@@ -94,8 +96,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
.user .user
?.name ?? ?.name ??
'', '',
style: style: _streamChatThemeData.textTheme.footnoteBold,
StreamChatTheme.of(context).textTheme.footnoteBold,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -128,9 +129,8 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
), ),
Text( Text(
members[index].user?.name ?? '', members[index].user?.name ?? '',
style: StreamChatTheme.of(context) style:
.textTheme _streamChatThemeData.textTheme.footnoteBold,
.footnoteBold,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -146,7 +146,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
leading: Padding( leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamSvgIcon.user( child: StreamSvgIcon.user(
color: StreamChatTheme.of(context).colorTheme.grey, color: _streamChatThemeData.colorTheme.grey,
), ),
), ),
title: 'View Info', title: 'View Info',
@@ -157,7 +157,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
leading: Padding( leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamSvgIcon.userRemove( child: StreamSvgIcon.userRemove(
color: StreamChatTheme.of(context).colorTheme.grey, color: _streamChatThemeData.colorTheme.grey,
), ),
), ),
title: 'Leave Group', title: 'Leave Group',
@@ -176,12 +176,11 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
leading: Padding( leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamSvgIcon.delete( child: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed, color: _streamChatThemeData.colorTheme.accentRed,
), ),
), ),
title: 'Delete Conversation', title: 'Delete Conversation',
titleColor: titleColor: _streamChatThemeData.colorTheme.accentRed,
StreamChatTheme.of(context).colorTheme.accentRed,
onTap: () async { onTap: () async {
setState(() { setState(() {
_showActions = false; _showActions = false;
@@ -196,7 +195,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
leading: Padding( leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamSvgIcon.closeSmall( child: StreamSvgIcon.closeSmall(
color: StreamChatTheme.of(context).colorTheme.grey, color: _streamChatThemeData.colorTheme.grey,
), ),
), ),
title: 'Cancel', 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 { Future<void> _showDeleteDialog() async {
final res = await showConfirmationDialog( final res = await showConfirmationDialog(
context, context,
@@ -217,10 +224,10 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
question: 'Are you sure you want to delete this conversation?', question: 'Are you sure you want to delete this conversation?',
cancelText: 'CANCEL', cancelText: 'CANCEL',
icon: StreamSvgIcon.delete( 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) { if (res == true) {
await channel.delete(); await channel.delete();
Navigator.pop(context); Navigator.pop(context);
@@ -235,12 +242,12 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
question: 'Are you sure you want to leave this conversation?', question: 'Are you sure you want to leave this conversation?',
cancelText: 'CANCEL', cancelText: 'CANCEL',
icon: StreamSvgIcon.userRemove( icon: StreamSvgIcon.userRemove(
color: StreamChatTheme.of(context).colorTheme.accentRed, color: _streamChatThemeData.colorTheme.accentRed,
), ),
); );
if (res == true) { if (res == true) {
final channel = StreamChannel.of(context).channel; final channel = _streamChannelState.channel;
final user = StreamChat.of(context).user; final user = _streamChatState.user;
if (user != null) { if (user != null) {
await channel.removeMembers([user.id]); await channel.removeMembers([user.id]);
} }
@@ -104,6 +104,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
final chatThemeData = StreamChatTheme.of(context);
final leadingWidget = leading ?? final leadingWidget = leading ??
(showBackButton (showBackButton
@@ -139,26 +140,18 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
brightness: Theme.of(context).brightness, brightness: Theme.of(context).brightness,
elevation: 1, elevation: 1,
leading: leadingWidget, leading: leadingWidget,
backgroundColor: StreamChatTheme.of(context) backgroundColor:
.channelTheme chatThemeData.channelTheme.channelHeaderTheme.color,
.channelHeaderTheme
.color,
actions: actions ?? actions: actions ??
<Widget>[ <Widget>[
Padding( Padding(
padding: const EdgeInsets.only(right: 10), padding: const EdgeInsets.only(right: 10),
child: Center( child: Center(
child: ChannelImage( child: ChannelImage(
borderRadius: StreamChatTheme.of(context) borderRadius: chatThemeData.channelTheme
.channelTheme .channelHeaderTheme.avatarTheme?.borderRadius,
.channelHeaderTheme constraints: chatThemeData.channelTheme
.avatarTheme .channelHeaderTheme.avatarTheme?.constraints,
?.borderRadius,
constraints: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.avatarTheme
?.constraints,
onTap: onImageTap, onTap: onImageTap,
), ),
), ),
@@ -175,20 +168,16 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
children: <Widget>[ children: <Widget>[
title ?? title ??
ChannelName( ChannelName(
textStyle: StreamChatTheme.of(context) textStyle: chatThemeData
.channelTheme .channelTheme.channelHeaderTheme.title,
.channelHeaderTheme
.title,
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
subtitle ?? subtitle ??
ChannelInfo( ChannelInfo(
showTypingIndicator: showTypingIndicator, showTypingIndicator: showTypingIndicator,
channel: channel, channel: channel,
textStyle: StreamChatTheme.of(context) textStyle: chatThemeData
.channelTheme .channelTheme.channelHeaderTheme.subtitle,
.channelHeaderTheme
.subtitle,
), ),
], ],
), ),
@@ -88,6 +88,7 @@ class ChannelImage extends StatelessWidget {
initialData: channel.extraData, initialData: channel.extraData,
builder: (context, snapshot) { builder: (context, snapshot) {
String? image; String? image;
final chatThemeData = StreamChatTheme.of(context);
if (snapshot.data!.containsKey('image') == true) { if (snapshot.data!.containsKey('image') == true) {
image = snapshot.data!['image']; image = snapshot.data!['image'];
} else if (channel.state?.members.length == 2) { } else if (channel.state?.members.length == 2) {
@@ -99,20 +100,16 @@ class ChannelImage extends StatelessWidget {
initialData: otherMember!.user, initialData: otherMember!.user,
builder: (context, snapshot) => UserAvatar( builder: (context, snapshot) => UserAvatar(
borderRadius: borderRadius ?? borderRadius: borderRadius ??
StreamChatTheme.of(context) chatThemeData
.channelPreviewTheme .channelPreviewTheme.avatarTheme?.borderRadius,
.avatarTheme
?.borderRadius,
user: snapshot.data ?? otherMember.user!, user: snapshot.data ?? otherMember.user!,
constraints: constraints ?? constraints: constraints ??
StreamChatTheme.of(context) chatThemeData
.channelPreviewTheme .channelPreviewTheme.avatarTheme?.constraints,
.avatarTheme
?.constraints,
onTap: onTap != null ? (_) => onTap!() : null, onTap: onTap != null ? (_) => onTap!() : null,
selected: selected, selected: selected,
selectionColor: selectionColor ?? selectionColor:
StreamChatTheme.of(context).colorTheme.accentBlue, selectionColor ?? chatThemeData.colorTheme.accentBlue,
selectionThickness: selectionThickness, selectionThickness: selectionThickness,
)); ));
} else { } else {
@@ -127,37 +124,25 @@ class ChannelImage extends StatelessWidget {
return GroupImage( return GroupImage(
images: images ?? [], images: images ?? [],
borderRadius: borderRadius ?? borderRadius: borderRadius ??
StreamChatTheme.of(context) chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius,
.channelPreviewTheme
.avatarTheme
?.borderRadius,
constraints: constraints ?? constraints: constraints ??
StreamChatTheme.of(context) chatThemeData.channelPreviewTheme.avatarTheme?.constraints,
.channelPreviewTheme
.avatarTheme
?.constraints,
onTap: onTap, onTap: onTap,
selected: selected, selected: selected,
selectionColor: selectionColor ?? selectionColor:
StreamChatTheme.of(context).colorTheme.accentBlue, selectionColor ?? chatThemeData.colorTheme.accentBlue,
selectionThickness: selectionThickness, selectionThickness: selectionThickness,
); );
} }
Widget child = ClipRRect( Widget child = ClipRRect(
borderRadius: borderRadius ?? borderRadius: borderRadius ??
StreamChatTheme.of(context) chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius,
.channelPreviewTheme
.avatarTheme
?.borderRadius,
child: Container( child: Container(
constraints: constraints ?? constraints: constraints ??
StreamChatTheme.of(context) chatThemeData.channelPreviewTheme.avatarTheme?.constraints,
.channelPreviewTheme
.avatarTheme
?.constraints,
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.accentBlue, color: chatThemeData.colorTheme.accentBlue,
), ),
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
@@ -172,7 +157,7 @@ class ChannelImage extends StatelessWidget {
? snapshot.data!['name'][0] ? snapshot.data!['name'][0]
: '', : '',
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -180,7 +165,7 @@ class ChannelImage extends StatelessWidget {
fit: BoxFit.cover, fit: BoxFit.cover,
) )
else else
StreamChatTheme.of(context).defaultChannelImage( chatThemeData.defaultChannelImage(
context, context,
channel, channel,
), ),
@@ -198,20 +183,13 @@ class ChannelImage extends StatelessWidget {
child = ClipRRect( child = ClipRRect(
key: const Key('selectedImage'), key: const Key('selectedImage'),
borderRadius: (borderRadius ?? borderRadius: (borderRadius ??
StreamChatTheme.of(context) chatThemeData.ownMessageTheme.avatarTheme?.borderRadius ??
.ownMessageTheme
.avatarTheme
?.borderRadius ??
BorderRadius.zero) + BorderRadius.zero) +
BorderRadius.circular(selectionThickness), BorderRadius.circular(selectionThickness),
child: Container( child: Container(
constraints: constraints ?? constraints: constraints ??
StreamChatTheme.of(context) chatThemeData.ownMessageTheme.avatarTheme?.constraints,
.ownMessageTheme color: selectionColor ?? chatThemeData.colorTheme.accentBlue,
.avatarTheme
?.constraints,
color: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue,
child: Padding( child: Padding(
padding: EdgeInsets.all(selectionThickness), padding: EdgeInsets.all(selectionThickness),
child: child, child: child,
@@ -46,7 +46,9 @@ class ChannelInfo extends StatelessWidget {
} }
Widget _buildConnectedTitleState( Widget _buildConnectedTitleState(
BuildContext context, List<Member>? members) { BuildContext context,
List<Member>? members,
) {
Widget? alternativeWidget; Widget? alternativeWidget;
if (channel.memberCount != null && channel.memberCount! > 2) { if (channel.memberCount != null && channel.memberCount! > 2) {
@@ -61,8 +63,9 @@ class ChannelInfo extends StatelessWidget {
.subtitle, .subtitle,
); );
} else { } else {
final userId = StreamChat.of(context).user?.id;
final otherMember = members?.firstWhereOrNull( final otherMember = members?.firstWhereOrNull(
(element) => element.userId != StreamChat.of(context).user?.id, (element) => element.userId != userId,
); );
if (otherMember != null) { if (otherMember != null) {
@@ -114,6 +114,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
break; break;
} }
final chatThemeData = StreamChatTheme.of(context);
return InfoTile( return InfoTile(
// ignore: avoid_bool_literals_in_conditional_expressions // ignore: avoid_bool_literals_in_conditional_expressions
showMessage: showConnectionStateTile ? showStatus : false, showMessage: showConnectionStateTile ? showStatus : false,
@@ -121,8 +122,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
child: AppBar( child: AppBar(
brightness: Theme.of(context).brightness, brightness: Theme.of(context).brightness,
elevation: 1, elevation: 1,
backgroundColor: backgroundColor: chatThemeData.channelListHeaderTheme.color,
StreamChatTheme.of(context).channelListHeaderTheme.color,
centerTitle: true, centerTitle: true,
leading: leading ?? leading: leading ??
Center( Center(
@@ -137,14 +137,10 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
} }
Scaffold.of(context).openDrawer(); Scaffold.of(context).openDrawer();
}, },
borderRadius: StreamChatTheme.of(context) borderRadius: chatThemeData
.channelListHeaderTheme .channelListHeaderTheme.avatarTheme?.borderRadius,
.avatarTheme constraints: chatThemeData
?.borderRadius, .channelListHeaderTheme.avatarTheme?.constraints,
constraints: StreamChatTheme.of(context)
.channelListHeaderTheme
.avatarTheme
?.constraints,
) )
: const Offstage(), : const Offstage(),
), ),
@@ -157,9 +153,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
Color? color; Color? color;
switch (status) { switch (status) {
case ConnectionStatus.connected: case ConnectionStatus.connected:
color = StreamChatTheme.of(context) color = chatThemeData.colorTheme.accentBlue;
.colorTheme
.accentBlue;
break; break;
case ConnectionStatus.connecting: case ConnectionStatus.connecting:
color = Colors.grey; color = Colors.grey;
@@ -209,12 +203,15 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
); );
} }
Widget _buildConnectedTitleState(BuildContext context) => Text( Widget _buildConnectedTitleState(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return Text(
'Stream Chat', 'Stream Chat',
style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith( style: chatThemeData.textTheme.headlineBold.copyWith(
color: StreamChatTheme.of(context).colorTheme.black, color: chatThemeData.colorTheme.black,
), ),
); );
}
Widget _buildConnectingTitleState(BuildContext context) => Row( Widget _buildConnectingTitleState(BuildContext context) => Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -243,16 +240,14 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
Widget _buildDisconnectedTitleState( Widget _buildDisconnectedTitleState(
BuildContext context, BuildContext context,
StreamChatClient client, StreamChatClient client,
) => ) {
Row( final chatThemeData = StreamChatTheme.of(context);
return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
'Offline...', 'Offline...',
style: StreamChatTheme.of(context) style: chatThemeData.channelListHeaderTheme.title?.copyWith(
.channelListHeaderTheme
.title
?.copyWith(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -264,18 +259,16 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
}, },
child: Text( child: Text(
'Try Again', 'Try Again',
style: StreamChatTheme.of(context) style: chatThemeData.channelListHeaderTheme.title?.copyWith(
.channelListHeaderTheme
.title
?.copyWith(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: StreamChatTheme.of(context).colorTheme.accentBlue, color: chatThemeData.colorTheme.accentBlue,
), ),
), ),
), ),
], ],
); );
}
@override @override
Size get preferredSize => const Size.fromHeight(kToolbarHeight); Size get preferredSize => const Size.fromHeight(kToolbarHeight);
@@ -237,7 +237,9 @@ class _ChannelListViewState extends State<ChannelListView> {
} }
Widget _buildEmptyWidget(BuildContext context) => LayoutBuilder( Widget _buildEmptyWidget(BuildContext context) => LayoutBuilder(
builder: (context, viewportConstraints) => SingleChildScrollView( builder: (context, viewportConstraints) {
final chatThemeData = StreamChatTheme.of(context);
return SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
child: Stack( child: Stack(
children: [ children: [
@@ -252,16 +254,14 @@ class _ChannelListViewState extends State<ChannelListView> {
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: StreamSvgIcon.message( child: StreamSvgIcon.message(
size: 136, size: 136,
color: StreamChatTheme.of(context) color: chatThemeData.colorTheme.greyGainsboro,
.colorTheme
.greyGainsboro,
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Text( child: Text(
'Lets start chatting!', 'Lets start chatting!',
style: StreamChatTheme.of(context).textTheme.headline, style: chatThemeData.textTheme.headline,
), ),
), ),
Padding( Padding(
@@ -272,12 +272,8 @@ class _ChannelListViewState extends State<ChannelListView> {
child: Text( child: Text(
'How about sending your first message to a friend?', 'How about sending your first message to a friend?',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.body.copyWith(
.textTheme color: chatThemeData.colorTheme.grey,
.body
.copyWith(
color:
StreamChatTheme.of(context).colorTheme.grey,
), ),
), ),
), ),
@@ -294,13 +290,8 @@ class _ChannelListViewState extends State<ChannelListView> {
onPressed: widget.onStartChatPressed, onPressed: widget.onStartChatPressed,
child: Text( child: Text(
'Start a chat', 'Start a chat',
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.bodyBold.copyWith(
.textTheme color: chatThemeData.colorTheme.accentBlue,
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
), ),
), ),
), ),
@@ -308,7 +299,8 @@ class _ChannelListViewState extends State<ChannelListView> {
), ),
], ],
), ),
), );
},
); );
Widget _buildLoadingWidget(BuildContext context) => ListView( Widget _buildLoadingWidget(BuildContext context) => ListView(
@@ -331,10 +323,11 @@ class _ChannelListViewState extends State<ChannelListView> {
); );
Shimmer _buildLoadingItem(BuildContext context) { Shimmer _buildLoadingItem(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
if (widget.crossAxisCount > 1) { if (widget.crossAxisCount > 1) {
return Shimmer.fromColors( return Shimmer.fromColors(
baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, baseColor: chatThemeData.colorTheme.greyGainsboro,
highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke, highlightColor: chatThemeData.colorTheme.whiteSmoke,
child: Column( child: Column(
children: [ children: [
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -362,12 +355,12 @@ class _ChannelListViewState extends State<ChannelListView> {
); );
} else { } else {
return Shimmer.fromColors( return Shimmer.fromColors(
baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, baseColor: chatThemeData.colorTheme.greyGainsboro,
highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke, highlightColor: chatThemeData.colorTheme.whiteSmoke,
child: ListTile( child: ListTile(
leading: Container( leading: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
constraints: const BoxConstraints.tightFor( constraints: const BoxConstraints.tightFor(
@@ -383,7 +376,7 @@ class _ChannelListViewState extends State<ChannelListView> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
borderRadius: BorderRadius.circular(11), borderRadius: BorderRadius.circular(11),
), ),
constraints: const BoxConstraints.tightFor( constraints: const BoxConstraints.tightFor(
@@ -400,7 +393,7 @@ class _ChannelListViewState extends State<ChannelListView> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
borderRadius: BorderRadius.circular(11), borderRadius: BorderRadius.circular(11),
), ),
constraints: const BoxConstraints.expand( constraints: const BoxConstraints.expand(
@@ -412,7 +405,7 @@ class _ChannelListViewState extends State<ChannelListView> {
Container( Container(
margin: const EdgeInsets.only(left: 16), margin: const EdgeInsets.only(left: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
borderRadius: BorderRadius.circular(11), borderRadius: BorderRadius.circular(11),
), ),
constraints: const BoxConstraints.tightFor( constraints: const BoxConstraints.tightFor(
@@ -456,12 +449,13 @@ class _ChannelListViewState extends State<ChannelListView> {
); );
Widget _listItemBuilder(BuildContext context, int i, List<Channel> channels) { Widget _listItemBuilder(BuildContext context, int i, List<Channel> channels) {
final channelsProvider = ChannelsBloc.of(context); final channelsBloc = ChannelsBloc.of(context);
if (i < channels.length) { if (i < channels.length) {
final channel = channels[i]; final channel = channels[i];
final onTap = _getChannelTap(context); final onTap = _getChannelTap(context);
final backgroundColor = StreamChatTheme.of(context).colorTheme.whiteSmoke; final chatThemeData = StreamChatTheme.of(context);
final backgroundColor = chatThemeData.colorTheme.whiteSmoke;
return StreamChannel( return StreamChannel(
key: ValueKey<String>('CHANNEL-${channel.id}'), key: ValueKey<String>('CHANNEL-${channel.id}'),
channel: channel, channel: channel,
@@ -506,7 +500,7 @@ class _ChannelListViewState extends State<ChannelListView> {
IconSlideAction( IconSlideAction(
color: backgroundColor, color: backgroundColor,
iconWidget: StreamSvgIcon.delete( iconWidget: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed, color: chatThemeData.colorTheme.accentRed,
), ),
onTap: () async { onTap: () async {
final res = await showConfirmationDialog( final res = await showConfirmationDialog(
@@ -517,7 +511,7 @@ class _ChannelListViewState extends State<ChannelListView> {
'Are you sure you want to delete this conversation?', 'Are you sure you want to delete this conversation?',
cancelText: 'CANCEL', cancelText: 'CANCEL',
icon: StreamSvgIcon.delete( icon: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed, color: chatThemeData.colorTheme.accentRed,
), ),
); );
if (res == true) { if (res == true) {
@@ -527,7 +521,7 @@ class _ChannelListViewState extends State<ChannelListView> {
), ),
], ],
child: Container( child: Container(
color: StreamChatTheme.of(context).colorTheme.whiteSnow, color: chatThemeData.colorTheme.whiteSnow,
child: widget.channelPreviewBuilder?.call(context, channel) ?? child: widget.channelPreviewBuilder?.call(context, channel) ??
ChannelPreview( ChannelPreview(
onLongPress: widget.onChannelLongPress, onLongPress: widget.onChannelLongPress,
@@ -540,7 +534,7 @@ class _ChannelListViewState extends State<ChannelListView> {
), ),
); );
} else { } else {
return _buildQueryProgressIndicator(context, channelsProvider); return _buildQueryProgressIndicator(context, channelsBloc);
} }
} }
@@ -68,6 +68,8 @@ class ChannelPreview extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final channelPreviewTheme = StreamChatTheme.of(context).channelPreviewTheme; final channelPreviewTheme = StreamChatTheme.of(context).channelPreviewTheme;
final streamChatState = StreamChat.of(context);
return StreamBuilder<bool>( return StreamBuilder<bool>(
stream: channel.isMutedStream, stream: channel.isMutedStream,
initialData: channel.isMuted, initialData: channel.isMuted,
@@ -130,7 +132,7 @@ class ChannelPreview extends StatelessWidget {
(m) => !m.isDeleted && m.shadowed != true, (m) => !m.isDeleted && m.shadowed != true,
); );
if (lastMessage?.user?.id == if (lastMessage?.user?.id ==
StreamChat.of(context).user?.id) { streamChatState.user?.id) {
return Padding( return Padding(
padding: const EdgeInsets.only(right: 4), padding: const EdgeInsets.only(right: 4),
child: SendingIndicator( child: SendingIndicator(
@@ -194,6 +196,7 @@ class ChannelPreview extends StatelessWidget {
); );
Widget _buildSubtitle(BuildContext context) { Widget _buildSubtitle(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
if (channel.isMuted) { if (channel.isMuted) {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
@@ -203,7 +206,7 @@ class ChannelPreview extends StatelessWidget {
), ),
Text( Text(
' Channel is muted', ' Channel is muted',
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle, style: chatThemeData.channelPreviewTheme.subtitle,
), ),
], ],
); );
@@ -211,7 +214,7 @@ class ChannelPreview extends StatelessWidget {
return TypingIndicator( return TypingIndicator(
channel: channel, channel: channel,
alternativeWidget: _buildLastMessage(context), alternativeWidget: _buildLastMessage(context),
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle, style: chatThemeData.channelPreviewTheme.subtitle,
); );
} }
@@ -245,30 +248,19 @@ class ChannelPreview extends StatelessWidget {
text = parts.join(' '); text = parts.join(' ');
final chatThemeData = StreamChatTheme.of(context);
return Text.rich( return Text.rich(
_getDisplayText( _getDisplayText(
text, text,
lastMessage.mentionedUsers, lastMessage.mentionedUsers,
lastMessage.attachments, lastMessage.attachments,
StreamChatTheme.of(context) chatThemeData.channelPreviewTheme.subtitle?.copyWith(
.channelPreviewTheme color: chatThemeData.channelPreviewTheme.subtitle?.color,
.subtitle
?.copyWith(
color: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle
?.color,
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
? FontStyle.italic ? FontStyle.italic
: FontStyle.normal), : FontStyle.normal),
StreamChatTheme.of(context) chatThemeData.channelPreviewTheme.subtitle?.copyWith(
.channelPreviewTheme color: chatThemeData.channelPreviewTheme.subtitle?.color,
.subtitle
?.copyWith(
color: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle
?.color,
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
? FontStyle.italic ? FontStyle.italic
: FontStyle.normal, : FontStyle.normal,
@@ -44,17 +44,18 @@ class DateDivider extends StatelessWidget {
if (uppercase) dayInfo = dayInfo.toUpperCase(); if (uppercase) dayInfo = dayInfo.toUpperCase();
final chatThemeData = StreamChatTheme.of(context);
return Center( return Center(
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1),
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.overlayDark, color: chatThemeData.colorTheme.overlayDark,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Text( child: Text(
dayInfo, dayInfo,
style: StreamChatTheme.of(context).textTheme.footnote.copyWith( style: chatThemeData.textTheme.footnote.copyWith(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
), ),
), ),
), ),
@@ -31,7 +31,9 @@ class DeletedMessage extends StatelessWidget {
final bool reverse; final bool reverse;
@override @override
Widget build(BuildContext context) => Material( Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return Material(
color: messageTheme.messageBackgroundColor, color: messageTheme.messageBackgroundColor,
shape: shape ?? shape: shape ??
RoundedRectangleBorder( RoundedRectangleBorder(
@@ -39,14 +41,8 @@ class DeletedMessage extends StatelessWidget {
side: borderSide ?? side: borderSide ??
BorderSide( BorderSide(
color: Theme.of(context).brightness == Brightness.dark color: Theme.of(context).brightness == Brightness.dark
? StreamChatTheme.of(context) ? chatThemeData.colorTheme.white.withAlpha(24)
.colorTheme : chatThemeData.colorTheme.black.withAlpha(24),
.white
.withAlpha(24)
: StreamChatTheme.of(context)
.colorTheme
.black
.withAlpha(24),
), ),
), ),
child: Padding( child: Padding(
@@ -64,3 +60,4 @@ class DeletedMessage extends StatelessWidget {
), ),
); );
} }
}
@@ -46,18 +46,12 @@ class GroupImage extends StatelessWidget {
onTap: onTap, onTap: onTap,
child: ClipRRect( child: ClipRRect(
borderRadius: borderRadius ?? borderRadius: borderRadius ??
StreamChatTheme.of(context) streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius,
.ownMessageTheme
.avatarTheme
?.borderRadius,
child: Container( child: Container(
constraints: constraints ?? constraints: constraints ??
StreamChatTheme.of(context) streamChatTheme.ownMessageTheme.avatarTheme?.constraints,
.ownMessageTheme
.avatarTheme
?.constraints,
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.accentBlue, color: streamChatTheme.colorTheme.accentBlue,
), ),
child: Flex( child: Flex(
direction: Axis.vertical, direction: Axis.vertical,
@@ -125,8 +119,7 @@ class GroupImage extends StatelessWidget {
BorderRadius.zero) + BorderRadius.zero) +
BorderRadius.circular(selectionThickness), BorderRadius.circular(selectionThickness),
child: Container( child: Container(
color: selectionColor ?? color: selectionColor ?? streamChatTheme.colorTheme.accentBlue,
StreamChatTheme.of(context).colorTheme.accentBlue,
height: 64, height: 64,
width: 64, width: 64,
child: Padding( child: Padding(
@@ -77,16 +77,18 @@ class _ImageFooterState extends State<ImageFooter> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final showShareButton = !kIsWeb; final showShareButton = !kIsWeb;
final mediaQueryData = MediaQuery.of(context);
final chatThemeData = StreamChatTheme.of(context);
return SizedBox.fromSize( return SizedBox.fromSize(
size: Size( size: Size(
MediaQuery.of(context).size.width, mediaQueryData.size.width,
MediaQuery.of(context).padding.bottom + widget.preferredSize.height, mediaQueryData.padding.bottom + widget.preferredSize.height,
), ),
child: MediaQuery.removePadding( child: MediaQuery.removePadding(
context: context, context: context,
removeTop: true, removeTop: true,
child: BottomAppBar( child: BottomAppBar(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -98,7 +100,7 @@ class _ImageFooterState extends State<ImageFooter> {
IconButton( IconButton(
icon: StreamSvgIcon.iconShare( icon: StreamSvgIcon.iconShare(
size: 24, size: 24,
color: StreamChatTheme.of(context).colorTheme.black, color: chatThemeData.colorTheme.black,
), ),
onPressed: () async { onPressed: () async {
final attachment = final attachment =
@@ -134,8 +136,7 @@ class _ImageFooterState extends State<ImageFooter> {
children: <Widget>[ children: <Widget>[
Text( Text(
'${widget.currentPage + 1} of ${widget.totalPages}', '${widget.currentPage + 1} of ${widget.totalPages}',
style: style: chatThemeData.textTheme.headlineBold,
StreamChatTheme.of(context).textTheme.headlineBold,
), ),
], ],
), ),
@@ -143,7 +144,7 @@ class _ImageFooterState extends State<ImageFooter> {
), ),
IconButton( IconButton(
icon: StreamSvgIcon.iconGrid( icon: StreamSvgIcon.iconGrid(
color: StreamChatTheme.of(context).colorTheme.black, color: chatThemeData.colorTheme.black,
), ),
onPressed: () => _showPhotosModal(context), onPressed: () => _showPhotosModal(context),
), ),
@@ -155,10 +156,11 @@ class _ImageFooterState extends State<ImageFooter> {
} }
void _showPhotosModal(context) { void _showPhotosModal(context) {
final chatThemeData = StreamChatTheme.of(context);
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
barrierColor: StreamChatTheme.of(context).colorTheme.overlay, barrierColor: chatThemeData.colorTheme.overlay,
backgroundColor: StreamChatTheme.of(context).colorTheme.white, backgroundColor: chatThemeData.colorTheme.white,
isScrollControlled: true, isScrollControlled: true,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
@@ -189,9 +191,7 @@ class _ImageFooterState extends State<ImageFooter> {
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Text( child: Text(
'Photos', 'Photos',
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.headlineBold,
.textTheme
.headlineBold,
), ),
), ),
), ),
@@ -199,7 +199,7 @@ class _ImageFooterState extends State<ImageFooter> {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: IconButton( child: IconButton(
icon: StreamSvgIcon.close( icon: StreamSvgIcon.close(
color: StreamChatTheme.of(context).colorTheme.black, color: chatThemeData.colorTheme.black,
), ),
onPressed: () => Navigator.maybePop(context), onPressed: () => Navigator.maybePop(context),
), ),
@@ -262,9 +262,7 @@ class _ImageFooterState extends State<ImageFooter> {
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
blurRadius: 8, blurRadius: 8,
color: StreamChatTheme.of(context) color: chatThemeData.colorTheme.black
.colorTheme
.black
.withOpacity(0.3), .withOpacity(0.3),
), ),
], ],
@@ -50,25 +50,26 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
final int currentIndex; final int currentIndex;
@override @override
Widget build(BuildContext context) => AppBar( Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return AppBar(
brightness: Theme.of(context).brightness, brightness: Theme.of(context).brightness,
elevation: 1, elevation: 1,
leading: showBackButton leading: showBackButton
? IconButton( ? IconButton(
icon: StreamSvgIcon.close( icon: StreamSvgIcon.close(
color: StreamChatTheme.of(context).colorTheme.black, color: chatThemeData.colorTheme.black,
size: 24, size: 24,
), ),
onPressed: onBackPressed, onPressed: onBackPressed,
) )
: const SizedBox(), : const SizedBox(),
backgroundColor: backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color,
StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color,
actions: <Widget>[ actions: <Widget>[
if (message.type != 'ephemeral') if (message.type != 'ephemeral')
IconButton( IconButton(
icon: StreamSvgIcon.iconMenuPoint( icon: StreamSvgIcon.iconMenuPoint(
color: StreamChatTheme.of(context).colorTheme.black, color: chatThemeData.colorTheme.black,
), ),
onPressed: () { onPressed: () {
_showMessageActionModalBottomSheet(context); _showMessageActionModalBottomSheet(context);
@@ -88,14 +89,11 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
userName, userName,
style: style: chatThemeData.textTheme.headlineBold,
StreamChatTheme.of(context).textTheme.headlineBold,
), ),
Text( Text(
sentAt, sentAt,
style: StreamChatTheme.of(context) style: chatThemeData.channelPreviewTheme.subtitle,
.channelPreviewTheme
.subtitle,
), ),
], ],
), ),
@@ -103,6 +101,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
) )
: const SizedBox(), : const SizedBox(),
); );
}
@override @override
final Size preferredSize; final Size preferredSize;
@@ -38,19 +38,21 @@ class InfoTile extends StatelessWidget {
final Color? backgroundColor; final Color? backgroundColor;
@override @override
Widget build(BuildContext context) => PortalEntry( Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return PortalEntry(
visible: showMessage, visible: showMessage,
portalAnchor: tileAnchor ?? Alignment.topCenter, portalAnchor: tileAnchor ?? Alignment.topCenter,
childAnchor: childAnchor ?? Alignment.bottomCenter, childAnchor: childAnchor ?? Alignment.bottomCenter,
portal: Container( portal: Container(
height: 25, height: 25,
color: backgroundColor ?? color:
StreamChatTheme.of(context).colorTheme.grey.withOpacity(0.9), backgroundColor ?? chatThemeData.colorTheme.grey.withOpacity(0.9),
child: Center( child: Center(
child: Text( child: Text(
message, message,
style: textStyle ?? style: textStyle ??
StreamChatTheme.of(context).textTheme.body.copyWith( chatThemeData.textTheme.body.copyWith(
color: Colors.white, color: Colors.white,
), ),
maxLines: 1, maxLines: 1,
@@ -61,3 +63,4 @@ class InfoTile extends StatelessWidget {
child: child, child: child,
); );
} }
}
@@ -56,6 +56,7 @@ class _MediaListViewState extends State<MediaListView> {
position, position,
) { ) {
final media = _media.elementAt(position); final media = _media.elementAt(position);
final chatThemeData = StreamChatTheme.of(context);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 1, vertical: 1), padding: const EdgeInsets.symmetric(horizontal: 1, vertical: 1),
child: InkWell( child: InkWell(
@@ -89,10 +90,8 @@ class _MediaListViewState extends State<MediaListView> {
? 1.0 ? 1.0
: 0.0, : 0.0,
child: Container( child: Container(
color: StreamChatTheme.of(context) color:
.colorTheme chatThemeData.colorTheme.black.withOpacity(0.5),
.black
.withOpacity(0.5),
alignment: Alignment.topRight, alignment: Alignment.topRight,
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 8, top: 8,
@@ -100,13 +99,10 @@ class _MediaListViewState extends State<MediaListView> {
), ),
child: CircleAvatar( child: CircleAvatar(
radius: 12, radius: 12,
backgroundColor: backgroundColor: chatThemeData.colorTheme.white,
StreamChatTheme.of(context).colorTheme.white,
child: StreamSvgIcon.check( child: StreamSvgIcon.check(
size: 24, size: 24,
color: StreamChatTheme.of(context) color: chatThemeData.colorTheme.black,
.colorTheme
.black,
), ),
), ),
), ),
@@ -128,7 +124,7 @@ class _MediaListViewState extends State<MediaListView> {
child: Text( child: Text(
media.videoDuration.format(), media.videoDuration.format(),
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
), ),
), ),
), ),
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// This widget is used for showing user tiles for mentions /// This widget is used for showing user tiles for mentions
@@ -32,7 +31,9 @@ class MentionTile extends StatelessWidget {
final Widget? trailing; final Widget? trailing;
@override @override
Widget build(BuildContext context) => SizedBox( Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return SizedBox(
height: 56, height: 56,
child: Row( child: Row(
children: [ children: [
@@ -64,7 +65,7 @@ class MentionTile extends StatelessWidget {
member.user!.name, member.user!.name,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: StreamChatTheme.of(context).textTheme.bodyBold, style: chatThemeData.textTheme.bodyBold,
), ),
const SizedBox( const SizedBox(
height: 2, height: 2,
@@ -74,12 +75,8 @@ class MentionTile extends StatelessWidget {
'@${member.userId}', '@${member.userId}',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.footnoteBold.copyWith(
.textTheme color: chatThemeData.colorTheme.grey,
.footnoteBold
.copyWith(
color:
StreamChatTheme.of(context).colorTheme.grey,
), ),
), ),
], ],
@@ -93,10 +90,11 @@ class MentionTile extends StatelessWidget {
left: 8, left: 8,
), ),
child: StreamSvgIcon.mentions( child: StreamSvgIcon.mentions(
color: StreamChatTheme.of(context).colorTheme.accentBlue, color: chatThemeData.colorTheme.accentBlue,
), ),
), ),
], ],
), ),
); );
} }
}
@@ -3,12 +3,12 @@ import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.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/message_action.dart';
import 'package:stream_chat_flutter/src/reaction_picker.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/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.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 /// Constructs a modal with actions for a message
class MessageActionsModal extends StatefulWidget { class MessageActionsModal extends StatefulWidget {
@@ -108,7 +108,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
Widget build(BuildContext context) => _showMessageOptionsModal(); Widget build(BuildContext context) => _showMessageOptionsModal();
Widget _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 user = StreamChat.of(context).user;
final roughMaxSize = 2 * size.width / 3; final roughMaxSize = 2 * size.width / 3;
@@ -133,6 +134,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
final hasFileAttachment = final hasFileAttachment =
widget.message.attachments.any((it) => it.type == 'file') == true; widget.message.attachments.any((it) => it.type == 'file') == true;
final streamChatThemeData = StreamChatTheme.of(context);
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () => Navigator.maybePop(context), onTap: () => Navigator.maybePop(context),
@@ -145,7 +147,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
sigmaY: 10, sigmaY: 10,
), ),
child: Container( 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, left: widget.reverse ? 0 : 40,
), ),
child: SizedBox( child: SizedBox(
width: MediaQuery.of(context).size.width * 0.75, width: mediaQueryData.size.width * 0.75,
child: Material( child: Material(
color: StreamChatTheme.of(context) color: streamChatThemeData.colorTheme.whiteSnow,
.colorTheme
.whiteSnow,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
@@ -266,9 +266,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
].insertBetween( ].insertBetween(
Container( Container(
height: 1, height: 1,
color: StreamChatTheme.of(context) color: streamChatThemeData
.colorTheme .colorTheme.greyWhisper,
.greyWhisper,
), ),
), ),
), ),
@@ -310,11 +309,12 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
void _showFlagDialog() async { void _showFlagDialog() async {
final client = StreamChat.of(context).client; final client = StreamChat.of(context).client;
final streamChatThemeData = StreamChatTheme.of(context);
final answer = await showConfirmationDialog( final answer = await showConfirmationDialog(
context, context,
title: 'Flag Message', title: 'Flag Message',
icon: StreamSvgIcon.flag( icon: StreamSvgIcon.flag(
color: StreamChatTheme.of(context).colorTheme.accentRed, color: streamChatThemeData.colorTheme.accentRed,
size: 24, size: 24,
), ),
question: question:
@@ -324,7 +324,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
cancelText: 'CANCEL', cancelText: 'CANCEL',
); );
final theme = StreamChatTheme.of(context); final theme = streamChatThemeData;
if (answer == true) { if (answer == true) {
try { try {
await client.flagMessage(widget.message.id); await client.flagMessage(widget.message.id);
@@ -400,7 +400,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
); );
} }
Widget _buildReplyButton(BuildContext context) => InkWell( Widget _buildReplyButton(BuildContext context) {
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell(
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
if (widget.onReplyTap != null) { if (widget.onReplyTap != null) {
@@ -412,36 +414,40 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
child: Row( child: Row(
children: [ children: [
StreamSvgIcon.reply( StreamSvgIcon.reply(
color: StreamChatTheme.of(context).primaryIconTheme.color, color: streamChatThemeData.primaryIconTheme.color,
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Reply', 'Reply',
style: StreamChatTheme.of(context).textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
), ),
), ),
); );
}
Widget _buildFlagButton(BuildContext context) => InkWell( Widget _buildFlagButton(BuildContext context) {
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell(
onTap: _showFlagDialog, onTap: _showFlagDialog,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
child: Row( child: Row(
children: [ children: [
StreamSvgIcon.iconFlag( StreamSvgIcon.iconFlag(
color: StreamChatTheme.of(context).primaryIconTheme.color, color: streamChatThemeData.primaryIconTheme.color,
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Flag Message', 'Flag Message',
style: StreamChatTheme.of(context).textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
), ),
), ),
); );
}
Widget _buildDeleteButton(BuildContext context) { Widget _buildDeleteButton(BuildContext context) {
final isDeleteFailed = final isDeleteFailed =
@@ -469,7 +475,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
); );
} }
Widget _buildCopyButton(BuildContext context) => InkWell( Widget _buildCopyButton(BuildContext context) {
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell(
onTap: () async { onTap: () async {
widget.onCopyTap?.call(widget.message); widget.onCopyTap?.call(widget.message);
Navigator.pop(context); Navigator.pop(context);
@@ -480,19 +488,22 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
children: [ children: [
StreamSvgIcon.copy( StreamSvgIcon.copy(
size: 24, size: 24,
color: StreamChatTheme.of(context).primaryIconTheme.color, color: streamChatThemeData.primaryIconTheme.color,
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Copy Message', 'Copy Message',
style: StreamChatTheme.of(context).textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
), ),
), ),
); );
}
Widget _buildEditMessage(BuildContext context) => InkWell( Widget _buildEditMessage(BuildContext context) {
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell(
onTap: () async { onTap: () async {
Navigator.pop(context); Navigator.pop(context);
_showEditBottomSheet(context); _showEditBottomSheet(context);
@@ -502,21 +513,23 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
child: Row( child: Row(
children: [ children: [
StreamSvgIcon.edit( StreamSvgIcon.edit(
color: StreamChatTheme.of(context).primaryIconTheme.color, color: streamChatThemeData.primaryIconTheme.color,
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Edit Message', 'Edit Message',
style: StreamChatTheme.of(context).textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
), ),
), ),
); );
}
Widget _buildResendMessage(BuildContext context) { Widget _buildResendMessage(BuildContext context) {
final isUpdateFailed = final isUpdateFailed =
widget.message.status == MessageSendingStatus.failed_update; widget.message.status == MessageSendingStatus.failed_update;
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell( return InkWell(
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
@@ -532,12 +545,12 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
child: Row( child: Row(
children: [ children: [
StreamSvgIcon.circleUp( StreamSvgIcon.circleUp(
color: StreamChatTheme.of(context).colorTheme.accentBlue, color: streamChatThemeData.colorTheme.accentBlue,
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
isUpdateFailed ? 'Resend Edited Message' : 'Resend', 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) { void _showEditBottomSheet(BuildContext context) {
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
final streamChatThemeData = StreamChatTheme.of(context);
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
elevation: 2, elevation: 2,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
isScrollControlled: true, isScrollControlled: true,
backgroundColor: backgroundColor: streamChatThemeData.messageInputTheme.inputBackground,
StreamChatTheme.of(context).messageInputTheme.inputBackground,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(16), topLeft: Radius.circular(16),
@@ -575,8 +588,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: StreamSvgIcon.edit( child: StreamSvgIcon.edit(
color: color: streamChatThemeData.colorTheme.greyGainsboro,
StreamChatTheme.of(context).colorTheme.greyGainsboro,
), ),
), ),
const Text( const Text(
@@ -608,7 +620,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
); );
} }
Widget _buildThreadReplyButton(BuildContext context) => InkWell( Widget _buildThreadReplyButton(BuildContext context) {
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell(
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
if (widget.onThreadReplyTap != null) { if (widget.onThreadReplyTap != null) {
@@ -620,15 +634,16 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
child: Row( child: Row(
children: [ children: [
StreamSvgIcon.thread( StreamSvgIcon.thread(
color: StreamChatTheme.of(context).primaryIconTheme.color, color: streamChatThemeData.primaryIconTheme.color,
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Thread Reply', 'Thread Reply',
style: StreamChatTheme.of(context).textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
), ),
), ),
); );
} }
}
@@ -12,6 +12,7 @@ import 'package:image_picker/image_picker.dart';
import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager/photo_manager.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/media_list_view.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart';
import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart';
import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart';
@@ -19,11 +20,10 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter/src/video_service.dart';
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:substring_highlight/substring_highlight.dart'; import 'package:substring_highlight/substring_highlight.dart';
import 'package:video_compress/video_compress.dart'; import 'package:video_compress/video_compress.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Builder for attachment thumbnails /// Builder for attachment thumbnails
typedef AttachmentThumbnailBuilder = Widget Function( typedef AttachmentThumbnailBuilder = Widget Function(
@@ -299,8 +299,9 @@ class MessageInputState extends State<MessageInput> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final streamChatThemeData = StreamChatTheme.of(context);
Widget child = Container( Widget child = Container(
color: StreamChatTheme.of(context).messageInputTheme.inputBackground, color: streamChatThemeData.messageInputTheme.inputBackground,
child: SafeArea( child: SafeArea(
child: GestureDetector( child: GestureDetector(
onPanUpdate: (details) { onPanUpdate: (details) {
@@ -325,9 +326,7 @@ class MessageInputState extends State<MessageInput> {
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: StreamSvgIcon.reply( child: StreamSvgIcon.reply(
color: StreamChatTheme.of(context) color: streamChatThemeData.colorTheme.greyGainsboro,
.colorTheme
.greyGainsboro,
), ),
), ),
const Text( const Text(
@@ -385,7 +384,9 @@ class MessageInputState extends State<MessageInput> {
], ],
); );
Widget _buildDmCheckbox() => Row( Widget _buildDmCheckbox() {
final streamChatThemeData = StreamChatTheme.of(context);
return Row(
children: [ children: [
Container( Container(
height: 16, height: 16,
@@ -394,10 +395,7 @@ class MessageInputState extends State<MessageInput> {
border: _sendAsDm border: _sendAsDm
? null ? null
: Border.all( : Border.all(
color: StreamChatTheme.of(context) color: streamChatThemeData.colorTheme.black.withOpacity(.5),
.colorTheme
.black
.withOpacity(.5),
width: 2, width: 2,
), ),
borderRadius: BorderRadius.circular(3), borderRadius: BorderRadius.circular(3),
@@ -406,8 +404,8 @@ class MessageInputState extends State<MessageInput> {
child: Material( child: Material(
borderRadius: BorderRadius.circular(3), borderRadius: BorderRadius.circular(3),
color: _sendAsDm color: _sendAsDm
? StreamChatTheme.of(context).colorTheme.accentBlue ? streamChatThemeData.colorTheme.accentBlue
: StreamChatTheme.of(context).colorTheme.white, : streamChatThemeData.colorTheme.white,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
setState(() { setState(() {
@@ -422,7 +420,7 @@ class MessageInputState extends State<MessageInput> {
: CrossFadeState.showSecond, : CrossFadeState.showSecond,
firstChild: StreamSvgIcon.check( firstChild: StreamSvgIcon.check(
size: 16, size: 16,
color: StreamChatTheme.of(context).colorTheme.white, color: streamChatThemeData.colorTheme.white,
), ),
secondChild: const SizedBox( secondChild: const SizedBox(
height: 16, height: 16,
@@ -437,16 +435,14 @@ class MessageInputState extends State<MessageInput> {
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text( child: Text(
'Also send as direct message', 'Also send as direct message',
style: StreamChatTheme.of(context).textTheme.footnote.copyWith( style: streamChatThemeData.textTheme.footnote.copyWith(
color: StreamChatTheme.of(context) color: streamChatThemeData.colorTheme.black.withOpacity(0.5),
.colorTheme
.black
.withOpacity(0.5),
), ),
), ),
), ),
], ],
); );
}
Widget _animateSendButton(BuildContext context) { Widget _animateSendButton(BuildContext context) {
final sendButton = widget.activeSendButton != null final sendButton = widget.activeSendButton != null
@@ -636,10 +632,7 @@ class MessageInputState extends State<MessageInput> {
), ),
Text( Text(
_chosenCommand?.name.toUpperCase() ?? '', _chosenCommand?.name.toUpperCase() ?? '',
style: StreamChatTheme.of(context) style: theme.textTheme.footnoteBold.copyWith(
.textTheme
.footnoteBold
.copyWith(
color: Colors.white, color: Colors.white,
), ),
), ),
@@ -839,7 +832,9 @@ class MessageInputState extends State<MessageInput> {
tween: Tween(begin: 0, end: 1), tween: Tween(begin: 0, end: 1),
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.easeInOutExpo, curve: Curves.easeInOutExpo,
builder: (context, val, wid) => Transform.scale( builder: (context, val, wid) {
final streamChatThemeData = StreamChatTheme.of(context);
return Transform.scale(
scale: val, scale: val,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
@@ -848,15 +843,13 @@ class MessageInputState extends State<MessageInput> {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
color: StreamChatTheme.of(context).colorTheme.white, color: streamChatThemeData.colorTheme.white,
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: Container( child: Container(
constraints: BoxConstraints.loose( constraints: BoxConstraints.loose(
const Size.fromHeight(400)), const Size.fromHeight(400)),
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context) color: streamChatThemeData.colorTheme.white,
.colorTheme
.white,
borderRadius: BorderRadius.circular(8)), borderRadius: BorderRadius.circular(8)),
child: ListView( child: ListView(
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
@@ -872,17 +865,15 @@ class MessageInputState extends State<MessageInput> {
horizontal: 8, horizontal: 8,
), ),
child: StreamSvgIcon.lightning( child: StreamSvgIcon.lightning(
color: StreamChatTheme.of(context) color: streamChatThemeData
.colorTheme .colorTheme.accentBlue,
.accentBlue,
), ),
), ),
Text( Text(
'Instant Commands', 'Instant Commands',
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context) color: streamChatThemeData
.colorTheme .colorTheme.black
.black
.withOpacity(.5), .withOpacity(.5),
), ),
) )
@@ -919,16 +910,11 @@ class MessageInputState extends State<MessageInput> {
TextSpan( TextSpan(
text: text:
' /${c.name} ${c.args}', ' /${c.name} ${c.args}',
style: style: streamChatThemeData
StreamChatTheme.of( .textTheme.body
context)
.textTheme
.body
.copyWith( .copyWith(
// ignore: lines_longer_than_80_chars // ignore: lines_longer_than_80_chars
color: StreamChatTheme.of( color: streamChatThemeData
// ignore: lines_longer_than_80_chars
context)
// ignore: lines_longer_than_80_chars // ignore: lines_longer_than_80_chars
.colorTheme .colorTheme
.grey, .grey,
@@ -948,7 +934,8 @@ class MessageInputState extends State<MessageInput> {
), ),
), ),
), ),
)), );
}),
)); ));
} }
@@ -956,37 +943,30 @@ class MessageInputState extends State<MessageInput> {
final _attachmentContainsFile = final _attachmentContainsFile =
_attachments.values.any((it) => it.type == 'file'); _attachments.values.any((it) => it.type == 'file');
final chatThemeData = StreamChatTheme.of(context);
Color _getIconColor(int index) { Color _getIconColor(int index) {
final streamChatThemeData = chatThemeData;
switch (index) { switch (index) {
case 0: case 0:
return _attachments.isEmpty return _attachments.isEmpty
? StreamChatTheme.of(context).colorTheme.accentBlue ? streamChatThemeData.colorTheme.accentBlue
: (!_attachmentContainsFile : (!_attachmentContainsFile
? StreamChatTheme.of(context).colorTheme.accentBlue ? streamChatThemeData.colorTheme.accentBlue
: StreamChatTheme.of(context) : streamChatThemeData.colorTheme.black.withOpacity(0.2));
.colorTheme
.black
.withOpacity(0.2));
case 1: case 1:
return _attachmentContainsFile return _attachmentContainsFile
? StreamChatTheme.of(context).colorTheme.accentBlue ? streamChatThemeData.colorTheme.accentBlue
: (_attachments.isEmpty : (_attachments.isEmpty
? StreamChatTheme.of(context) ? streamChatThemeData.colorTheme.black.withOpacity(0.5)
.colorTheme : streamChatThemeData.colorTheme.black.withOpacity(0.2));
.black
.withOpacity(0.5)
: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.2));
case 2: case 2:
return _attachmentContainsFile && _attachments.isNotEmpty return _attachmentContainsFile && _attachments.isNotEmpty
? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) ? streamChatThemeData.colorTheme.black.withOpacity(0.2)
: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); : streamChatThemeData.colorTheme.black.withOpacity(0.5);
case 3: case 3:
return _attachmentContainsFile && _attachments.isNotEmpty return _attachmentContainsFile && _attachments.isNotEmpty
? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) ? streamChatThemeData.colorTheme.black.withOpacity(0.2)
: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); : streamChatThemeData.colorTheme.black.withOpacity(0.5);
default: default:
return Colors.black; return Colors.black;
} }
@@ -997,7 +977,7 @@ class MessageInputState extends State<MessageInput> {
_animateContainer ? const Duration(milliseconds: 300) : Duration.zero, _animateContainer ? const Duration(milliseconds: 300) : Duration.zero,
height: _openFilePickerSection ? _filePickerSize : 0, height: _openFilePickerSection ? _filePickerSize : 0,
child: Material( child: Material(
color: StreamChatTheme.of(context).colorTheme.whiteSmoke, color: chatThemeData.colorTheme.whiteSmoke,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -1061,7 +1041,7 @@ class MessageInputState extends State<MessageInput> {
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16), topLeft: Radius.circular(16),
topRight: Radius.circular(16), topRight: Radius.circular(16),
@@ -1076,8 +1056,7 @@ class MessageInputState extends State<MessageInput> {
width: 40, width: 40,
height: 4, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: chatThemeData.colorTheme.whiteSmoke,
StreamChatTheme.of(context).colorTheme.whiteSmoke,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
), ),
@@ -1090,7 +1069,7 @@ class MessageInputState extends State<MessageInput> {
Expanded( Expanded(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: _PickerWidget( child: _PickerWidget(
@@ -1166,6 +1145,7 @@ class MessageInputState extends State<MessageInput> {
} }
Widget _buildCommandIcon(String iconType) { Widget _buildCommandIcon(String iconType) {
final chatThemeData = StreamChatTheme.of(context);
switch (iconType) { switch (iconType) {
case 'giphy': case 'giphy':
return CircleAvatar( return CircleAvatar(
@@ -1176,7 +1156,7 @@ class MessageInputState extends State<MessageInput> {
); );
case 'ban': case 'ban':
return CircleAvatar( return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, backgroundColor: chatThemeData.colorTheme.accentBlue,
radius: 12, radius: 12,
child: StreamSvgIcon.iconUserDelete( child: StreamSvgIcon.iconUserDelete(
size: 16, size: 16,
@@ -1185,7 +1165,7 @@ class MessageInputState extends State<MessageInput> {
); );
case 'flag': case 'flag':
return CircleAvatar( return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, backgroundColor: chatThemeData.colorTheme.accentBlue,
radius: 12, radius: 12,
child: StreamSvgIcon.flag( child: StreamSvgIcon.flag(
size: 14, size: 14,
@@ -1194,7 +1174,7 @@ class MessageInputState extends State<MessageInput> {
); );
case 'imgur': case 'imgur':
return CircleAvatar( return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, backgroundColor: chatThemeData.colorTheme.accentBlue,
radius: 12, radius: 12,
child: ClipOval( child: ClipOval(
child: StreamSvgIcon.imgur( child: StreamSvgIcon.imgur(
@@ -1204,7 +1184,7 @@ class MessageInputState extends State<MessageInput> {
); );
case 'mute': case 'mute':
return CircleAvatar( return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, backgroundColor: chatThemeData.colorTheme.accentBlue,
radius: 12, radius: 12,
child: StreamSvgIcon.mute( child: StreamSvgIcon.mute(
size: 16, size: 16,
@@ -1213,7 +1193,7 @@ class MessageInputState extends State<MessageInput> {
); );
case 'unban': case 'unban':
return CircleAvatar( return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, backgroundColor: chatThemeData.colorTheme.accentBlue,
radius: 12, radius: 12,
child: StreamSvgIcon.userAdd( child: StreamSvgIcon.userAdd(
size: 16, size: 16,
@@ -1222,7 +1202,7 @@ class MessageInputState extends State<MessageInput> {
); );
case 'unmute': case 'unmute':
return CircleAvatar( return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, backgroundColor: chatThemeData.colorTheme.accentBlue,
radius: 12, radius: 12,
child: StreamSvgIcon.volumeUp( child: StreamSvgIcon.volumeUp(
size: 16, size: 16,
@@ -1231,7 +1211,7 @@ class MessageInputState extends State<MessageInput> {
); );
default: default:
return CircleAvatar( return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, backgroundColor: chatThemeData.colorTheme.accentBlue,
radius: 12, radius: 12,
child: StreamSvgIcon.lightning( child: StreamSvgIcon.lightning(
size: 16, size: 16,
@@ -1249,17 +1229,14 @@ class MessageInputState extends State<MessageInput> {
Future<List<Member>>? queryMembers; Future<List<Member>>? queryMembers;
final channelState = StreamChannel.of(context);
if (query.isNotEmpty) { if (query.isNotEmpty) {
queryMembers = StreamChannel.of(context) queryMembers = channelState.channel
.channel
.queryMembers(filter: Filter.autoComplete('name', query)) .queryMembers(filter: Filter.autoComplete('name', query))
.then((res) => res.members); .then((res) => res.members);
} }
final members = StreamChannel.of(context) final members = channelState.channel.state?.members
.channel
.state
?.members
.where((m) => m.user?.name.toLowerCase().contains(query) == true) .where((m) => m.user?.name.toLowerCase().contains(query) == true)
.toList() ?? .toList() ??
[]; [];
@@ -1281,12 +1258,14 @@ class MessageInputState extends State<MessageInput> {
tween: Tween(begin: 0, end: 1), tween: Tween(begin: 0, end: 1),
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.easeInOutExpo, curve: Curves.easeInOutExpo,
builder: (context, val, wid) => Transform.scale( builder: (context, val, wid) {
final chatThemeData = StreamChatTheme.of(context);
return Transform.scale(
scale: val, scale: val,
child: Card( child: Card(
margin: const EdgeInsets.all(8), margin: const EdgeInsets.all(8),
elevation: 2, elevation: 2,
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
@@ -1294,7 +1273,7 @@ class MessageInputState extends State<MessageInput> {
child: Container( child: Container(
constraints: BoxConstraints.loose(const Size.fromHeight(240)), constraints: BoxConstraints.loose(const Size.fromHeight(240)),
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
), ),
child: FutureBuilder<List<Member>>( child: FutureBuilder<List<Member>>(
future: queryMembers ?? Future.value(members), future: queryMembers ?? Future.value(members),
@@ -1310,8 +1289,7 @@ class MessageInputState extends State<MessageInput> {
.where((it) => it.user != null) .where((it) => it.user != null)
.map( .map(
(m) => Material( (m) => Material(
color: color: chatThemeData.colorTheme.white,
StreamChatTheme.of(context).colorTheme.white,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
if (m.user != null) { if (m.user != null) {
@@ -1350,7 +1328,8 @@ class MessageInputState extends State<MessageInput> {
), ),
), ),
), ),
), );
},
), ),
), ),
); );
@@ -1379,15 +1358,16 @@ class MessageInputState extends State<MessageInput> {
final renderBox = context.findRenderObject() as RenderBox; final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size; final size = renderBox.size;
return OverlayEntry( return OverlayEntry(builder: (context) {
builder: (context) => Positioned( final chatThemeData = StreamChatTheme.of(context);
return Positioned(
bottom: size.height + MediaQuery.of(context).viewInsets.bottom, bottom: size.height + MediaQuery.of(context).viewInsets.bottom,
left: 0, left: 0,
right: 0, right: 0,
child: Card( child: Card(
margin: const EdgeInsets.all(8), margin: const EdgeInsets.all(8),
elevation: 2, elevation: 2,
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
@@ -1402,7 +1382,7 @@ class MessageInputState extends State<MessageInput> {
offset: Offset(0, -4), offset: Offset(0, -4),
), ),
], ],
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
), ),
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
@@ -1415,21 +1395,16 @@ class MessageInputState extends State<MessageInput> {
child: Row( child: Row(
children: [ children: [
Padding( Padding(
padding: padding: const EdgeInsets.symmetric(horizontal: 8),
const EdgeInsets.symmetric(horizontal: 8),
child: StreamSvgIcon.smile( child: StreamSvgIcon.smile(
color: StreamChatTheme.of(context) color: chatThemeData.colorTheme.accentBlue,
.colorTheme
.accentBlue,
), ),
), ),
Flexible( Flexible(
child: Text( child: Text(
'Emoji matching "$query"', 'Emoji matching "$query"',
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context) color: chatThemeData.colorTheme.black
.colorTheme
.black
.withOpacity(.5), .withOpacity(.5),
), ),
), ),
@@ -1440,6 +1415,7 @@ class MessageInputState extends State<MessageInput> {
} }
final emoji = emojis.elementAt(i - 1)!; final emoji = emojis.elementAt(i - 1)!;
final themeData = Theme.of(context);
return ListTile( return ListTile(
title: SubstringHighlight( title: SubstringHighlight(
text: text:
@@ -1447,12 +1423,11 @@ class MessageInputState extends State<MessageInput> {
"${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}",
term: query, term: query,
textStyleHighlight: textStyleHighlight:
Theme.of(context).textTheme.headline6!.copyWith( themeData.textTheme.headline6!.copyWith(
fontSize: 14.5, fontSize: 14.5,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
textStyle: textStyle: themeData.textTheme.headline6!.copyWith(
Theme.of(context).textTheme.headline6!.copyWith(
fontSize: 14.5, fontSize: 14.5,
), ),
), ),
@@ -1463,7 +1438,8 @@ class MessageInputState extends State<MessageInput> {
}), }),
), ),
), ),
)); );
});
} }
void _chooseEmoji(List<String> splits, Emoji emoji) { void _chooseEmoji(List<String> splits, Emoji emoji) {
@@ -1589,7 +1565,9 @@ class MessageInputState extends State<MessageInput> {
); );
} }
Widget _buildRemoveButton(Attachment attachment) => SizedBox( Widget _buildRemoveButton(Attachment attachment) {
final chatThemeData = StreamChatTheme.of(context);
return SizedBox(
height: 24, height: 24,
width: 24, width: 24,
child: RawMaterialButton( child: RawMaterialButton(
@@ -1603,16 +1581,16 @@ class MessageInputState extends State<MessageInput> {
onPressed: () { onPressed: () {
setState(() => _attachments.remove(attachment.id)); setState(() => _attachments.remove(attachment.id));
}, },
fillColor: fillColor: chatThemeData.colorTheme.black.withOpacity(.5),
StreamChatTheme.of(context).colorTheme.black.withOpacity(.5),
child: Center( child: Center(
child: StreamSvgIcon.close( child: StreamSvgIcon.close(
size: 24, size: 24,
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
), ),
), ),
), ),
); );
}
Widget _buildAttachment(Attachment attachment) { Widget _buildAttachment(Attachment attachment) {
if (widget.attachmentThumbnailBuilders?.containsKey(attachment.type) == if (widget.attachmentThumbnailBuilders?.containsKey(attachment.type) ==
@@ -1642,18 +1620,18 @@ class MessageInputState extends State<MessageInput> {
fit: BoxFit.cover, fit: BoxFit.cover,
errorWidget: (_, obj, trace) => errorWidget: (_, obj, trace) =>
getFileTypeImage(attachment.extraData['other'] as String?), getFileTypeImage(attachment.extraData['other'] as String?),
progressIndicatorBuilder: (context, _, progress) => progressIndicatorBuilder: (context, _, progress) {
Shimmer.fromColors( final chatThemeData = StreamChatTheme.of(context);
baseColor: return Shimmer.fromColors(
StreamChatTheme.of(context).colorTheme.greyGainsboro, baseColor: chatThemeData.colorTheme.greyGainsboro,
highlightColor: highlightColor: chatThemeData.colorTheme.whiteSmoke,
StreamChatTheme.of(context).colorTheme.whiteSmoke,
child: Image.asset( child: Image.asset(
'images/placeholder.png', 'images/placeholder.png',
fit: BoxFit.cover, fit: BoxFit.cover,
package: 'stream_chat_flutter', package: 'stream_chat_flutter',
), ),
), );
},
); );
case 'video': case 'video':
return Stack( return Stack(
@@ -1685,17 +1663,14 @@ class MessageInputState extends State<MessageInput> {
Widget _buildCommandButton() { Widget _buildCommandButton() {
final s = textEditingController.text.trim(); final s = textEditingController.text.trim();
final chatThemeData = StreamChatTheme.of(context);
return IconButton( return IconButton(
icon: StreamSvgIcon.lightning( icon: StreamSvgIcon.lightning(
color: s.isNotEmpty color: s.isNotEmpty
? StreamChatTheme.of(context).colorTheme.greyGainsboro ? chatThemeData.colorTheme.greyGainsboro
: (_commandsOverlay != null : (_commandsOverlay != null
? StreamChatTheme.of(context) ? chatThemeData.messageInputTheme.actionButtonColor
.messageInputTheme : chatThemeData.messageInputTheme.actionButtonIdleColor),
.actionButtonColor
: StreamChatTheme.of(context)
.messageInputTheme
.actionButtonIdleColor),
), ),
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
constraints: const BoxConstraints.tightFor( constraints: const BoxConstraints.tightFor(
@@ -1730,13 +1705,13 @@ class MessageInputState extends State<MessageInput> {
); );
} }
Widget _buildAttachmentButton() => IconButton( Widget _buildAttachmentButton() {
final chatThemeData = StreamChatTheme.of(context);
return IconButton(
icon: StreamSvgIcon.attach( icon: StreamSvgIcon.attach(
color: _openFilePickerSection color: _openFilePickerSection
? StreamChatTheme.of(context).messageInputTheme.actionButtonColor ? chatThemeData.messageInputTheme.actionButtonColor
: StreamChatTheme.of(context) : chatThemeData.messageInputTheme.actionButtonIdleColor,
.messageInputTheme
.actionButtonIdleColor,
), ),
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
constraints: const BoxConstraints.tightFor( constraints: const BoxConstraints.tightFor(
@@ -1763,6 +1738,7 @@ class MessageInputState extends State<MessageInput> {
} }
}, },
); );
}
/// Show the attachment modal, making the user choose where to /// Show the attachment modal, making the user choose where to
/// pick a media from /// pick a media from
@@ -2103,8 +2079,9 @@ class MessageInputState extends State<MessageInput> {
StreamSubscription? _keyboardListener; StreamSubscription? _keyboardListener;
void _showErrorAlert(String description) { void _showErrorAlert(String description) {
final chatThemeData = StreamChatTheme.of(context);
showModalBottomSheet( showModalBottomSheet(
backgroundColor: StreamChatTheme.of(context).colorTheme.white, backgroundColor: chatThemeData.colorTheme.white,
context: context, context: context,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
@@ -2118,7 +2095,7 @@ class MessageInputState extends State<MessageInput> {
height: 26, height: 26,
), ),
StreamSvgIcon.error( StreamSvgIcon.error(
color: StreamChatTheme.of(context).colorTheme.accentRed, color: chatThemeData.colorTheme.accentRed,
size: 24, size: 24,
), ),
const SizedBox( const SizedBox(
@@ -2126,7 +2103,7 @@ class MessageInputState extends State<MessageInput> {
), ),
Text( Text(
'Something went wrong', 'Something went wrong',
style: StreamChatTheme.of(context).textTheme.headlineBold, style: chatThemeData.textTheme.headlineBold,
), ),
const SizedBox( const SizedBox(
height: 7, height: 7,
@@ -2142,8 +2119,7 @@ class MessageInputState extends State<MessageInput> {
height: 36, height: 36,
), ),
Container( Container(
color: color: chatThemeData.colorTheme.black.withOpacity(.08),
StreamChatTheme.of(context).colorTheme.black.withOpacity(.08),
height: 1, height: 1,
), ),
Row( Row(
@@ -2155,13 +2131,8 @@ class MessageInputState extends State<MessageInput> {
}, },
child: Text( child: Text(
'OK', 'OK',
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.bodyBold
.textTheme .copyWith(color: chatThemeData.colorTheme.accentBlue),
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue),
), ),
), ),
], ],
@@ -2291,6 +2262,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
final chatThemeData = StreamChatTheme.of(context);
if (snapshot.data!) { if (snapshot.data!) {
if (widget.containsFile) { if (widget.containsFile) {
return GestureDetector( return GestureDetector(
@@ -2299,12 +2271,12 @@ class __PickerWidgetState extends State<_PickerWidget> {
}, },
child: Container( child: Container(
constraints: const BoxConstraints.expand(), constraints: const BoxConstraints.expand(),
color: StreamChatTheme.of(context).colorTheme.whiteSmoke, color: chatThemeData.colorTheme.whiteSmoke,
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
'Add more files', 'Add more files',
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.accentBlue, color: chatThemeData.colorTheme.accentBlue,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -2322,7 +2294,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
PhotoManager.openSetting(); PhotoManager.openSetting();
}, },
child: Container( child: Container(
color: StreamChatTheme.of(context).colorTheme.whiteSmoke, color: chatThemeData.colorTheme.whiteSmoke,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -2331,26 +2303,21 @@ class __PickerWidgetState extends State<_PickerWidget> {
'svgs/icon_picture_empty_state.svg', 'svgs/icon_picture_empty_state.svg',
package: 'stream_chat_flutter', package: 'stream_chat_flutter',
height: 140, height: 140,
color: StreamChatTheme.of(context).colorTheme.greyGainsboro, color: chatThemeData.colorTheme.greyGainsboro,
), ),
Text( Text(
// ignore: lines_longer_than_80_chars // ignore: lines_longer_than_80_chars
'Please enable access to your photos \nand videos so you can share them with friends.', 'Please enable access to your photos \nand videos so you can share them with friends.',
style: StreamChatTheme.of(context).textTheme.body.copyWith( style: chatThemeData.textTheme.body
color: StreamChatTheme.of(context).colorTheme.grey), .copyWith(color: chatThemeData.colorTheme.grey),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
Center( Center(
child: Text( child: Text(
'Allow access to your gallery', 'Allow access to your gallery',
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.bodyBold.copyWith(
.textTheme color: chatThemeData.colorTheme.accentBlue,
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
), ),
), ),
), ),
@@ -6,15 +6,15 @@ import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart'; import 'package:jiffy/jiffy.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.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/info_tile.dart';
import 'package:stream_chat_flutter/src/message_widget.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/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/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:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:visibility_detector/visibility_detector.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 /// Widget builder for message
typedef MessageBuilder = Widget Function( typedef MessageBuilder = Widget Function(
@@ -316,7 +316,9 @@ class _MessageListViewState extends State<MessageListView> {
final MessageListController _messageListController = MessageListController(); final MessageListController _messageListController = MessageListController();
@override @override
Widget build(BuildContext context) => MessageListCore( Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return MessageListCore(
messageFilter: widget.messageFilter, messageFilter: widget.messageFilter,
loadingBuilder: widget.loadingBuilder ?? loadingBuilder: widget.loadingBuilder ??
(context) => const Center( (context) => const Center(
@@ -326,18 +328,12 @@ class _MessageListViewState extends State<MessageListView> {
(context) => Center( (context) => Center(
child: Text( child: Text(
'No chats here yet...', 'No chats here yet...',
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.footnote.copyWith(
.textTheme color: chatThemeData.colorTheme.black.withOpacity(.5)),
.footnote
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5)),
), ),
), ),
messageListBuilder: widget.messageListBuilder ?? messageListBuilder:
(context, list) => _buildListView(list), widget.messageListBuilder ?? (context, list) => _buildListView(list),
messageListController: _messageListController, messageListController: _messageListController,
parentMessage: widget.parentMessage, parentMessage: widget.parentMessage,
showScrollToBottom: widget.showScrollToBottom, showScrollToBottom: widget.showScrollToBottom,
@@ -345,17 +341,12 @@ class _MessageListViewState extends State<MessageListView> {
(BuildContext context, Object error) => Center( (BuildContext context, Object error) => Center(
child: Text( child: Text(
'Something went wrong', 'Something went wrong',
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.footnote.copyWith(
.textTheme color: chatThemeData.colorTheme.black.withOpacity(.5)),
.footnote
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5)),
), ),
), ),
); );
}
Widget _buildListView(List<Message> data) { Widget _buildListView(List<Message> data) {
messages = data; messages = data;
@@ -448,10 +439,10 @@ class _MessageListViewState extends State<MessageListView> {
if (i == 0) return const SizedBox(height: 30); if (i == 0) return const SizedBox(height: 30);
if (i == messages.length + 1) { if (i == messages.length + 1) {
final replyCount = widget.parentMessage!.replyCount; final replyCount = widget.parentMessage!.replyCount;
final chatThemeData = StreamChatTheme.of(context);
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: gradient: chatThemeData.colorTheme.bgGradient,
StreamChatTheme.of(context).colorTheme.bgGradient,
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
@@ -459,10 +450,8 @@ class _MessageListViewState extends State<MessageListView> {
// ignore: lines_longer_than_80_chars // ignore: lines_longer_than_80_chars
'$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: StreamChatTheme.of(context) style: chatThemeData
.channelTheme .channelTheme.channelHeaderTheme.subtitle,
.channelHeaderTheme
.subtitle,
), ),
), ),
); );
@@ -517,13 +506,13 @@ class _MessageListViewState extends State<MessageListView> {
} }
if (i == messages.length + 1) { if (i == messages.length + 1) {
return _buildLoadingIndicator( return _buildLoadingIndicator(
streamChannel, streamChannel!,
QueryDirection.top, QueryDirection.top,
); );
} }
if (i == 0) { if (i == 0) {
return _buildLoadingIndicator( return _buildLoadingIndicator(
streamChannel, streamChannel!,
QueryDirection.bottom, QueryDirection.bottom,
); );
} }
@@ -536,7 +525,7 @@ class _MessageListViewState extends State<MessageListView> {
context, context,
message, message,
messages, messages,
streamChannel, streamChannel!,
); );
} else if (i == messages.length - 1) { } else if (i == messages.length - 1) {
messageWidget = _buildTopMessage( messageWidget = _buildTopMessage(
@@ -635,6 +624,7 @@ class _MessageListViewState extends State<MessageListView> {
final showUnreadCount = unreadCount > 0 && final showUnreadCount = unreadCount > 0 &&
streamChannel!.channel.state!.members.any((e) => streamChannel!.channel.state!.members.any((e) =>
e.userId == streamChannel!.channel.client.state.user!.id); e.userId == streamChannel!.channel.client.state.user!.id);
final chatThemeData = StreamChatTheme.of(context);
return Positioned( return Positioned(
bottom: 8, bottom: 8,
right: 8, right: 8,
@@ -644,7 +634,7 @@ class _MessageListViewState extends State<MessageListView> {
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
FloatingActionButton( FloatingActionButton(
backgroundColor: StreamChatTheme.of(context).colorTheme.white, backgroundColor: chatThemeData.colorTheme.white,
onPressed: () { onPressed: () {
if (unreadCount > 0) { if (unreadCount > 0) {
streamChannel!.channel.markRead(); streamChannel!.channel.markRead();
@@ -663,7 +653,7 @@ class _MessageListViewState extends State<MessageListView> {
} }
}, },
child: StreamSvgIcon.down( child: StreamSvgIcon.down(
color: StreamChatTheme.of(context).colorTheme.black, color: chatThemeData.colorTheme.black,
), ),
), ),
if (showUnreadCount) if (showUnreadCount)
@@ -692,12 +682,12 @@ class _MessageListViewState extends State<MessageListView> {
); );
Widget _buildLoadingIndicator( Widget _buildLoadingIndicator(
StreamChannelState? streamChannel, StreamChannelState streamChannel,
QueryDirection direction, QueryDirection direction,
) { ) {
final stream = direction == QueryDirection.top final stream = direction == QueryDirection.top
? streamChannel!.queryTopMessages ? streamChannel.queryTopMessages
: streamChannel!.queryBottomMessages; : streamChannel.queryBottomMessages;
return StreamBuilder<bool>( return StreamBuilder<bool>(
key: const Key('LOADING-INDICATOR'), key: const Key('LOADING-INDICATOR'),
stream: stream, stream: stream,
@@ -764,7 +754,7 @@ class _MessageListViewState extends State<MessageListView> {
BuildContext context, BuildContext context,
Message message, Message message,
List<Message> messages, List<Message> messages,
StreamChannelState? streamChannel, StreamChannelState streamChannel,
) { ) {
Widget messageWidget; Widget messageWidget;
if (widget.messageBuilder != null) { if (widget.messageBuilder != null) {
@@ -790,7 +780,7 @@ class _MessageListViewState extends State<MessageListView> {
onVisibilityChanged: (visibility) { onVisibilityChanged: (visibility) {
final isVisible = visibility.visibleBounds != Rect.zero; final isVisible = visibility.visibleBounds != Rect.zero;
if (isVisible) { if (isVisible) {
final channel = streamChannel!.channel; final channel = streamChannel.channel;
if (_upToDate && if (_upToDate &&
channel.config?.readEvents == true && channel.config?.readEvents == true &&
channel.state!.unreadCount! > 0) { channel.state!.unreadCount! > 0) {
@@ -811,6 +801,7 @@ class _MessageListViewState extends State<MessageListView> {
final isMyMessage = message.user!.id == StreamChat.of(context).user!.id; final isMyMessage = message.user!.id == StreamChat.of(context).user!.id;
final isOnlyEmoji = message.text!.isOnlyEmoji; final isOnlyEmoji = message.text!.isOnlyEmoji;
final chatThemeData = StreamChatTheme.of(context);
return MessageWidget( return MessageWidget(
showReplyMessage: false, showReplyMessage: false,
showResendMessage: false, showResendMessage: false,
@@ -837,8 +828,8 @@ class _MessageListViewState extends State<MessageListView> {
borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null, borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null,
showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show,
messageTheme: isMyMessage messageTheme: isMyMessage
? StreamChatTheme.of(context).ownMessageTheme ? chatThemeData.ownMessageTheme
: StreamChatTheme.of(context).otherMessageTheme, : chatThemeData.otherMessageTheme,
onShowMessage: widget.onShowMessage, onShowMessage: widget.onShowMessage,
onReturnAction: (action) { onReturnAction: (action) {
switch (action) { switch (action) {
@@ -946,6 +937,7 @@ class _MessageListViewState extends State<MessageListView> {
? BorderSide.none ? BorderSide.none
: null; : null;
final chatThemeData = StreamChatTheme.of(context);
Widget child = MessageWidget( Widget child = MessageWidget(
key: ValueKey<String>('MESSAGE-${message.id}'), key: ValueKey<String>('MESSAGE-${message.id}'),
message: message, message: message,
@@ -1023,8 +1015,8 @@ class _MessageListViewState extends State<MessageListView> {
horizontal: isOnlyEmoji ? 0 : 16.0, horizontal: isOnlyEmoji ? 0 : 16.0,
), ),
messageTheme: isMyMessage messageTheme: isMyMessage
? StreamChatTheme.of(context).ownMessageTheme ? chatThemeData.ownMessageTheme
: StreamChatTheme.of(context).otherMessageTheme, : chatThemeData.otherMessageTheme,
readList: readList, readList: readList,
allRead: allRead, allRead: allRead,
onShowMessage: widget.onShowMessage, onShowMessage: widget.onShowMessage,
@@ -1064,7 +1056,7 @@ class _MessageListViewState extends State<MessageListView> {
widget.onMessageSwiped?.call(message); widget.onMessageSwiped?.call(message);
}, },
backgroundIcon: StreamSvgIcon.reply( backgroundIcon: StreamSvgIcon.reply(
color: StreamChatTheme.of(context).colorTheme.accentBlue, color: chatThemeData.colorTheme.accentBlue,
), ),
child: child, child: child,
), ),
@@ -1074,7 +1066,7 @@ class _MessageListViewState extends State<MessageListView> {
if (!initialMessageHighlightComplete && if (!initialMessageHighlightComplete &&
widget.highlightInitialMessage && widget.highlightInitialMessage &&
_isInitialMessage(message.id)) { _isInitialMessage(message.id)) {
final colorTheme = StreamChatTheme.of(context).colorTheme; final colorTheme = chatThemeData.colorTheme;
final highlightColor = final highlightColor =
widget.messageHighlightColor ?? colorTheme.highlight; widget.messageHighlightColor ?? colorTheme.highlight;
child = TweenAnimationBuilder<Color?>( child = TweenAnimationBuilder<Color?>(
@@ -1105,8 +1097,17 @@ class _MessageListViewState extends State<MessageListView> {
_itemPositionListener = _itemPositionListener =
widget.itemPositionListener ?? ItemPositionsListener.create(); widget.itemPositionListener ?? ItemPositionsListener.create();
streamChannel = StreamChannel.of(context); _getOnThreadTap();
super.initState();
}
@override
void didChangeDependencies() {
final newStreamChannel = StreamChannel.of(context);
if (newStreamChannel != streamChannel) {
streamChannel = newStreamChannel;
_messageNewListener?.cancel();
initialIndex = _initialIndex; initialIndex = _initialIndex;
initialAlignment = _initialAlignment; initialAlignment = _initialAlignment;
@@ -1129,9 +1130,9 @@ class _MessageListViewState extends State<MessageListView> {
if (_isThreadConversation) { if (_isThreadConversation) {
streamChannel!.getReplies(widget.parentMessage!.id); streamChannel!.getReplies(widget.parentMessage!.id);
} }
}
_getOnThreadTap(); super.didChangeDependencies();
super.initState();
} }
void _getOnThreadTap() { void _getOnThreadTap() {
@@ -1,13 +1,13 @@
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; 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_bubble.dart';
import 'package:stream_chat_flutter/src/reaction_picker.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/stream_chat.dart';
import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Modal widget for displaying message reactions /// Modal widget for displaying message reactions
class MessageReactionsModal extends StatelessWidget { class MessageReactionsModal extends StatelessWidget {
@@ -176,8 +176,9 @@ class MessageReactionsModal extends StatelessWidget {
Widget _buildReactionCard(BuildContext context) { Widget _buildReactionCard(BuildContext context) {
final currentUser = StreamChat.of(context).user; final currentUser = StreamChat.of(context).user;
final chatThemeData = StreamChatTheme.of(context);
return Card( return Card(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
@@ -190,7 +191,7 @@ class MessageReactionsModal extends StatelessWidget {
children: [ children: [
Text( Text(
'Message Reactions', 'Message Reactions',
style: StreamChatTheme.of(context).textTheme.headlineBold, style: chatThemeData.textTheme.headlineBold,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Flexible( Flexible(
@@ -220,6 +221,7 @@ class MessageReactionsModal extends StatelessWidget {
BuildContext context, BuildContext context,
) { ) {
final isCurrentUser = reaction.user?.id == currentUser.id; final isCurrentUser = reaction.user?.id == currentUser.id;
final chatThemeData = StreamChatTheme.of(context);
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints.loose(const Size( constraints: BoxConstraints.loose(const Size(
64, 64,
@@ -258,7 +260,7 @@ class MessageReactionsModal extends StatelessWidget {
messageTheme.reactionsBorderColor ?? Colors.transparent, messageTheme.reactionsBorderColor ?? Colors.transparent,
backgroundColor: messageTheme.reactionsBackgroundColor ?? backgroundColor: messageTheme.reactionsBackgroundColor ??
Colors.transparent, Colors.transparent,
maskColor: StreamChatTheme.of(context).colorTheme.white, maskColor: chatThemeData.colorTheme.white,
tailCirclesSpacing: 1, tailCirclesSpacing: 1,
highlightOwnReactions: false, highlightOwnReactions: false,
), ),
@@ -269,7 +271,7 @@ class MessageReactionsModal extends StatelessWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
reaction.user!.name.split(' ')[0], reaction.user!.name.split(' ')[0],
style: StreamChatTheme.of(context).textTheme.footnoteBold, style: chatThemeData.textTheme.footnoteBold,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
@@ -35,6 +35,7 @@ class MessageSearchItem extends StatelessWidget {
final channel = getMessageResponse.channel; final channel = getMessageResponse.channel;
final channelName = channel?.extraData['name']; final channelName = channel?.extraData['name'];
final user = message.user!; final user = message.user!;
final chatThemeData = StreamChatTheme.of(context);
return ListTile( return ListTile(
onTap: onTap, onTap: onTap,
leading: UserAvatar( leading: UserAvatar(
@@ -49,21 +50,18 @@ class MessageSearchItem extends StatelessWidget {
children: [ children: [
Text( Text(
user.id == StreamChat.of(context).user?.id ? 'You' : user.name, user.id == StreamChat.of(context).user?.id ? 'You' : user.name,
style: StreamChatTheme.of(context).channelPreviewTheme.title, style: chatThemeData.channelPreviewTheme.title,
), ),
if (channelName != null) ...[ if (channelName != null) ...[
Text( Text(
' in ', ' in ',
style: StreamChatTheme.of(context) style: chatThemeData.channelPreviewTheme.title?.copyWith(
.channelPreviewTheme
.title
?.copyWith(
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
), ),
), ),
Text( Text(
channelName as String, channelName as String,
style: StreamChatTheme.of(context).channelPreviewTheme.title, style: chatThemeData.channelPreviewTheme.title,
), ),
], ],
], ],
@@ -121,17 +119,18 @@ class MessageSearchItem extends StatelessWidget {
text = parts.join(' '); text = parts.join(' ');
} }
final chatThemeData = StreamChatTheme.of(context);
return Text.rich( return Text.rich(
_getDisplayText( _getDisplayText(
text!, text!,
message.mentionedUsers, message.mentionedUsers,
message.attachments, message.attachments,
StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( chatThemeData.channelPreviewTheme.subtitle?.copyWith(
fontStyle: (message.isSystem || message.isDeleted) fontStyle: (message.isSystem || message.isDeleted)
? FontStyle.italic ? FontStyle.italic
: FontStyle.normal, : FontStyle.normal,
), ),
StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( chatThemeData.channelPreviewTheme.subtitle?.copyWith(
fontStyle: (message.isSystem || message.isDeleted) fontStyle: (message.isSystem || message.isDeleted)
? FontStyle.italic ? FontStyle.italic
: FontStyle.normal, : FontStyle.normal,
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/info_tile.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/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/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Callback called when tapping on a user /// Callback called when tapping on a user
typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); typedef MessageSearchItemTapCallback = void Function(GetMessageResponse);
@@ -270,12 +270,13 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
); );
if (widget.showResultCount) { if (widget.showResultCount) {
final chatThemeData = StreamChatTheme.of(context);
child = Column( child = Column(
children: [ children: [
Container( Container(
width: double.maxFinite, width: double.maxFinite,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: StreamChatTheme.of(context).colorTheme.bgGradient, gradient: chatThemeData.colorTheme.bgGradient,
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -285,7 +286,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
child: Text( child: Text(
'${items.length} results', '${items.length} results',
style: TextStyle( 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:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.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/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Text widget to display in message /// Text widget to display in message
class MessageText extends StatelessWidget { class MessageText extends StatelessWidget {
@@ -31,6 +31,7 @@ class MessageText extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\\\n'); final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\\\n');
final themeData = Theme.of(context);
return MarkdownBody( return MarkdownBody(
data: text, data: text,
onTapLink: ( onTapLink: (
@@ -60,8 +61,8 @@ class MessageText extends StatelessWidget {
} }
}, },
styleSheet: MarkdownStyleSheet.fromTheme( styleSheet: MarkdownStyleSheet.fromTheme(
Theme.of(context).copyWith( themeData.copyWith(
textTheme: Theme.of(context).textTheme.apply( textTheme: themeData.textTheme.apply(
bodyColor: messageTheme.messageText?.color, bodyColor: messageTheme.messageText?.color,
decoration: messageTheme.messageText?.decoration, decoration: messageTheme.messageText?.decoration,
decorationColor: messageTheme.messageText?.decorationColor, decorationColor: messageTheme.messageText?.decorationColor,
@@ -8,6 +8,8 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_portal/flutter_portal.dart';
import 'package:jiffy/jiffy.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_action.dart';
import 'package:stream_chat_flutter/src/message_actions_modal.dart'; import 'package:stream_chat_flutter/src/message_actions_modal.dart';
import 'package:stream_chat_flutter/src/message_reactions_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/reaction_bubble.dart';
import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.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 /// Widget builder for building attachments
typedef AttachmentBuilder = Widget Function( typedef AttachmentBuilder = Widget Function(
@@ -109,6 +109,7 @@ class MessageWidget extends StatefulWidget {
borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
); );
final mediaQueryData = MediaQuery.of(context);
if (attachments.length > 1) { if (attachments.length > 1) {
return Padding( return Padding(
padding: attachmentPadding, padding: attachmentPadding,
@@ -118,8 +119,8 @@ class MessageWidget extends StatefulWidget {
color: messageTheme.messageBackgroundColor, color: messageTheme.messageBackgroundColor,
child: ImageGroup( child: ImageGroup(
size: Size( size: Size(
MediaQuery.of(context).size.width * 0.8, mediaQueryData.size.width * 0.8,
MediaQuery.of(context).size.height * 0.3, mediaQueryData.size.height * 0.3,
), ),
images: attachments, images: attachments,
message: message, message: message,
@@ -142,8 +143,8 @@ class MessageWidget extends StatefulWidget {
message: message, message: message,
messageTheme: messageTheme, messageTheme: messageTheme,
size: Size( size: Size(
MediaQuery.of(context).size.width * 0.8, mediaQueryData.size.width * 0.8,
MediaQuery.of(context).size.height * 0.3, mediaQueryData.size.height * 0.3,
), ),
onShowMessage: onShowMessage, onShowMessage: onShowMessage,
onReturnAction: onReturnAction, onReturnAction: onReturnAction,
@@ -167,13 +168,14 @@ class MessageWidget extends StatefulWidget {
return wrapAttachmentWidget( return wrapAttachmentWidget(
context, context,
Column( Column(
children: attachments children: attachments.map((attachment) {
.map((attachment) => VideoAttachment( final mediaQueryData = MediaQuery.of(context);
return VideoAttachment(
attachment: attachment, attachment: attachment,
messageTheme: messageTheme, messageTheme: messageTheme,
size: Size( size: Size(
MediaQuery.of(context).size.width * 0.8, mediaQueryData.size.width * 0.8,
MediaQuery.of(context).size.height * 0.3, mediaQueryData.size.height * 0.3,
), ),
message: message, message: message,
onShowMessage: onShowMessage, onShowMessage: onShowMessage,
@@ -183,8 +185,8 @@ class MessageWidget extends StatefulWidget {
onAttachmentTap(message, attachment); onAttachmentTap(message, attachment);
} }
: null, : null,
)) );
.toList(), }).toList(),
), ),
border, border,
reverse, reverse,
@@ -200,18 +202,19 @@ class MessageWidget extends StatefulWidget {
return wrapAttachmentWidget( return wrapAttachmentWidget(
context, context,
Column( Column(
children: attachments children: attachments.map((attachment) {
.map((attachment) => GiphyAttachment( final mediaQueryData = MediaQuery.of(context);
return GiphyAttachment(
attachment: attachment, attachment: attachment,
message: message, message: message,
size: Size( size: Size(
MediaQuery.of(context).size.width * 0.8, mediaQueryData.size.width * 0.8,
MediaQuery.of(context).size.height * 0.3, mediaQueryData.size.height * 0.3,
), ),
onShowMessage: onShowMessage, onShowMessage: onShowMessage,
onReturnAction: onReturnAction, onReturnAction: onReturnAction,
)) );
.toList(), }).toList(),
), ),
border, border,
reverse, reverse,
@@ -230,21 +233,24 @@ class MessageWidget extends StatefulWidget {
return Column( return Column(
children: attachments children: attachments
.map<Widget>((attachment) => wrapAttachmentWidget( .map<Widget>((attachment) {
final mediaQueryData = MediaQuery.of(context);
return wrapAttachmentWidget(
context, context,
FileAttachment( FileAttachment(
message: message, message: message,
attachment: attachment, attachment: attachment,
size: Size( size: Size(
MediaQuery.of(context).size.width * 0.8, mediaQueryData.size.width * 0.8,
MediaQuery.of(context).size.height * 0.3, mediaQueryData.size.height * 0.3,
), ),
), ),
border, border,
reverse, reverse,
attachmentBorderRadiusGeometry as BorderRadius? ?? attachmentBorderRadiusGeometry as BorderRadius? ??
BorderRadius.zero, BorderRadius.zero,
)) );
})
.insertBetween(SizedBox( .insertBetween(SizedBox(
height: attachmentPadding.vertical / 2, height: attachmentPadding.vertical / 2,
)) ))
@@ -648,12 +654,13 @@ class _MessageWidgetState extends State<MessageWidget>
widget.onQuotedMessageTap != null widget.onQuotedMessageTap != null
? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId) ? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId)
: null; : null;
final chatThemeData = StreamChatTheme.of(context);
return QuotedMessageWidget( return QuotedMessageWidget(
onTap: onTap, onTap: onTap,
message: widget.message.quotedMessage!, message: widget.message.quotedMessage!,
messageTheme: isMyMessage messageTheme: isMyMessage
? StreamChatTheme.of(context).otherMessageTheme ? chatThemeData.otherMessageTheme
: StreamChatTheme.of(context).ownMessageTheme, : chatThemeData.ownMessageTheme,
reverse: widget.reverse, reverse: widget.reverse,
padding: EdgeInsets.only( padding: EdgeInsets.only(
right: 8, left: 8, top: 8, bottom: hasNonUrlAttachments ? 8 : 0), right: 8, left: 8, top: 8, bottom: hasNonUrlAttachments ? 8 : 0),
@@ -662,20 +669,19 @@ class _MessageWidgetState extends State<MessageWidget>
Widget get _bottomRow { Widget get _bottomRow {
if (isDeleted) { if (isDeleted) {
final chatThemeData = StreamChatTheme.of(context);
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
StreamSvgIcon.eye( StreamSvgIcon.eye(
color: StreamChatTheme.of(context).colorTheme.grey, color: chatThemeData.colorTheme.grey,
size: 16, size: 16,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Only visible to you', 'Only visible to you',
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.footnote
.textTheme .copyWith(color: chatThemeData.colorTheme.grey),
.footnote
.copyWith(color: StreamChatTheme.of(context).colorTheme.grey),
), ),
], ],
); );
@@ -41,15 +41,16 @@ class OptionListTile extends StatelessWidget {
final TextStyle? titleTextStyle; final TextStyle? titleTextStyle;
@override @override
Widget build(BuildContext context) => Column( Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return Column(
children: [ children: [
Container( Container(
color: separatorColor ?? color: separatorColor ?? chatThemeData.colorTheme.greyGainsboro,
StreamChatTheme.of(context).colorTheme.greyGainsboro,
height: 1, height: 1,
), ),
Material( Material(
color: tileColor ?? StreamChatTheme.of(context).colorTheme.white, color: tileColor ?? chatThemeData.colorTheme.white,
child: SizedBox( child: SizedBox(
height: 63, height: 63,
child: InkWell( child: InkWell(
@@ -67,11 +68,8 @@ class OptionListTile extends StatelessWidget {
title!, title!,
style: titleTextStyle ?? style: titleTextStyle ??
(titleColor == null (titleColor == null
? StreamChatTheme.of(context).textTheme.bodyBold ? chatThemeData.textTheme.bodyBold
: StreamChatTheme.of(context) : chatThemeData.textTheme.bodyBold.copyWith(
.textTheme
.bodyBold
.copyWith(
color: titleColor, color: titleColor,
)), )),
), ),
@@ -94,3 +92,4 @@ class OptionListTile extends StatelessWidget {
], ],
); );
} }
}
@@ -121,6 +121,8 @@ class ReactionBubble extends StatelessWidget {
(r) => r.type == reaction.type, (r) => r.type == reaction.type,
); );
final chatThemeData = StreamChatTheme.of(context);
final userId = StreamChat.of(context).user?.id;
return Padding( return Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 4, horizontal: 4,
@@ -130,24 +132,16 @@ class ReactionBubble extends StatelessWidget {
assetName: reactionIcon.assetName, assetName: reactionIcon.assetName,
width: 16, width: 16,
height: 16, height: 16,
color: (!highlightOwnReactions || color: (!highlightOwnReactions || reaction.user?.id == userId)
reaction.user?.id == StreamChat.of(context).user?.id) ? chatThemeData.colorTheme.accentBlue
? StreamChatTheme.of(context).colorTheme.accentBlue : chatThemeData.colorTheme.black.withOpacity(.5),
: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
) )
: Icon( : Icon(
Icons.help_outline_rounded, Icons.help_outline_rounded,
size: 16, size: 16,
color: (!highlightOwnReactions || color: (!highlightOwnReactions || reaction.user?.id == userId)
reaction.user?.id == StreamChat.of(context).user?.id) ? chatThemeData.colorTheme.accentBlue
? StreamChatTheme.of(context).colorTheme.accentBlue : chatThemeData.colorTheme.black.withOpacity(.5),
: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
), ),
); );
} }
@@ -2,8 +2,8 @@ import 'dart:math';
import 'package:ezanimation/ezanimation.dart'; import 'package:ezanimation/ezanimation.dart';
import 'package:flutter/material.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/extension.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png)
@@ -33,7 +33,8 @@ class _ReactionPickerState extends State<ReactionPicker>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final reactionIcons = StreamChatTheme.of(context).reactionIcons; final chatThemeData = StreamChatTheme.of(context);
final reactionIcons = chatThemeData.reactionIcons;
if (animations.isEmpty && reactionIcons.isNotEmpty) { if (animations.isEmpty && reactionIcons.isNotEmpty) {
reactionIcons.forEach((element) { reactionIcons.forEach((element) {
@@ -57,7 +58,7 @@ class _ReactionPickerState extends State<ReactionPicker>
scale: val, scale: val,
child: Material( child: Material(
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -119,9 +120,8 @@ class _ReactionPickerState extends State<ReactionPicker>
animations[index].value * 24.0, animations[index].value * 24.0,
), ),
color: ownReactionIndex != -1 color: ownReactionIndex != -1
? StreamChatTheme.of(context) ? chatThemeData
.colorTheme .colorTheme.accentBlue
.accentBlue
: Theme.of(context) : Theme.of(context)
.iconTheme .iconTheme
.color! .color!
@@ -97,7 +97,9 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
final List<Widget>? actions; final List<Widget>? actions;
@override @override
Widget build(BuildContext context) => AppBar( Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return AppBar(
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
brightness: Theme.of(context).brightness, brightness: Theme.of(context).brightness,
elevation: 1, elevation: 1,
@@ -109,8 +111,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
showUnreads: true, showUnreads: true,
) )
: const SizedBox()), : const SizedBox()),
backgroundColor: backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color,
StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color,
centerTitle: true, centerTitle: true,
actions: actions, actions: actions,
title: InkWell( title: InkWell(
@@ -123,10 +124,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
title ?? title ??
Text( Text(
'Thread Reply', 'Thread Reply',
style: StreamChatTheme.of(context) style: chatThemeData.channelTheme.channelHeaderTheme.title,
.channelTheme
.channelHeaderTheme
.title,
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
subtitle ?? subtitle ??
@@ -136,17 +134,13 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
children: [ children: [
Text( Text(
'with ', 'with ',
style: StreamChatTheme.of(context) style: chatThemeData
.channelTheme .channelTheme.channelHeaderTheme.subtitle,
.channelHeaderTheme
.subtitle,
), ),
Flexible( Flexible(
child: ChannelName( child: ChannelName(
textStyle: StreamChatTheme.of(context) textStyle: chatThemeData
.channelTheme .channelTheme.channelHeaderTheme.subtitle,
.channelHeaderTheme
.subtitle,
), ),
), ),
], ],
@@ -156,6 +150,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
), ),
), ),
); );
}
@override @override
final Size preferredSize; final Size preferredSize;
@@ -26,7 +26,9 @@ class UrlAttachment extends StatelessWidget {
final EdgeInsets textPadding; final EdgeInsets textPadding;
@override @override
Widget build(BuildContext context) => GestureDetector( Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return GestureDetector(
onTap: () { onTap: () {
launchURL( launchURL(
context, context,
@@ -58,8 +60,7 @@ class UrlAttachment extends StatelessWidget {
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topRight: Radius.circular(16), topRight: Radius.circular(16),
), ),
color: color: chatThemeData.colorTheme.blueAlice,
StreamChatTheme.of(context).colorTheme.blueAlice,
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
@@ -69,13 +70,8 @@ class UrlAttachment extends StatelessWidget {
), ),
child: Text( child: Text(
hostDisplayName, hostDisplayName,
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.bodyBold.copyWith(
.textTheme color: chatThemeData.colorTheme.accentBlue,
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
), ),
), ),
), ),
@@ -94,17 +90,13 @@ class UrlAttachment extends StatelessWidget {
urlAttachment.title!.trim(), urlAttachment.title!.trim(),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.body
.textTheme
.body
.copyWith(fontWeight: FontWeight.w700), .copyWith(fontWeight: FontWeight.w700),
), ),
if (urlAttachment.text != null) if (urlAttachment.text != null)
Text( Text(
urlAttachment.text!, urlAttachment.text!,
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.body
.textTheme
.body
.copyWith(fontWeight: FontWeight.w400), .copyWith(fontWeight: FontWeight.w400),
), ),
], ],
@@ -114,3 +106,4 @@ class UrlAttachment extends StatelessWidget {
), ),
); );
} }
}
@@ -1,7 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.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/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Widget that displays a user avatar /// Widget that displays a user avatar
class UserAvatar extends StatelessWidget { class UserAvatar extends StatelessWidget {
@@ -95,8 +95,7 @@ class UserAvatar extends StatelessWidget {
child: Container( child: Container(
constraints: constraints ?? constraints: constraints ??
streamChatTheme.ownMessageTheme.avatarTheme?.constraints, streamChatTheme.ownMessageTheme.avatarTheme?.constraints,
color: selectionColor ?? color: selectionColor ?? streamChatTheme.colorTheme.accentBlue,
StreamChatTheme.of(context).colorTheme.accentBlue,
child: Padding( child: Padding(
padding: EdgeInsets.all(selectionThickness), padding: EdgeInsets.all(selectionThickness),
child: avatar, child: avatar,
@@ -48,7 +48,9 @@ class UserItem extends StatelessWidget {
final bool showLastOnline; final bool showLastOnline;
@override @override
Widget build(BuildContext context) => ListTile( Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return ListTile(
onTap: () { onTap: () {
if (onTap != null) { if (onTap != null) {
onTap!(user); onTap!(user);
@@ -73,22 +75,25 @@ class UserItem extends StatelessWidget {
), ),
trailing: selected trailing: selected
? StreamSvgIcon.checkSend( ? StreamSvgIcon.checkSend(
color: StreamChatTheme.of(context).colorTheme.accentBlue, color: chatThemeData.colorTheme.accentBlue,
) )
: null, : null,
title: Text( title: Text(
user.name, user.name,
style: StreamChatTheme.of(context).textTheme.bodyBold, style: chatThemeData.textTheme.bodyBold,
), ),
subtitle: showLastOnline ? _buildLastActive(context) : null, subtitle: showLastOnline ? _buildLastActive(context) : null,
); );
}
Widget _buildLastActive(context) => Text( Widget _buildLastActive(context) {
final chatTheme = StreamChatTheme.of(context);
return Text(
user.online == true user.online == true
? 'Online' ? 'Online'
: 'Last online ${Jiffy(user.lastActive).fromNow()}', : 'Last online ${Jiffy(user.lastActive).fromNow()}',
style: StreamChatTheme.of(context).textTheme.footnote.copyWith( style: chatTheme.textTheme.footnote
color: .copyWith(color: chatTheme.colorTheme.black.withOpacity(.5)),
StreamChatTheme.of(context).colorTheme.black.withOpacity(.5)),
); );
} }
}
@@ -295,9 +295,11 @@ class _UserListViewState extends State<UserListView>
if (i < items.length) { if (i < items.length) {
final item = items[i]; final item = items[i];
return item.when( return item.when(
headerItem: (header) => Container( headerItem: (header) {
final chatThemeData = StreamChatTheme.of(context);
return Container(
key: ValueKey<String>('HEADER-$header'), key: ValueKey<String>('HEADER-$header'),
color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05), color: chatThemeData.colorTheme.black.withOpacity(0.05),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Text( child: Text(
@@ -305,11 +307,12 @@ class _UserListViewState extends State<UserListView>
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 14.5, fontSize: 14.5,
color: StreamChatTheme.of(context).colorTheme.grey, color: chatThemeData.colorTheme.grey,
),
), ),
), ),
), ),
);
},
userItem: (user) { userItem: (user) {
final selected = widget.selectedUsers?.contains(user) ?? false; final selected = widget.selectedUsers?.contains(user) ?? false;
return Container( return Container(
+20 -27
View File
@@ -1,9 +1,9 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; 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:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Launch URL /// Launch URL
Future<void> launchURL(BuildContext context, String? url) async { Future<void> launchURL(BuildContext context, String? url) async {
@@ -27,9 +27,10 @@ Future<bool?> showConfirmationDialog(
Widget? icon, Widget? icon,
String? question, String? question,
String? cancelText, String? cancelText,
}) => }) {
showModalBottomSheet( final chatThemeData = StreamChatTheme.of(context);
backgroundColor: StreamChatTheme.of(context).colorTheme.white, return showModalBottomSheet(
backgroundColor: chatThemeData.colorTheme.white,
context: context, context: context,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
@@ -37,7 +38,7 @@ Future<bool?> showConfirmationDialog(
topRight: Radius.circular(16), topRight: Radius.circular(16),
)), )),
builder: (context) { builder: (context) {
final effect = StreamChatTheme.of(context).colorTheme.borderTop; final effect = chatThemeData.colorTheme.borderTop;
return SafeArea( return SafeArea(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -47,7 +48,7 @@ Future<bool?> showConfirmationDialog(
const SizedBox(height: 26), const SizedBox(height: 26),
Text( Text(
title, title,
style: StreamChatTheme.of(context).textTheme.headlineBold, style: chatThemeData.textTheme.headlineBold,
), ),
const SizedBox(height: 7), const SizedBox(height: 7),
if (question != null) if (question != null)
@@ -72,13 +73,8 @@ Future<bool?> showConfirmationDialog(
}, },
child: Text( child: Text(
cancelText, cancelText,
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.bodyBold.copyWith(
.textTheme color: chatThemeData.colorTheme.black
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.5)), .withOpacity(0.5)),
), ),
), ),
@@ -93,13 +89,8 @@ Future<bool?> showConfirmationDialog(
}, },
child: Text( child: Text(
okText, okText,
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.bodyBold.copyWith(
.textTheme color: chatThemeData.colorTheme.accentRed),
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentRed),
), ),
), ),
), ),
@@ -110,6 +101,7 @@ Future<bool?> showConfirmationDialog(
), ),
); );
}); });
}
/// Shows info dialog /// Shows info dialog
Future<bool?> showInfoDialog( Future<bool?> showInfoDialog(
@@ -119,10 +111,10 @@ Future<bool?> showInfoDialog(
Widget? icon, Widget? icon,
String? details, String? details,
StreamChatThemeData? theme, StreamChatThemeData? theme,
}) => }) {
showModalBottomSheet( final chatThemeData = StreamChatTheme.of(context);
backgroundColor: theme?.colorTheme.white ?? return showModalBottomSheet(
StreamChatTheme.of(context).colorTheme.white, backgroundColor: theme?.colorTheme.white ?? chatThemeData.colorTheme.white,
context: context, context: context,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
@@ -143,7 +135,7 @@ Future<bool?> showInfoDialog(
Text( Text(
title, title,
style: theme?.textTheme.headlineBold ?? style: theme?.textTheme.headlineBold ??
StreamChatTheme.of(context).textTheme.headlineBold, chatThemeData.textTheme.headlineBold,
), ),
const SizedBox( const SizedBox(
height: 7, height: 7,
@@ -154,7 +146,7 @@ Future<bool?> showInfoDialog(
), ),
Container( Container(
color: theme?.colorTheme.black.withOpacity(.08) ?? color: theme?.colorTheme.black.withOpacity(.08) ??
StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), chatThemeData.colorTheme.black.withOpacity(.08),
height: 1, height: 1,
), ),
Center( Center(
@@ -166,7 +158,7 @@ Future<bool?> showInfoDialog(
okText, okText,
style: TextStyle( style: TextStyle(
color: theme?.colorTheme.black.withOpacity(0.5) ?? color: theme?.colorTheme.black.withOpacity(0.5) ??
StreamChatTheme.of(context).colorTheme.accentBlue, chatThemeData.colorTheme.accentBlue,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
), ),
@@ -176,6 +168,7 @@ Future<bool?> showInfoDialog(
), ),
), ),
); );
}
/// Get random png with initials /// Get random png with initials
String getRandomPicUrl(User user) => String getRandomPicUrl(User user) =>
@@ -2,9 +2,9 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.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:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_thumbnail/video_thumbnail.dart'; import 'package:video_thumbnail/video_thumbnail.dart';
import 'package:stream_chat_flutter/src/video_service.dart';
/// Widget for creating video thumbnail image /// Widget for creating video thumbnail image
class VideoThumbnailImage extends StatefulWidget { class VideoThumbnailImage extends StatefulWidget {
@@ -47,6 +47,7 @@ class VideoThumbnailImage extends StatefulWidget {
class _VideoThumbnailImageState extends State<VideoThumbnailImage> { class _VideoThumbnailImageState extends State<VideoThumbnailImage> {
late Future<Uint8List?> thumbnailFuture; late Future<Uint8List?> thumbnailFuture;
late StreamChatThemeData _streamChatTheme;
@override @override
void initState() { void initState() {
@@ -57,6 +58,12 @@ class _VideoThumbnailImageState extends State<VideoThumbnailImage> {
super.initState(); super.initState();
} }
@override
void didChangeDependencies() {
_streamChatTheme = StreamChatTheme.of(context);
super.didChangeDependencies();
}
@override @override
void didUpdateWidget(covariant VideoThumbnailImage oldWidget) { void didUpdateWidget(covariant VideoThumbnailImage oldWidget) {
if (oldWidget.video != widget.video || oldWidget.format != widget.format) { if (oldWidget.video != widget.video || oldWidget.format != widget.format) {
@@ -87,11 +94,8 @@ class _VideoThumbnailImageState extends State<VideoThumbnailImage> {
constraints: const BoxConstraints.expand(), constraints: const BoxConstraints.expand(),
child: widget.placeholderBuilder?.call(context) ?? child: widget.placeholderBuilder?.call(context) ??
Shimmer.fromColors( Shimmer.fromColors(
baseColor: StreamChatTheme.of(context) baseColor: _streamChatTheme.colorTheme.greyGainsboro,
.colorTheme highlightColor: _streamChatTheme.colorTheme.whiteSmoke,
.greyGainsboro,
highlightColor:
StreamChatTheme.of(context).colorTheme.whiteSmoke,
child: Image.asset( child: Image.asset(
'images/placeholder.png', 'images/placeholder.png',
fit: BoxFit.cover, fit: BoxFit.cover,
@@ -119,12 +119,11 @@ class ChannelListCore extends StatefulWidget {
/// The current state of the [ChannelListCore]. /// The current state of the [ChannelListCore].
class ChannelListCoreState extends State<ChannelListCore> { class ChannelListCoreState extends State<ChannelListCore> {
@override late ChannelsBlocState _channelsBloc;
Widget build(BuildContext context) { StreamChatCoreState? _streamChatCoreState;
final channelsBloc = ChannelsBloc.of(context);
return _buildListView(channelsBloc); @override
} Widget build(BuildContext context) => _buildListView(_channelsBloc);
StreamBuilder<List<Channel>> _buildListView( StreamBuilder<List<Channel>> _buildListView(
ChannelsBlocState channelsBlocState, ChannelsBlocState channelsBlocState,
@@ -147,36 +146,41 @@ class ChannelListCoreState extends State<ChannelListCore> {
); );
/// Fetches initial channels and updates the widget /// Fetches initial channels and updates the widget
Future<void> loadData() { Future<void> loadData() => _channelsBloc.queryChannels(
final channelsBloc = ChannelsBloc.of(context);
return channelsBloc.queryChannels(
filter: widget.filter, filter: widget.filter,
sortOptions: widget.sort, sortOptions: widget.sort,
paginationParams: widget.pagination, paginationParams: widget.pagination,
options: widget.options, options: widget.options,
); );
}
/// Fetches more channels with updated pagination and updates the widget /// Fetches more channels with updated pagination and updates the widget
Future<void> paginateData() { Future<void> paginateData() => _channelsBloc.queryChannels(
final channelsBloc = ChannelsBloc.of(context);
return channelsBloc.queryChannels(
filter: widget.filter, filter: widget.filter,
sortOptions: widget.sort, sortOptions: widget.sort,
paginationParams: widget.pagination.copyWith( paginationParams: widget.pagination.copyWith(
offset: channelsBloc.channels?.length ?? 0, offset: _channelsBloc.channels?.length ?? 0,
), ),
options: widget.options, options: widget.options,
); );
}
late StreamSubscription<Event> _subscription; StreamSubscription<Event>? _subscription;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_setupController();
}
@override
void didChangeDependencies() {
_channelsBloc = ChannelsBloc.of(context);
final newStreamChatCoreState = StreamChatCore.of(context);
if (newStreamChatCoreState != _streamChatCoreState) {
_streamChatCoreState = newStreamChatCoreState;
loadData(); loadData();
final client = StreamChatCore.of(context).client; final client = _streamChatCoreState!.client;
_subscription?.cancel();
_subscription = client _subscription = client
.on( .on(
EventType.connectionRecovered, EventType.connectionRecovered,
@@ -185,11 +189,9 @@ class ChannelListCoreState extends State<ChannelListCore> {
EventType.channelVisible, EventType.channelVisible,
) )
.listen((event) => loadData()); .listen((event) => loadData());
if (widget.channelListController != null) {
widget.channelListController!.loadData = loadData;
widget.channelListController!.paginateData = paginateData;
} }
super.didChangeDependencies();
} }
@override @override
@@ -203,11 +205,22 @@ class ChannelListCoreState extends State<ChannelListCore> {
oldWidget.pagination.toJson().toString()) { oldWidget.pagination.toJson().toString()) {
loadData(); loadData();
} }
if (widget.channelListController != oldWidget.channelListController) {
_setupController();
}
}
void _setupController() {
if (widget.channelListController != null) {
widget.channelListController!.loadData = loadData;
widget.channelListController!.paginateData = paginateData;
}
} }
@override @override
void dispose() { void dispose() {
_subscription.cancel(); _subscription?.cancel();
super.dispose(); super.dispose();
} }
} }
@@ -50,17 +50,20 @@ class ChannelsBloc extends StatefulWidget {
streamChatState = context.findAncestorStateOfType<ChannelsBlocState>(); streamChatState = context.findAncestorStateOfType<ChannelsBlocState>();
if (streamChatState == null) { assert(
throw Exception('You must have a ChannelsBloc widget as ancestor'); streamChatState != null,
} 'You must have a ChannelsBloc widget as ancestor',
);
return streamChatState; return streamChatState!;
} }
} }
/// The current state of the [ChannelsBloc]. /// The current state of the [ChannelsBloc].
class ChannelsBlocState extends State<ChannelsBloc> class ChannelsBlocState extends State<ChannelsBloc>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
StreamChatCoreState? _streamChatCoreState;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
@@ -86,6 +89,8 @@ class ChannelsBlocState extends State<ChannelsBloc>
bool _paginationEnded = false; bool _paginationEnded = false;
final List<StreamSubscription> _subscriptions = [];
/// Calls [client.queryChannels] updating [queryChannelsLoading] stream /// Calls [client.queryChannels] updating [queryChannelsLoading] stream
Future<void> queryChannels({ Future<void> queryChannels({
Filter? filter, Filter? filter,
@@ -93,7 +98,7 @@ class ChannelsBlocState extends State<ChannelsBloc>
PaginationParams paginationParams = const PaginationParams(limit: 30), PaginationParams paginationParams = const PaginationParams(limit: 30),
Map<String, dynamic>? options, Map<String, dynamic>? options,
}) async { }) async {
final client = StreamChatCore.of(context).client; final client = _streamChatCoreState!.client;
final clear = paginationParams.offset == 0; final clear = paginationParams.offset == 0;
@@ -139,14 +144,15 @@ class ChannelsBlocState extends State<ChannelsBloc>
} }
} }
final List<StreamSubscription> _subscriptions = [];
@override @override
void initState() { void didChangeDependencies() {
super.initState(); final newStreamChatCoreState = StreamChatCore.of(context);
final client = StreamChatCore.of(context).client; if (newStreamChatCoreState != _streamChatCoreState) {
_streamChatCoreState = newStreamChatCoreState;
final client = _streamChatCoreState!.client;
_cancelSubscriptions();
if (!widget.lockChannelsOrder) { if (!widget.lockChannelsOrder) {
_subscriptions.add(client _subscriptions.add(client
.on( .on(
@@ -161,7 +167,8 @@ class ChannelsBlocState extends State<ChannelsBloc>
newChannels.insert(0, channel); newChannels.insert(0, channel);
} }
} else if (widget.shouldAddChannel?.call(e) == true) { } else if (widget.shouldAddChannel?.call(e) == true) {
final hiddenIndex = _hiddenChannels.indexWhere((c) => c.cid == e.cid); final hiddenIndex =
_hiddenChannels.indexWhere((c) => c.cid == e.cid);
if (hiddenIndex != -1) { if (hiddenIndex != -1) {
newChannels.insert(0, _hiddenChannels[hiddenIndex]); newChannels.insert(0, _hiddenChannels[hiddenIndex]);
_hiddenChannels.removeAt(hiddenIndex); _hiddenChannels.removeAt(hiddenIndex);
@@ -179,37 +186,46 @@ class ChannelsBlocState extends State<ChannelsBloc>
})); }));
} }
_subscriptions.add(client.on(EventType.channelHidden).listen((event) async { _subscriptions
..add(client.on(EventType.channelHidden).listen((event) async {
final newChannels = List<Channel>.from(channels ?? []); final newChannels = List<Channel>.from(channels ?? []);
final channelIndex = newChannels.indexWhere((c) => c.cid == event.cid); final channelIndex =
newChannels.indexWhere((c) => c.cid == event.cid);
if (channelIndex > -1) { if (channelIndex > -1) {
final channel = newChannels.removeAt(channelIndex); final channel = newChannels.removeAt(channelIndex);
_hiddenChannels.add(channel); _hiddenChannels.add(channel);
_channelsController.add(newChannels); _channelsController.add(newChannels);
} }
})); }))
// ignore: cascade_invocations ..add(client
_subscriptions.add(client
.on( .on(
EventType.channelDeleted, EventType.channelDeleted,
EventType.notificationRemovedFromChannel, EventType.notificationRemovedFromChannel,
) )
.listen((e) { .listen((e) {
// ignore: cascade_invocations
final channel = e.channel; final channel = e.channel;
_channelsController.add(List.from( _channelsController.add(List.from(
(channels ?? [])..removeWhere((c) => c.cid == channel?.cid))); (channels ?? [])..removeWhere((c) => c.cid == channel?.cid)));
})); }));
} }
super.didChangeDependencies();
}
@override @override
void dispose() { void dispose() {
_channelsController.close(); _channelsController.close();
_queryChannelsLoadingController.close(); _queryChannelsLoadingController.close();
_subscriptions.forEach((s) => s.cancel()); _cancelSubscriptions();
super.dispose(); super.dispose();
} }
void _cancelSubscriptions() {
_subscriptions
..forEach((s) => s.cancel())
..clear();
}
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
} }
@@ -109,23 +109,23 @@ class MessageListCore extends StatefulWidget {
/// The current state of the [MessageListCore]. /// The current state of the [MessageListCore].
class MessageListCoreState extends State<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; 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>[]; var _messages = <Message>[];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final messagesStream = _isThreadConversation final messagesStream = _isThreadConversation
? _streamChannel.channel.state?.threadsStream ? _streamChannel!.channel.state?.threadsStream
.where((threads) => threads.containsKey(widget.parentMessage!.id)) .where((threads) => threads.containsKey(widget.parentMessage!.id))
.map((threads) => threads[widget.parentMessage!.id]) .map((threads) => threads[widget.parentMessage!.id])
: _streamChannel.channel.state?.messagesStream; : _streamChannel!.channel.state?.messagesStream;
bool defaultFilter(Message m) { bool defaultFilter(Message m) {
final isMyMessage = m.user?.id == _currentUser?.id; final isMyMessage = m.user?.id == _currentUser?.id;
@@ -167,31 +167,58 @@ class MessageListCoreState extends State<MessageListCore> {
QueryDirection direction = QueryDirection.top, QueryDirection direction = QueryDirection.top,
}) { }) {
if (!_isThreadConversation) { if (!_isThreadConversation) {
return _streamChannel.queryMessages(direction: direction); return _streamChannel!.queryMessages(direction: direction);
} else { } 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 @override
void initState() { void initState() {
_streamChannel = StreamChannel.of(context); _setupController();
if (_isThreadConversation) { super.initState();
_streamChannel.getReplies(widget.parentMessage!.id);
} }
void _setupController() {
if (widget.messageListController != null) { if (widget.messageListController != null) {
widget.messageListController!.paginateData = paginateData; widget.messageListController!.paginateData = paginateData;
} }
super.initState();
} }
@override @override
void dispose() { void dispose() {
if (!_upToDate) { if (!_upToDate) {
_streamChannel.reloadChannel(); _streamChannel!.reloadChannel();
} }
super.dispose(); super.dispose();
} }
@@ -29,17 +29,20 @@ class MessageSearchBloc extends StatefulWidget {
state = context.findAncestorStateOfType<MessageSearchBlocState>(); state = context.findAncestorStateOfType<MessageSearchBlocState>();
if (state == null) { assert(
throw Exception('You must have a MessageSearchBloc widget as ancestor'); state != null,
} 'You must have a MessageSearchBloc widget as ancestor',
);
return state; return state!;
} }
} }
/// The current state of the [MessageSearchBloc] /// The current state of the [MessageSearchBloc]
class MessageSearchBlocState extends State<MessageSearchBloc> class MessageSearchBlocState extends State<MessageSearchBloc>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
late StreamChatCoreState _streamChatCoreState;
/// The current messages list /// The current messages list
List<GetMessageResponse>? get messageResponses => _messageResponses.value; List<GetMessageResponse>? get messageResponses => _messageResponses.value;
@@ -64,7 +67,7 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
String? query, String? query,
PaginationParams? pagination, PaginationParams? pagination,
}) async { }) async {
final client = StreamChatCore.of(context).client; final client = _streamChatCoreState.client;
if (_queryMessagesLoadingController.value == true) return; if (_queryMessagesLoadingController.value == true) return;
@@ -109,6 +112,12 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
return widget.child; return widget.child;
} }
@override
void didChangeDependencies() {
_streamChatCoreState = StreamChatCore.of(context);
super.didChangeDependencies();
}
@override @override
void dispose() { void dispose() {
_messageResponses.close(); _messageResponses.close();
@@ -105,9 +105,14 @@ class MessageSearchListCore extends StatefulWidget {
/// The current state of the [MessageSearchListCore]. /// The current state of the [MessageSearchListCore].
class MessageSearchListCoreState extends State<MessageSearchListCore> { class MessageSearchListCoreState extends State<MessageSearchListCore> {
MessageSearchBlocState? _messageSearchBloc;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); final newMessageSearchBloc = MessageSearchBloc.of(context);
if (newMessageSearchBloc != _messageSearchBloc) {
_messageSearchBloc = newMessageSearchBloc;
loadData(); loadData();
if (widget.messageSearchListController != null) { if (widget.messageSearchListController != null) {
widget.messageSearchListController!.loadData = loadData; widget.messageSearchListController!.loadData = loadData;
@@ -115,12 +120,12 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
} }
} }
@override super.didChangeDependencies();
Widget build(BuildContext context) {
final messageSearchBloc = MessageSearchBloc.of(context);
return _buildListView(messageSearchBloc);
} }
@override
Widget build(BuildContext context) => _buildListView(_messageSearchBloc!);
Widget _buildListView(MessageSearchBlocState messageSearchBloc) => Widget _buildListView(MessageSearchBlocState messageSearchBloc) =>
StreamBuilder<List<GetMessageResponse>>( StreamBuilder<List<GetMessageResponse>>(
stream: messageSearchBloc.messagesStream, stream: messageSearchBloc.messagesStream,
@@ -140,30 +145,24 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
); );
/// Fetches initial messages and updates the widget /// Fetches initial messages and updates the widget
Future<void> loadData() { Future<void> loadData() => _messageSearchBloc!.search(
final messageSearchBloc = MessageSearchBloc.of(context);
return messageSearchBloc.search(
filter: widget.filters, filter: widget.filters,
sort: widget.sortOptions, sort: widget.sortOptions,
query: widget.messageQuery, query: widget.messageQuery,
pagination: widget.paginationParams, pagination: widget.paginationParams,
messageFilter: widget.messageFilters, messageFilter: widget.messageFilters,
); );
}
/// Fetches more messages with updated pagination and updates the widget /// Fetches more messages with updated pagination and updates the widget
Future<void> paginateData() { Future<void> paginateData() => _messageSearchBloc!.search(
final messageSearchBloc = MessageSearchBloc.of(context);
return messageSearchBloc.search(
filter: widget.filters, filter: widget.filters,
sort: widget.sortOptions, sort: widget.sortOptions,
pagination: widget.paginationParams!.copyWith( pagination: widget.paginationParams!.copyWith(
offset: messageSearchBloc.messageResponses?.length ?? 0, offset: _messageSearchBloc!.messageResponses?.length ?? 0,
), ),
query: widget.messageQuery, query: widget.messageQuery,
messageFilter: widget.messageFilters, messageFilter: widget.messageFilters,
); );
}
@override @override
void didUpdateWidget(MessageSearchListCore oldWidget) { void didUpdateWidget(MessageSearchListCore oldWidget) {
@@ -47,13 +47,12 @@ class StreamChannel extends StatefulWidget {
streamChannelState = context.findAncestorStateOfType<StreamChannelState>(); streamChannelState = context.findAncestorStateOfType<StreamChannelState>();
if (streamChannelState == null) { assert(
throw Exception( streamChannelState != null,
'You must have a StreamChannel widget at the top of your widget tree', 'You must have a StreamChannel widget at the top of your widget tree',
); );
}
return streamChannelState; return streamChannelState!;
} }
@override @override
@@ -70,12 +70,12 @@ class StreamChatCore extends StatefulWidget {
streamChatState = context.findAncestorStateOfType<StreamChatCoreState>(); streamChatState = context.findAncestorStateOfType<StreamChatCoreState>();
if (streamChatState == null) { assert(
throw Exception( streamChatState != null,
'You must have a StreamChat widget at the top of your widget tree'); '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]. /// The current state of the [UserListCore].
class UserListCoreState extends State<UserListCore> class UserListCoreState extends State<UserListCore>
with WidgetsBindingObserver { with WidgetsBindingObserver {
UsersBlocState? _usersBloc;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); final newUsersBloc = UsersBloc.of(context);
if (newUsersBloc != _usersBloc) {
_usersBloc = newUsersBloc;
loadData(); loadData();
if (widget.userListController != null) { if (widget.userListController != null) {
widget.userListController!.loadData = loadData; widget.userListController!.loadData = loadData;
widget.userListController!.paginateData = paginateData; widget.userListController!.paginateData = paginateData;
} }
} }
super.didChangeDependencies();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => _buildListView();
final _usersBloc = UsersBloc.of(context);
return _buildListView(_usersBloc);
}
bool get _isListAlreadySorted => bool get _isListAlreadySorted =>
widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false; widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false;
Stream<List<ListItem>> _buildUserStream( Stream<List<ListItem>> _buildUserStream() => _usersBloc!.usersStream.map(
UsersBlocState usersBlocState,
) =>
usersBlocState.usersStream.map(
(users) { (users) {
if (widget.groupAlphabetically) { if (widget.groupAlphabetically) {
var temp = users; var temp = users;
@@ -169,11 +169,8 @@ class UserListCoreState extends State<UserListCore>
}, },
); );
StreamBuilder<List<ListItem>> _buildListView( StreamBuilder<List<ListItem>> _buildListView() => StreamBuilder(
UsersBlocState usersBlocState, stream: _buildUserStream(),
) =>
StreamBuilder(
stream: _buildUserStream(usersBlocState),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
return widget.errorBuilder(snapshot.error!); return widget.errorBuilder(snapshot.error!);
@@ -190,28 +187,22 @@ class UserListCoreState extends State<UserListCore>
); );
// ignore: public_member_api_docs // ignore: public_member_api_docs
Future<void> loadData() { Future<void> loadData() => _usersBloc!.queryUsers(
final _usersBloc = UsersBloc.of(context);
return _usersBloc.queryUsers(
filter: widget.filter, filter: widget.filter,
sort: widget.sort, sort: widget.sort,
pagination: widget.pagination, pagination: widget.pagination,
options: widget.options, options: widget.options,
); );
}
// ignore: public_member_api_docs // ignore: public_member_api_docs
Future<void> paginateData() { Future<void> paginateData() => _usersBloc!.queryUsers(
final _usersBloc = UsersBloc.of(context);
return _usersBloc.queryUsers(
filter: widget.filter, filter: widget.filter,
sort: widget.sort, sort: widget.sort,
pagination: widget.pagination!.copyWith( pagination: widget.pagination!.copyWith(
offset: _usersBloc.users?.length ?? 0, offset: _usersBloc!.users?.length ?? 0,
), ),
options: widget.options, options: widget.options,
); );
}
@override @override
void didUpdateWidget(UserListCore oldWidget) { void didUpdateWidget(UserListCore oldWidget) {
@@ -30,11 +30,12 @@ class UsersBloc extends StatefulWidget {
state = context.findAncestorStateOfType<UsersBlocState>(); state = context.findAncestorStateOfType<UsersBlocState>();
if (state == null) { assert(
throw Exception('You must have a UsersBloc widget as ancestor'); 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 /// The stream notifying the state of queryUsers call
Stream<bool> get queryUsersLoading => _queryUsersLoadingController.stream; Stream<bool> get queryUsersLoading => _queryUsersLoadingController.stream;
late StreamChatCoreState _streamChatCore;
/// The Query Users method allows you to search for users and see if they are /// The Query Users method allows you to search for users and see if they are
/// online/offline. /// online/offline.
/// [API Reference](https://getstream.io/chat/docs/flutter-dart/query_users/?language=dart) /// [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, Map<String, dynamic>? options,
PaginationParams? pagination, PaginationParams? pagination,
}) async { }) async {
final client = StreamChatCore.of(context).client; final client = _streamChatCore.client;
if (_queryUsersLoadingController.value == true) return; if (_queryUsersLoadingController.value == true) return;
@@ -101,6 +104,12 @@ class UsersBlocState extends State<UsersBloc>
} }
} }
@override
void didChangeDependencies() {
_streamChatCore = StreamChatCore.of(context);
super.didChangeDependencies();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
@@ -46,7 +46,7 @@ void main() {
await tester.pumpWidget(channelListCore); await tester.pumpWidget(channelListCore);
expect(find.byKey(channelListCoreKey), findsNothing); 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(channelsBlocKey), findsNothing);
expect(find.byKey(childKey), 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); await tester.pumpWidget(messageListCore);
expect(find.byKey(messageListCoreKey), findsNothing); 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( testWidgets(
'should build error widget if messagesStream emits error', 'should build error widget if messagesStream emits error',
(tester) async { (tester) async {
@@ -35,27 +35,12 @@ void main() {
'messageSearchBlocState.search() should throw if used where ' 'messageSearchBlocState.search() should throw if used where '
'StreamChat is not present in the widget tree', 'StreamChat is not present in the widget tree',
(tester) async { (tester) async {
const messageSearchBlocKey = Key('messageSearchBloc');
const childKey = Key('child');
final messageSearchBloc = MessageSearchBloc( final messageSearchBloc = MessageSearchBloc(
key: messageSearchBlocKey, child: Offstage(),
child: Offstage(key: childKey),
); );
await tester.pumpWidget(messageSearchBloc); await tester.pumpWidget(messageSearchBloc);
expect(tester.takeException(), isInstanceOf<AssertionError>());
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>());
}
}, },
); );
@@ -45,7 +45,7 @@ void main() {
await tester.pumpWidget(messageSearchListCore); await tester.pumpWidget(messageSearchListCore);
expect(find.byKey(messageSearchListCoreKey), findsNothing); 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(find.byKey(messageSearchListCoreKey), findsOneWidget);
expect(controller.loadData, isNotNull); expect(controller.loadData, isNotNull);
@@ -49,7 +49,7 @@ void main() {
await tester.pumpWidget(userListCore); await tester.pumpWidget(userListCore);
expect(find.byKey(userListCoreKey), findsNothing); 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 ' 'usersBlocState.queryUsers() should throw if used where '
'StreamChat is not present in the widget tree', 'StreamChat is not present in the widget tree',
(tester) async { (tester) async {
const usersBlocKey = Key('usersBloc');
const childKey = Key('child');
final usersBloc = UsersBloc( final usersBloc = UsersBloc(
key: usersBlocKey, child: Offstage(),
child: Offstage(key: childKey),
); );
await tester.pumpWidget(usersBloc); await tester.pumpWidget(usersBloc);
expect(tester.takeException(), isInstanceOf<AssertionError>());
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>());
}
}, },
); );