diff --git a/example/lib/main.dart b/example/lib/main.dart index ac895cfa..012a6051 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -91,7 +91,7 @@ class MyApp extends StatelessWidget { return MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), - themeMode: ThemeMode.system, + themeMode: ThemeMode.dark, home: Container( child: StreamChat( client: client, diff --git a/lib/src/channel_header.dart b/lib/src/channel_header.dart index 5a7a146c..26668697 100644 --- a/lib/src/channel_header.dart +++ b/lib/src/channel_header.dart @@ -157,9 +157,6 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { child: Icon( Icons.arrow_back_ios, size: 15, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, ), ), ); diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index a2ef4abe..bb60d891 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -476,7 +476,6 @@ class _MessageInputState extends State { child: Icon( Icons.close, size: 15, - color: Colors.black, ), ), ), diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index b4373594..14366b1c 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -71,6 +71,8 @@ class MessageListView extends StatefulWidget { this.onMessageActions, this.attachmentBuilders, this.dateDividerBuilder, + this.showAvatar = true, + this.customClipperBuilder, }) : super(key: key); /// Function used to build a custom message widget @@ -110,6 +112,13 @@ class MessageListView extends StatefulWidget { /// Builder used to render date dividers final Widget Function(DateTime) dateDividerBuilder; + /// if true shows the user avatar + final bool showAvatar; + + /// Custom clipper applied to the message bubble + final CustomClipper Function(BuildContext, Message, int index) + customClipperBuilder; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -171,6 +180,14 @@ class _MessageListViewState extends State { onUserAvatarTap: widget.onUserAvatarTap, onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, + showAvatar: widget.showAvatar, + customClipperBuilder: (message) { + return widget.customClipperBuilder( + context, + message, + i, + ); + }, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), @@ -235,6 +252,12 @@ class _MessageListViewState extends State { onUserAvatarTap: widget.onUserAvatarTap, onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, + showAvatar: widget.showAvatar, + customClipperBuilder: (message) { + if (widget.customClipperBuilder != null) { + return widget.customClipperBuilder(context, message, i); + } + }, ); } } @@ -308,7 +331,8 @@ class _MessageListViewState extends State { if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('MESSAGE-${message.id}'), - builder: (_) => widget.messageBuilder(context, message, 0), + builder: (_) => + widget.messageBuilder(context, message, _messages.length - 1), ); } else { messageWidget = MessageWidget( @@ -323,6 +347,11 @@ class _MessageListViewState extends State { onUserAvatarTap: widget.onUserAvatarTap, onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, + showAvatar: widget.showAvatar, + customClipperBuilder: (message) { + return widget.customClipperBuilder( + context, message, _messages.length - 1); + }, ); } @@ -364,6 +393,10 @@ class _MessageListViewState extends State { onUserAvatarTap: widget.onUserAvatarTap, onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, + showAvatar: widget.showAvatar, + customClipperBuilder: (message) { + return widget.customClipperBuilder(context, message, 0); + }, ); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index af5173d9..9ff08475 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -48,6 +48,8 @@ class MessageWidget extends StatefulWidget { this.showOtherMessageUsername = false, this.showVideoFullScreen = true, this.attachmentBuilders, + this.showAvatar = true, + this.customClipperBuilder, }) : super(key: key); /// Function called on mention tap @@ -83,6 +85,12 @@ class MessageWidget extends StatefulWidget { /// Map that defines a builder for an attachment type final Map attachmentBuilders; + /// if true shows the user avatar + final bool showAvatar; + + /// Custom clipper applied to the message bubble + final CustomClipper Function(Message) customClipperBuilder; + @override _MessageWidgetState createState() => _MessageWidgetState(); } @@ -130,7 +138,7 @@ class _MessageWidgetState extends State ), _isNextUser ? Container( - width: 40, + width: widget.showAvatar ? 40 : 8, ) : _buildUserAvatar(), ]); @@ -164,11 +172,13 @@ class _MessageWidgetState extends State right: _isMyMessage ? 0 : 8.0, ), child: Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - UserAvatar( - user: widget.message.user, - onTap: widget.onUserAvatarTap, - ), + if (widget.showAvatar) + UserAvatar( + user: widget.message.user, + onTap: widget.onUserAvatarTap, + ), if (_isMyMessage && widget.nextMessage == null) SendingIndicator( message: widget.message, @@ -466,7 +476,7 @@ class _MessageWidgetState extends State ); } - Padding _buildMessageText( + Widget _buildMessageText( int nOfAttachmentWidgets, String text, BuildContext context, @@ -476,46 +486,57 @@ class _MessageWidgetState extends State right: _isMyMessage ? 0.0 : 8.0, left: _isMyMessage ? 8.0 : 0.0, ), - child: Container( - decoration: - _buildBoxDecoration(_isLastUser || nOfAttachmentWidgets > 0), - padding: EdgeInsets.all(10), - constraints: BoxConstraints.loose( - Size.fromWidth(MediaQuery.of(context).size.width * 0.7), - ), - child: _buildSendingError( - MarkdownBody( - data: text, - onTapLink: (link) { - if (link.startsWith('@')) { - final mentionedUser = widget.message.mentionedUsers.firstWhere( - (u) => '@${u.name.replaceAll(' ', '')}' == link, - orElse: () => null, - ); + child: ClipPath( + clipper: widget.customClipperBuilder != null + ? widget.customClipperBuilder(widget.message) + : null, + clipBehavior: Clip.hardEdge, + child: Container( + decoration: + _buildBoxDecoration(_isLastUser || nOfAttachmentWidgets > 0) + .copyWith( + borderRadius: widget.customClipperBuilder != null + ? BorderRadius.circular(0) + : null), + padding: EdgeInsets.all(10), + constraints: BoxConstraints.loose( + Size.fromWidth(MediaQuery.of(context).size.width * 0.7), + ), + child: _buildSendingError( + MarkdownBody( + data: text, + onTapLink: (link) { + if (link.startsWith('@')) { + final mentionedUser = + widget.message.mentionedUsers.firstWhere( + (u) => '@${u.name.replaceAll(' ', '')}' == link, + orElse: () => null, + ); - if (widget.onMentionTap != null) { - widget.onMentionTap(mentionedUser); + if (widget.onMentionTap != null) { + widget.onMentionTap(mentionedUser); + } else { + print('tap on ${mentionedUser.name}'); + } } else { - print('tap on ${mentionedUser.name}'); + launchURL(context, link); } - } else { - launchURL(context, link); - } - }, - styleSheet: MarkdownStyleSheet.fromTheme( - Theme.of(context).copyWith( - textTheme: Theme.of(context).textTheme.apply( - bodyColor: _messageTheme.messageText.color, - decoration: _messageTheme.messageText.decoration, - decorationColor: - _messageTheme.messageText.decorationColor, - decorationStyle: - _messageTheme.messageText.decorationStyle, - fontFamily: _messageTheme.messageText.fontFamily, - ), + }, + styleSheet: MarkdownStyleSheet.fromTheme( + Theme.of(context).copyWith( + textTheme: Theme.of(context).textTheme.apply( + bodyColor: _messageTheme.messageText.color, + decoration: _messageTheme.messageText.decoration, + decorationColor: + _messageTheme.messageText.decorationColor, + decorationStyle: + _messageTheme.messageText.decorationStyle, + fontFamily: _messageTheme.messageText.fontFamily, + ), + ), + ).copyWith( + p: _messageTheme.messageText, ), - ).copyWith( - p: _messageTheme.messageText, ), ), ), diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 1e92ad1e..75a739a6 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -73,9 +73,11 @@ class StreamChatState extends State with WidgetsBindingObserver { builder: (context) { final materialTheme = Theme.of(context); final isDark = materialTheme.brightness == Brightness.dark; + final streamTheme = StreamChatTheme.of(context); return Theme( data: materialTheme.copyWith( - accentColor: StreamChatTheme.of(context).accentColor, + primaryIconTheme: streamTheme.primaryIconTheme, + accentColor: streamTheme.accentColor, scaffoldBackgroundColor: isDark ? Colors.black : Colors.white, backgroundColor: isDark ? Colors.black : Colors.white, ), @@ -113,6 +115,7 @@ class StreamChatState extends State with WidgetsBindingObserver { final theme = defaultTheme.copyWith( primaryColor: themeData?.primaryColor, defaultChannelImage: themeData?.defaultChannelImage, + primaryIconTheme: themeData?.primaryIconTheme, defaultUserImage: themeData?.defaultUserImage, channelTheme: defaultTheme.channelTheme.copyWith( channelHeaderTheme: diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index 058d1baf..acf7f4f0 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -57,6 +57,9 @@ class StreamChatThemeData { /// The widget that will be built when the user image is unavailable final Widget Function(BuildContext, User) defaultUserImage; + /// Primary icon theme + final IconThemeData primaryIconTheme; + /// Create a theme from scratch StreamChatThemeData({ this.primaryColor, @@ -68,6 +71,7 @@ class StreamChatThemeData { this.ownMessageTheme, this.defaultChannelImage, this.defaultUserImage, + this.primaryIconTheme, }); /// Create a theme from a Material [Theme] @@ -76,6 +80,7 @@ class StreamChatThemeData { return defaultTheme.copyWith( accentColor: theme.accentColor, + primaryIconTheme: theme.primaryIconTheme, primaryColor: theme.colorScheme.primary, secondaryColor: theme.colorScheme.secondary, channelTheme: ChannelTheme( @@ -108,10 +113,12 @@ class StreamChatThemeData { MessageTheme otherMessageTheme, Widget Function(BuildContext, Channel) defaultChannelImage, Widget Function(BuildContext, User) defaultUserImage, + IconThemeData primaryIconTheme, }) => StreamChatThemeData( primaryColor: primaryColor ?? this.primaryColor, secondaryColor: secondaryColor ?? this.secondaryColor, + primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme, accentColor: accentColor ?? this.accentColor, defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage, defaultUserImage: defaultUserImage ?? this.defaultUserImage, @@ -183,6 +190,8 @@ class StreamChatThemeData { return StreamChatThemeData( accentColor: accentColor, primaryColor: isDark ? Colors.black : Colors.white, + primaryIconTheme: + IconThemeData(color: isDark ? Colors.white : Colors.black), defaultChannelImage: (context, channel) => SizedBox(), defaultUserImage: (context, user) => Center( child: Text(