diff --git a/lib/src/channel_bottom_sheet.dart b/lib/src/channel_bottom_sheet.dart index d03f233c..6b3e4658 100644 --- a/lib/src/channel_bottom_sheet.dart +++ b/lib/src/channel_bottom_sheet.dart @@ -74,7 +74,7 @@ class _ChannelBottomSheetState extends State { ), borderRadius: BorderRadius.circular(32.0), onlineIndicatorConstraints: - BoxConstraints.tight(Size(16.0, 16.0)), + BoxConstraints.tight(Size(12.0, 12.0)), ), SizedBox( height: 6.0, @@ -110,6 +110,8 @@ class _ChannelBottomSheetState extends State { maxWidth: 64.0, ), borderRadius: BorderRadius.circular(32.0), + onlineIndicatorConstraints: + BoxConstraints.tight(Size(12.0, 12.0)), ), SizedBox( height: 6.0, diff --git a/lib/src/file_attachment.dart b/lib/src/file_attachment.dart index ca51bf76..96061cab 100644 --- a/lib/src/file_attachment.dart +++ b/lib/src/file_attachment.dart @@ -62,16 +62,9 @@ class _FileAttachmentState extends State { child: Container( width: widget.size?.width ?? 100, height: 56.0, - decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, - borderRadius: - widget.trailing != null ? BorderRadius.circular(16.0) : null, - border: widget.trailing != null - ? Border.fromBorderSide(BorderSide( - color: StreamChatTheme.of(context).colorTheme.greyWhisper)) - : null, - ), + color: StreamChatTheme.of(context).colorTheme.white, child: Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( child: _getFileTypeImage(), @@ -79,9 +72,7 @@ class _FileAttachmentState extends State { width: 33.33, margin: EdgeInsets.all(8.0), ), - SizedBox( - width: 6.0, - ), + SizedBox(width: 8.0), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -93,32 +84,36 @@ class _FileAttachmentState extends State { maxLines: 1, overflow: TextOverflow.ellipsis, ), - SizedBox( - height: 3.0, - ), + SizedBox(height: 3.0), Text( '${getSizeText(widget.attachment.extraData['file_size'])}', - style: StreamChatTheme.of(context).textTheme.body.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5)), + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), ), ], ), ), - Column( - children: [ - widget.trailing ?? - IconButton( - icon: StreamSvgIcon.cloud_download( - color: StreamChatTheme.of(context).colorTheme.black, - ), - onPressed: () { - launchURL(context, widget.attachment.assetUrl); - }, + SizedBox(width: 8.0), + Material( + type: MaterialType.transparency, + child: widget.trailing ?? + IconButton( + icon: StreamSvgIcon.cloud_download( + color: StreamChatTheme.of(context).colorTheme.black, ), - ], + padding: const EdgeInsets.all(8), + visualDensity: VisualDensity.compact, + splashRadius: 16, + onPressed: () { + launchURL(context, widget.attachment.assetUrl); + }, + ), ), ], ), diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index 332117d4..97729ca1 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -8,6 +8,7 @@ import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:path_provider/path_provider.dart'; import '../stream_chat_flutter.dart'; +import 'extension.dart'; class ImageActionsModal extends StatelessWidget { final Message message; @@ -35,127 +36,124 @@ class ImageActionsModal extends StatelessWidget { } Widget _buildPage(context) { - return Material( - color: Colors.transparent, - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - SizedBox( - height: kToolbarHeight, - child: IconButton( - icon: StreamSvgIcon.close(), - onPressed: () => Navigator.maybePop(context), + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + SizedBox(height: kToolbarHeight), + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Container( + width: MediaQuery.of(context).size.width * 0.5, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16.0), ), - ), - Align( - alignment: Alignment.centerRight, child: Container( - width: MediaQuery.of(context).size.width / 1.8, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Material( - clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + _buildButton( + context, + 'Reply', + StreamSvgIcon.Icon_curve_line_left_up( + size: 24.0, + color: StreamChatTheme.of(context).colorTheme.grey, + ), + () {}, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: ListTile.divideTiles( - color: StreamChatTheme.of(context).colorTheme.greyWhisper, - context: context, - tiles: [ - _buildButton( - context, - 'Reply', - StreamSvgIcon.Icon_curve_line_left_up( - size: 24.0, - color: - StreamChatTheme.of(context).colorTheme.grey, - ), - () {}), - _buildButton( - context, - 'Show in Chat', - StreamSvgIcon.eye( - size: 24.0, - color: - StreamChatTheme.of(context).colorTheme.black, - ), - onShowMessage), - _buildButton( - context, - 'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}', - StreamSvgIcon.Icon_save( - size: 24.0, - color: - StreamChatTheme.of(context).colorTheme.grey, - ), () async { - var url = urls[currentIndex].imageUrl ?? - urls[currentIndex].assetUrl ?? - urls[currentIndex].thumbUrl; + _buildButton( + context, + 'Show in Chat', + StreamSvgIcon.eye( + size: 24.0, + color: StreamChatTheme.of(context).colorTheme.black, + ), + onShowMessage, + ), + _buildButton( + context, + 'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}', + StreamSvgIcon.Icon_save( + size: 24.0, + color: StreamChatTheme.of(context).colorTheme.grey, + ), + () async { + var url = urls[currentIndex].imageUrl ?? + urls[currentIndex].assetUrl ?? + urls[currentIndex].thumbUrl; - if (urls[currentIndex].type == 'video') { - await _saveVideo(url); - Navigator.pop(context); - } else { - await _saveImage(url); - Navigator.pop(context); - } - }), - if (StreamChat.of(context).user.id == message.user.id) - _buildButton( - context, - 'Delete', - StreamSvgIcon.delete( - size: 24.0, - color: StreamChatTheme.of(context) - .colorTheme - .accentRed, - ), - () { - Navigator.pop(context); - Navigator.pop(context); - StreamChat.of(context).client.deleteMessage( - message, - StreamChannel.of(context).channel.cid, - ); - }, - color: StreamChatTheme.of(context) - .colorTheme - .accentRed, - ), - ], - ).toList(), + if (urls[currentIndex].type == 'video') { + await _saveVideo(url); + Navigator.pop(context); + } else { + await _saveImage(url); + Navigator.pop(context); + } + }, ), - ), + if (StreamChat.of(context).user.id == message.user.id) + _buildButton( + context, + 'Delete', + StreamSvgIcon.delete( + size: 24.0, + color: StreamChatTheme.of(context).colorTheme.accentRed, + ), + () { + Navigator.pop(context); + Navigator.pop(context); + StreamChat.of(context).client.deleteMessage( + message, + StreamChannel.of(context).channel.cid, + ); + }, + color: StreamChatTheme.of(context).colorTheme.accentRed, + ), + ] + .map((e) => + Align(alignment: Alignment.centerRight, child: e)) + .insertBetween( + Container( + height: 1, + color: + StreamChatTheme.of(context).colorTheme.greyWhisper, + ), + ), ), ), ), - ], - ), + ) + ], ); } Widget _buildButton( - context, String title, StreamSvgIcon icon, VoidCallback onTap, - {Color color}) { - var titleStyle = TextStyle( - fontSize: 14.5, - color: StreamChatTheme.of(context).colorTheme.black, - ); - + context, + String title, + StreamSvgIcon icon, + VoidCallback onTap, { + Color color, + }) { return Material( color: StreamChatTheme.of(context).colorTheme.white, child: InkWell( onTap: onTap, - child: ListTile( - dense: true, - title: Text( - title, - style: - color == null ? titleStyle : titleStyle.copyWith(color: color), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Row( + children: [ + icon, + SizedBox(width: 16), + Text( + title, + style: StreamChatTheme.of(context) + .textTheme + .body + .copyWith(color: color), + ), + ], ), - leading: icon, ), ), ); diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart index 2d125bab..9b93695e 100644 --- a/lib/src/message_actions_modal.dart +++ b/lib/src/message_actions_modal.dart @@ -14,6 +14,7 @@ import 'message_input.dart'; import 'message_widget.dart'; import 'stream_chat.dart'; import 'stream_chat_theme.dart'; +import 'extension.dart'; class MessageActionsModal extends StatefulWidget { final Widget Function(BuildContext, Message) editMessageInputBuilder; @@ -89,6 +90,8 @@ class _MessageActionsModalState extends State { duration: Duration(milliseconds: 300), curve: Curves.easeInOutBack, builder: (context, val, snapshot) { + final hasFileAttachment = + widget.message.attachments?.any((it) => it.type == 'file') == true; return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => Navigator.maybePop(context), @@ -110,7 +113,7 @@ class _MessageActionsModalState extends State { child: Center( child: SingleChildScrollView( child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), + padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: widget.reverse ? CrossAxisAlignment.end @@ -135,6 +138,7 @@ class _MessageActionsModalState extends State { messageTheme: widget.messageTheme, ), ), + SizedBox(height: 8), IgnorePointer( child: MessageWidget( key: Key('MessageWidget'), @@ -150,8 +154,17 @@ class _MessageActionsModalState extends State { showThreadReplyIndicator: false, showReplyMessage: false, showUserAvatar: widget.showUserAvatar, + attachmentPadding: EdgeInsets.all( + hasFileAttachment ? 4 : 2, + ), showTimestamp: false, translateUserAvatar: false, + padding: const EdgeInsets.all(0), + textPadding: EdgeInsets.symmetric( + vertical: 8.0, + horizontal: + widget.message.text.isOnlyEmoji ? 0 : 16.0, + ), showReactionPickerIndicator: widget.showReactions && (widget.message.status == @@ -162,10 +175,10 @@ class _MessageActionsModalState extends State { shape: widget.messageShape, ), ), + SizedBox(height: 8), Padding( padding: EdgeInsets.only( - right: widget.reverse ? 8 : 0, - left: widget.reverse ? 0 : 48, + left: widget.reverse ? 0 : 40, ), child: SizedBox( width: MediaQuery.of(context).size.width * 0.75, @@ -180,36 +193,37 @@ class _MessageActionsModalState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, - children: ListTile.divideTiles( - color: StreamChatTheme.of(context) - .colorTheme - .greyWhisper, - context: context, - tiles: [ - if (widget.showReplyMessage && - (widget.message.status == - MessageSendingStatus.SENT || - widget.message.status == null) && - widget.message.parentId == null) - _buildReplyButton(context), - if (widget.showThreadReplyMessage && - (widget.message.status == - MessageSendingStatus.SENT || - widget.message.status == null) && - widget.message.parentId == null) - _buildThreadReplyButton(context), - if (widget.showResendMessage) - _buildResendMessage(context), - if (widget.showEditMessage) - _buildEditMessage(context), - if (widget.showCopyMessage) - _buildCopyButton(context), - if (widget.showFlagButton) - _buildFlagButton(context), - if (widget.showDeleteMessage) - _buildDeleteButton(context), - ], - ).toList(), + children: [ + if (widget.showReplyMessage && + (widget.message.status == + MessageSendingStatus.SENT || + widget.message.status == null) && + widget.message.parentId == null) + _buildReplyButton(context), + if (widget.showThreadReplyMessage && + (widget.message.status == + MessageSendingStatus.SENT || + widget.message.status == null) && + widget.message.parentId == null) + _buildThreadReplyButton(context), + if (widget.showResendMessage) + _buildResendMessage(context), + if (widget.showEditMessage) + _buildEditMessage(context), + if (widget.showCopyMessage) + _buildCopyButton(context), + if (widget.showFlagButton) + _buildFlagButton(context), + if (widget.showDeleteMessage) + _buildDeleteButton(context), + ].insertBetween( + Container( + height: 1, + color: StreamChatTheme.of(context) + .colorTheme + .greyWhisper, + ), + ), ), ), ), @@ -410,136 +424,131 @@ class _MessageActionsModalState extends State { } Widget _buildReplyButton(BuildContext context) { - return ListTile( - dense: true, - title: Row( - children: [ - StreamSvgIcon.reply( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Reply', - style: StreamChatTheme.of(context).textTheme.headline, - ), - ], - ), + return InkWell( onTap: () { Navigator.pop(context); if (widget.onReplyTap != null) { widget.onReplyTap(widget.message); } }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Row( + children: [ + StreamSvgIcon.reply( + color: StreamChatTheme.of(context).primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Reply', + style: StreamChatTheme.of(context).textTheme.body, + ), + ], + ), + ), ); } Widget _buildFlagButton(BuildContext context) { - return ListTile( - dense: true, - title: Row( - children: [ - StreamSvgIcon.icon_flag( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Flag', - style: StreamChatTheme.of(context).textTheme.headline, - ), - ], - ), + return InkWell( onTap: () => _showFlagDialog(), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Row( + children: [ + StreamSvgIcon.icon_flag( + color: StreamChatTheme.of(context).primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Flag', + style: StreamChatTheme.of(context).textTheme.body, + ), + ], + ), + ), ); } Widget _buildDeleteButton(BuildContext context) { final isDeleteFailed = widget.message.status == MessageSendingStatus.FAILED_DELETE; - return ListTile( - dense: true, - title: Row( - children: [ - StreamSvgIcon.delete( - color: Colors.red, - ), - const SizedBox(width: 16), - Text( - isDeleteFailed ? 'Retry Deleting Message' : 'Delete Message', - style: StreamChatTheme.of(context) - .textTheme - .headline - .copyWith(color: Colors.red), - ), - ], + return InkWell( + onTap: () => _showDeleteDialog(), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Row( + children: [ + StreamSvgIcon.delete( + color: Colors.red, + ), + const SizedBox(width: 16), + Text( + isDeleteFailed ? 'Retry Deleting Message' : 'Delete Message', + style: StreamChatTheme.of(context) + .textTheme + .body + .copyWith(color: Colors.red), + ), + ], + ), ), - onTap: () { - _showDeleteDialog(); - }, ); } Widget _buildCopyButton(BuildContext context) { - return ListTile( - dense: true, - title: Row( - children: [ - StreamSvgIcon.copy( - size: 24, - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Copy Message', - style: StreamChatTheme.of(context).textTheme.headline, - ), - ], - ), + return InkWell( onTap: () async { await Clipboard.setData(ClipboardData(text: widget.message.text)); Navigator.pop(context); }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Row( + children: [ + StreamSvgIcon.copy( + size: 24, + color: StreamChatTheme.of(context).primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Copy Message', + style: StreamChatTheme.of(context).textTheme.body, + ), + ], + ), + ), ); } Widget _buildEditMessage(BuildContext context) { - return ListTile( - dense: true, - title: Row( - children: [ - StreamSvgIcon.edit( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Edit Message', - style: StreamChatTheme.of(context).textTheme.headline, - ), - ], - ), + return InkWell( onTap: () async { Navigator.pop(context); _showEditBottomSheet(context); }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Row( + children: [ + StreamSvgIcon.edit( + color: StreamChatTheme.of(context).primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Edit Message', + style: StreamChatTheme.of(context).textTheme.body, + ), + ], + ), + ), ); } Widget _buildResendMessage(BuildContext context) { final isUpdateFailed = widget.message.status == MessageSendingStatus.FAILED_UPDATE; - return ListTile( - dense: true, - title: Row( - children: [ - StreamSvgIcon.circle_up( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ), - const SizedBox(width: 16), - Text( - isUpdateFailed ? 'Resend Edited Message' : 'Resend', - style: StreamChatTheme.of(context).textTheme.headline, - ), - ], - ), + return InkWell( onTap: () { Navigator.pop(context); final client = StreamChat.of(context).client; @@ -550,6 +559,21 @@ class _MessageActionsModalState extends State { channel.sendMessage(widget.message); } }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Row( + children: [ + StreamSvgIcon.circle_up( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + ), + const SizedBox(width: 16), + Text( + isUpdateFailed ? 'Resend Edited Message' : 'Resend', + style: StreamChatTheme.of(context).textTheme.body, + ), + ], + ), + ), ); } @@ -623,26 +647,28 @@ class _MessageActionsModalState extends State { } Widget _buildThreadReplyButton(BuildContext context) { - return ListTile( - dense: true, - title: Row( - children: [ - StreamSvgIcon.thread( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Thread Reply', - style: StreamChatTheme.of(context).textTheme.headline, - ), - ], - ), + return InkWell( onTap: () { Navigator.pop(context); if (widget.onThreadReplyTap != null) { widget.onThreadReplyTap(widget.message); } }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Row( + children: [ + StreamSvgIcon.thread( + color: StreamChatTheme.of(context).primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Thread Reply', + style: StreamChatTheme.of(context).textTheme.body, + ), + ], + ), + ), ); } } diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index cc12fbd9..22a36882 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -1527,14 +1527,12 @@ class MessageInputState extends State { return Transform( transform: Matrix4.rotationY(pi), alignment: Alignment.center, - child: Padding( + child: QuotedMessageWidget( + reverse: true, + showBorder: !containsUrl, + message: widget.quotedMessage, + messageTheme: StreamChatTheme.of(context).otherMessageTheme, padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), - child: QuotedMessageWidget( - reverse: true, - showBorder: !containsUrl, - message: widget.quotedMessage, - messageTheme: StreamChatTheme.of(context).otherMessageTheme, - ), ), ); } @@ -1544,17 +1542,17 @@ class MessageInputState extends State { return Column( children: [ if (_attachments.any((e) => e.attachment?.type == 'file')) - LimitedBox( - maxHeight: 136.0, - child: ListView( - reverse: true, - shrinkWrap: true, - children: _attachments.reversed - .where((e) => e.attachment?.type == 'file') - .map( - (e) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: ClipRRect( + Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), + child: LimitedBox( + maxHeight: 136.0, + child: ListView( + reverse: true, + shrinkWrap: true, + children: _attachments.reversed + .where((e) => e.attachment?.type == 'file') + .map( + (e) => ClipRRect( borderRadius: BorderRadius.circular(10), clipBehavior: Clip.antiAlias, child: FileAttachment( @@ -1580,31 +1578,28 @@ class MessageInputState extends State { .white, ), ), - onTap: () { - setState(() { - _attachments.remove(e); - }); - }, + onTap: () => + setState(() => _attachments.remove(e)), ), ), ), ), - ), - ) - .toList(), + ) + .insertBetween(const SizedBox(width: 8)), + ), ), ), if (_attachments.any((e) => e.attachment?.type != 'file')) - LimitedBox( - maxHeight: 104.0, - child: ListView( - scrollDirection: Axis.horizontal, - children: _attachments - .where((e) => e.attachment?.type != 'file') - .map( - (attachment) => Padding( - padding: const EdgeInsets.all(8.0), - child: ClipRRect( + Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), + child: LimitedBox( + maxHeight: 104.0, + child: ListView( + scrollDirection: Axis.horizontal, + children: _attachments + .where((e) => e.attachment?.type != 'file') + .map( + (attachment) => ClipRRect( borderRadius: BorderRadius.circular(10), clipBehavior: Clip.antiAlias, child: Stack( @@ -1631,9 +1626,9 @@ class MessageInputState extends State { ], ), ), - ), - ) - .toList(), + ) + .insertBetween(const SizedBox(width: 8)), + ), ), ), ], diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 8dee39ed..a0b5a60d 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -770,6 +770,10 @@ class _MessageListViewState extends State { topRight: Radius.circular(16), bottomRight: Radius.circular(16), ), + textPadding: EdgeInsets.symmetric( + vertical: 8.0, + horizontal: isOnlyEmoji ? 0 : 16.0, + ), borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null, showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, messageTheme: isMyMessage @@ -817,7 +821,7 @@ class _MessageListViewState extends State { final allRead = readList.length >= (channel.memberCount ?? 0) - 1; final hasFileAttachment = - message.attachments.any((it) => it.type == 'file'); + message.attachments?.any((it) => it.type == 'file') == true; final isThreadMessage = message?.parentId != null && message?.showInChannel == true; @@ -849,13 +853,11 @@ class _MessageListViewState extends State { final showThreadReplyIndicator = !_isThreadConversation && hasReplies; final isOnlyEmoji = message.text.isOnlyEmoji; - final showMessageBorder = - showThreadReplyIndicator || showInChannelIndicator; - final borderSide = isMyMessage - ? !showMessageBorder - ? BorderSide.none - : null - : isOnlyEmoji && !showMessageBorder + final hasUrlAttachment = + message.attachments?.any((it) => it.ogScrapeUrl != null) == true; + + final borderSide = + isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment) ? BorderSide.none : null; @@ -901,14 +903,15 @@ class _MessageListViewState extends State { attachmentBorderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(attachmentBorderRadius), bottomLeft: Radius.circular( - (timeDiff >= 1 || !isNextUserSame) && !(hasReplies || isThreadMessage) + (timeDiff >= 1 || !isNextUserSame) && + !(hasReplies || isThreadMessage || hasFileAttachment) ? 0 : attachmentBorderRadius, ), topRight: Radius.circular(attachmentBorderRadius), bottomRight: Radius.circular(attachmentBorderRadius), ), - attachmentPadding: const EdgeInsets.all(2), + attachmentPadding: EdgeInsets.all(hasFileAttachment ? 4 : 2), borderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(16), bottomLeft: Radius.circular( @@ -919,6 +922,10 @@ class _MessageListViewState extends State { topRight: Radius.circular(16), bottomRight: Radius.circular(16), ), + textPadding: EdgeInsets.symmetric( + vertical: 8.0, + horizontal: isOnlyEmoji ? 0 : 16.0, + ), messageTheme: isMyMessage ? StreamChatTheme.of(context).ownMessageTheme : StreamChatTheme.of(context).otherMessageTheme, diff --git a/lib/src/message_reactions_modal.dart b/lib/src/message_reactions_modal.dart index 15a301cc..c96b3afa 100644 --- a/lib/src/message_reactions_modal.dart +++ b/lib/src/message_reactions_modal.dart @@ -9,6 +9,7 @@ import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'message_widget.dart'; import 'stream_chat_theme.dart'; +import 'extension.dart'; class MessageReactionsModal extends StatelessWidget { final Widget Function(BuildContext, Message) editMessageInputBuilder; @@ -61,6 +62,8 @@ class MessageReactionsModal extends StatelessWidget { duration: Duration(milliseconds: 300), curve: Curves.easeInOutBack, builder: (context, val, snapshot) { + final hasFileAttachment = + message.attachments?.any((it) => it.type == 'file') == true; return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => Navigator.maybePop(context), @@ -82,7 +85,7 @@ class MessageReactionsModal extends StatelessWidget { child: Center( child: SingleChildScrollView( child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), + padding: const EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -105,6 +108,7 @@ class MessageReactionsModal extends StatelessWidget { messageTheme: messageTheme, ), ), + const SizedBox(height: 8), IgnorePointer( child: MessageWidget( key: Key('MessageWidget'), @@ -123,15 +127,25 @@ class MessageReactionsModal extends StatelessWidget { translateUserAvatar: false, showSendingIndicator: false, shape: messageShape, + padding: const EdgeInsets.all(0), + attachmentPadding: EdgeInsets.all( + hasFileAttachment ? 4 : 2, + ), showInChannelIndicator: false, + textPadding: EdgeInsets.symmetric( + vertical: 8.0, + horizontal: message.text.isOnlyEmoji ? 0 : 16.0, + ), showReactionPickerIndicator: showReactions && (message.status == MessageSendingStatus.SENT || message.status == null), ), ), - if (message.latestReactions?.isNotEmpty == true) + if (message.latestReactions?.isNotEmpty == true) ...[ + const SizedBox(height: 8), _buildReactionCard(context), + ] ], ), ), @@ -145,47 +159,42 @@ class MessageReactionsModal extends StatelessWidget { ); } - Padding _buildReactionCard(BuildContext context) { + Widget _buildReactionCard(BuildContext context) { final currentUser = StreamChat.of(context).user; - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, + return Card( + color: StreamChatTheme.of(context).colorTheme.white, + clipBehavior: Clip.hardEdge, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), ), - child: Card( - color: StreamChatTheme.of(context).colorTheme.white, - clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - margin: EdgeInsets.zero, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Message Reactions', - style: StreamChatTheme.of(context).textTheme.headlineBold, - ), - const SizedBox(height: 16), - Flexible( - child: SingleChildScrollView( - child: Wrap( - spacing: 16, - runSpacing: 16, - alignment: WrapAlignment.start, - children: message.latestReactions - .map((e) => _buildReaction( - e, - currentUser, - context, - )) - .toList(), - ), + margin: EdgeInsets.zero, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Message Reactions', + style: StreamChatTheme.of(context).textTheme.headlineBold, + ), + const SizedBox(height: 16), + Flexible( + child: SingleChildScrollView( + child: Wrap( + spacing: 16, + runSpacing: 16, + alignment: WrapAlignment.start, + children: message.latestReactions + .map((e) => _buildReaction( + e, + currentUser, + context, + )) + .toList(), ), ), - ], - ), + ), + ], ), ), ); @@ -208,6 +217,7 @@ class MessageReactionsModal extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Stack( + overflow: Overflow.visible, children: [ UserAvatar( onTap: onUserAvatarTap, @@ -216,6 +226,10 @@ class MessageReactionsModal extends StatelessWidget { height: 64, width: 64, ), + onlineIndicatorConstraints: BoxConstraints.tightFor( + height: 12, + width: 12, + ), borderRadius: BorderRadius.circular(32), ), Positioned( @@ -227,12 +241,14 @@ class MessageReactionsModal extends StatelessWidget { flipTail: !reverse, borderColor: messageTheme.reactionsBorderColor, backgroundColor: messageTheme.reactionsBackgroundColor, + maskColor: StreamChatTheme.of(context).colorTheme.white, + tailCirclesSpacing: 1, highlightOwnReactions: false, ), ), bottom: 6, - left: isCurrentUser ? 0 : null, - right: isCurrentUser ? 0 : null, + left: isCurrentUser ? -3 : null, + right: isCurrentUser ? -3 : null, ), ], ), diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index dd0c49cd..070bdad5 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -278,6 +278,9 @@ class _MessageWidgetState extends State { ?.isNotEmpty == true; + bool get hasUrlAttachments => + widget.message.attachments?.any((it) => it.ogScrapeUrl != null) == true; + bool get showBottomRow => showThreadReplyIndicator || showUsername || @@ -416,7 +419,7 @@ class _MessageWidgetState extends State { if (hasQuotedMessage) _buildQuotedMessage(), if (hasNonUrlAttachments) - ..._parseAttachments( + _parseAttachments( context), if (widget.message.text .trim() @@ -441,9 +444,9 @@ class _MessageWidgetState extends State { StreamChatTheme.of(context) .colorTheme .white, - StreamChatTheme.of(context) - .colorTheme - .white, + Colors.transparent, + Colors.transparent, + tailCirclesSpace: 1, ), ), ), @@ -494,21 +497,15 @@ class _MessageWidgetState extends State { widget.onQuotedMessageTap != null ? () => widget.onQuotedMessageTap(widget.message.quotedMessageId) : null; - return Padding( + return QuotedMessageWidget( + onTap: onTap, + message: widget.message.quotedMessage, + messageTheme: isMyMessage + ? StreamChatTheme.of(context).otherMessageTheme + : StreamChatTheme.of(context).ownMessageTheme, + reverse: widget.reverse, padding: EdgeInsets.only( - right: 8, - left: 8, - top: 8, - bottom: hasNonUrlAttachments ? 8 : 0, - ), - child: QuotedMessageWidget( - onTap: onTap, - message: widget.message.quotedMessage, - messageTheme: isMyMessage - ? StreamChatTheme.of(context).otherMessageTheme - : StreamChatTheme.of(context).ownMessageTheme, - reverse: widget.reverse, - ), + right: 8, left: 8, top: 8, bottom: hasNonUrlAttachments ? 8 : 0), ); } @@ -564,11 +561,10 @@ class _MessageWidgetState extends State { }; children.addAll([ - if (showSendingIndicator) _buildSendingIndicator(), if (showInChannel || showThreadReplyIndicator) ...[ if (showThreadParticipants) SizedBox.fromSize( - size: Size((threadParticipants.length * 8.0) + 10, 16), + size: Size((threadParticipants.length * 8.0) + 8, 16), child: _buildThreadParticipantsIndicator(), ), InkWell( @@ -588,16 +584,18 @@ class _MessageWidgetState extends State { Jiffy(widget.message.createdAt.toLocal()).jm, style: widget.messageTheme.createdAt, ), + if (showSendingIndicator) _buildSendingIndicator(), ]); - if (widget.reverse) children = children.reversed.toList(); + final showThreadTail = !(hasUrlAttachments || isGiphy || isOnlyEmoji) && + (showThreadReplyIndicator || showInChannel); return Flex( direction: Axis.horizontal, clipBehavior: Clip.none, crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (showThreadReplyIndicator || showInChannel) + if (showThreadTail) Container( margin: EdgeInsets.only( bottom: widget.messageTheme.replies.fontSize / 2, @@ -648,18 +646,20 @@ class _MessageWidgetState extends State { var padding = 0.0; return Stack( children: widget.message.threadParticipants.map((user) { - padding += 10.0; + padding += 8.0; return Positioned( - left: padding - 10, + right: padding - 8, bottom: 0, top: 0, - child: Material( - color: Colors.white, - clipBehavior: Clip.antiAlias, - shape: CircleBorder(), + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: StreamChatTheme.of(context).colorTheme.white, + ), + padding: const EdgeInsets.all(1), child: UserAvatar( user: user, - constraints: BoxConstraints.loose(Size.fromRadius(8)), + constraints: BoxConstraints.loose(Size.fromRadius(7)), showOnlineStatus: false, ), ), @@ -694,6 +694,7 @@ class _MessageWidgetState extends State { flipTail: widget.reverse, backgroundColor: widget.messageTheme.reactionsBackgroundColor, borderColor: widget.messageTheme.reactionsBorderColor, + maskColor: widget.messageTheme.reactionsMaskColor, reactions: reactionsList, ), ) @@ -777,7 +778,7 @@ class _MessageWidgetState extends State { side: widget.attachmentBorderSide ?? widget.borderSide ?? BorderSide( - color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + color: StreamChatTheme.of(context).colorTheme.greyWhisper, ), borderRadius: widget.attachmentBorderRadiusGeometry ?? widget.borderRadiusGeometry ?? @@ -785,7 +786,7 @@ class _MessageWidgetState extends State { ); } - List _parseAttachments(BuildContext context) { + Widget _parseAttachments(BuildContext context) { final images = widget.message.attachments ?.where((element) => element.type == 'image' && element.ogScrapeUrl == null) @@ -793,8 +794,9 @@ class _MessageWidgetState extends State { []; if (images.length > 1) { - return [ - wrapAttachmentWidget( + return Padding( + padding: widget.attachmentPadding, + child: wrapAttachmentWidget( context, Material( color: widget.messageTheme.messageBackgroundColor, @@ -809,30 +811,36 @@ class _MessageWidgetState extends State { ), ), ), - ]; + ); } - return widget.message.attachments - ?.where((element) => element.ogScrapeUrl == null) - ?.map((attachment) { - final attachmentBuilder = widget.attachmentBuilders[attachment.type]; + return Padding( + padding: widget.attachmentPadding, + child: Column( + mainAxisSize: MainAxisSize.min, + children: widget.message.attachments + ?.where((element) => element.ogScrapeUrl == null) + ?.map((attachment) { + final attachmentBuilder = + widget.attachmentBuilders[attachment.type]; - if (attachmentBuilder == null) { - return SizedBox(); - } - - final attachmentWidget = attachmentBuilder( - context, - widget.message, - attachment, - ); - return wrapAttachmentWidget( - context, - attachmentWidget, - attachment: attachment, - ); - })?.toList() ?? - []; + if (attachmentBuilder == null) return SizedBox(); + final attachmentWidget = attachmentBuilder( + context, + widget.message, + attachment, + ); + return wrapAttachmentWidget( + context, + attachmentWidget, + attachment: attachment, + ); + })?.insertBetween(SizedBox( + height: widget.attachmentPadding.vertical / 2, + )) ?? + [], + ), + ); } Widget wrapAttachmentWidget( @@ -843,21 +851,13 @@ class _MessageWidgetState extends State { final attachmentShape = widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context); return Material( - color: _getBackgroundColor(), clipBehavior: Clip.antiAlias, shape: attachmentShape, - child: Padding( - padding: widget.attachmentPadding, - child: Material( - clipBehavior: Clip.hardEdge, - shape: attachmentShape, - type: MaterialType.transparency, - child: Transform( - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - alignment: Alignment.center, - child: attachmentWidget, - ), - ), + type: MaterialType.transparency, + child: Transform( + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + alignment: Alignment.center, + child: attachmentWidget, ), ); } @@ -944,11 +944,7 @@ class _MessageWidgetState extends State { : widget.messageTheme, ), ), - if (widget.message.attachments - ?.any((element) => element.ogScrapeUrl != null) == - true && - !hasQuotedMessage) - _buildUrlAttachment(), + if (hasUrlAttachments && !hasQuotedMessage) _buildUrlAttachment(), ], ), ); @@ -961,9 +957,7 @@ class _MessageWidgetState extends State { return widget.messageTheme.messageBackgroundColor; } - if (widget.message.attachments - ?.any((element) => element.ogScrapeUrl != null) == - true) { + if (hasUrlAttachments) { return StreamChatTheme.of(context).colorTheme.blueAlice; } diff --git a/lib/src/quoted_message_widget.dart b/lib/src/quoted_message_widget.dart index 309df703..e4e10e17 100644 --- a/lib/src/quoted_message_widget.dart +++ b/lib/src/quoted_message_widget.dart @@ -83,6 +83,8 @@ class QuotedMessageWidget extends StatelessWidget { final Map attachmentThumbnailBuilders; + final EdgeInsetsGeometry padding; + final GestureTapCallback onTap; /// @@ -94,6 +96,7 @@ class QuotedMessageWidget extends StatelessWidget { this.showBorder = false, this.textLimit = 170, this.attachmentThumbnailBuilders, + this.padding = const EdgeInsets.all(8), this.onTap, }) : super(key: key); @@ -107,16 +110,19 @@ class QuotedMessageWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return InkWell( - onTap: onTap, - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Flexible(child: _buildMessage(context)), - SizedBox(width: 8), - _buildUserAvatar(), - ], + return Padding( + padding: padding, + child: InkWell( + onTap: onTap, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible(child: _buildMessage(context)), + SizedBox(width: 8), + _buildUserAvatar(), + ], + ), ), ); } diff --git a/lib/src/reaction_bubble.dart b/lib/src/reaction_bubble.dart index 9eee9997..74a2ae99 100644 --- a/lib/src/reaction_bubble.dart +++ b/lib/src/reaction_bubble.dart @@ -12,17 +12,21 @@ class ReactionBubble extends StatelessWidget { @required this.reactions, @required this.borderColor, @required this.backgroundColor, + @required this.maskColor, this.reverse = false, this.flipTail = false, this.highlightOwnReactions = true, + this.tailCirclesSpacing = 0, }) : super(key: key); final List reactions; final Color borderColor; final Color backgroundColor; + final Color maskColor; final bool reverse; final bool flipTail; final bool highlightOwnReactions; + final double tailCirclesSpacing; @override Widget build(BuildContext context) { @@ -38,51 +42,58 @@ class ReactionBubble extends StatelessWidget { Transform.translate( offset: Offset(reverse ? offset : -offset, 0), child: Container( - padding: EdgeInsets.symmetric( - vertical: 4, - horizontal: totalReactions > 1 ? 4 : 0, - ), + padding: const EdgeInsets.all(2), decoration: BoxDecoration( - border: Border.all( - color: borderColor, - ), - color: backgroundColor, - borderRadius: BorderRadius.all(Radius.circular(14)), + color: maskColor, + borderRadius: BorderRadius.all(Radius.circular(16)), ), - child: LayoutBuilder( - builder: (context, constraints) { - return Flex( - direction: Axis.horizontal, - mainAxisSize: MainAxisSize.min, - children: [ - if (constraints.maxWidth < double.infinity) - ...reactions - .take((constraints.maxWidth) ~/ 22) - .map((reaction) { - return _buildReaction( - reactionIcons, - reaction, - context, - ); - }).toList(), - if (constraints.maxWidth == double.infinity) - ...reactions.map((reaction) { - return _buildReaction( - reactionIcons, - reaction, - context, - ); - }).toList(), - ], - ); - }, + child: Container( + padding: EdgeInsets.symmetric( + vertical: 4, + horizontal: totalReactions > 1 ? 4 : 0, + ), + decoration: BoxDecoration( + border: Border.all( + color: borderColor, + ), + color: backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(14)), + ), + child: LayoutBuilder( + builder: (context, constraints) { + return Flex( + direction: Axis.horizontal, + mainAxisSize: MainAxisSize.min, + children: [ + if (constraints.maxWidth < double.infinity) + ...reactions + .take((constraints.maxWidth) ~/ 22) + .map((reaction) { + return _buildReaction( + reactionIcons, + reaction, + context, + ); + }).toList(), + if (constraints.maxWidth == double.infinity) + ...reactions.map((reaction) { + return _buildReaction( + reactionIcons, + reaction, + context, + ); + }).toList(), + ], + ); + }, + ), ), ), ), Positioned( - bottom: 0, - left: reverse ? null : 11, - right: !reverse ? null : 11, + bottom: 2, + left: reverse ? null : 13, + right: !reverse ? null : 13, child: _buildReactionsTail(context), ), ], @@ -136,6 +147,8 @@ class ReactionBubble extends StatelessWidget { painter: ReactionBubblePainter( backgroundColor, borderColor, + maskColor, + tailCirclesSpace: tailCirclesSpacing, ), ); return Transform( @@ -149,14 +162,22 @@ class ReactionBubble extends StatelessWidget { class ReactionBubblePainter extends CustomPainter { final Color color; final Color borderColor; + final Color maskColor; + final double tailCirclesSpace; ReactionBubblePainter( this.color, this.borderColor, - ); + this.maskColor, { + this.tailCirclesSpace = 0, + }); @override void paint(Canvas canvas, Size size) { + _drawOvalMask(size, canvas); + + _drawMask(size, canvas); + _drawOval(size, canvas); _drawOvalBorder(size, canvas); @@ -166,6 +187,21 @@ class ReactionBubblePainter extends CustomPainter { _drawBorder(size, canvas); } + void _drawOvalMask(Size size, Canvas canvas) { + final paint = Paint() + ..color = maskColor + ..style = PaintingStyle.fill; + + final path = Path(); + path.addOval( + Rect.fromCircle( + center: Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace), + radius: 4, + ), + ); + canvas.drawPath(path, paint); + } + void _drawOvalBorder(Size size, Canvas canvas) { final paint = Paint() ..color = borderColor @@ -175,7 +211,7 @@ class ReactionBubblePainter extends CustomPainter { final path = Path(); path.addOval( Rect.fromCircle( - center: Offset(4, 3), + center: Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace), radius: 2, ), ); @@ -189,7 +225,7 @@ class ReactionBubblePainter extends CustomPainter { final path = Path(); path.addOval(Rect.fromCircle( - center: Offset(4, 3), + center: Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace), radius: 2, )); canvas.drawPath(path, paint); @@ -236,6 +272,27 @@ class ReactionBubblePainter extends CustomPainter { canvas.drawPath(path, paint); } + void _drawMask(Size size, Canvas canvas) { + final paint = Paint() + ..color = maskColor + ..strokeWidth = 1 + ..style = PaintingStyle.fill; + + final dy = -2.2; + final startAngle = 1.1; + final sweepAngle = 1.2; + final path = Path(); + path.addArc( + Rect.fromCircle( + center: Offset(1, dy), + radius: 6, + ), + -pi * startAngle, + -pi / sweepAngle, + ); + canvas.drawPath(path, paint); + } + @override bool shouldRepaint(CustomPainter oldDelegate) { return true; diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index c6d2d72f..70845ba9 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -234,6 +234,7 @@ class StreamChatThemeData { messageBackgroundColor: colorTheme.greyGainsboro, reactionsBackgroundColor: colorTheme.white, reactionsBorderColor: colorTheme.greyWhisper, + reactionsMaskColor: colorTheme.whiteSnow, messageBorderColor: colorTheme.greyGainsboro, avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), @@ -249,6 +250,7 @@ class StreamChatThemeData { otherMessageTheme: MessageTheme( reactionsBackgroundColor: colorTheme.greyGainsboro, reactionsBorderColor: colorTheme.white, + reactionsMaskColor: colorTheme.whiteSnow, messageText: textTheme.body, createdAt: textTheme.footnote.copyWith(color: colorTheme.grey), replies: textTheme.footnoteBold.copyWith(color: accentColor), @@ -710,6 +712,7 @@ class MessageTheme { final Color messageBorderColor; final Color reactionsBackgroundColor; final Color reactionsBorderColor; + final Color reactionsMaskColor; final AvatarTheme avatarTheme; const MessageTheme({ @@ -721,6 +724,7 @@ class MessageTheme { this.messageBorderColor, this.reactionsBackgroundColor, this.reactionsBorderColor, + this.reactionsMaskColor, this.avatarTheme, this.createdAt, }); @@ -736,6 +740,7 @@ class MessageTheme { AvatarTheme avatarTheme, Color reactionsBackgroundColor, Color reactionsBorderColor, + Color reactionsMaskColor, }) => MessageTheme( messageText: messageText ?? this.messageText, @@ -750,6 +755,7 @@ class MessageTheme { reactionsBackgroundColor: reactionsBackgroundColor ?? this.reactionsBackgroundColor, reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor, + reactionsMaskColor: reactionsMaskColor ?? this.reactionsMaskColor, ); MessageTheme merge(MessageTheme other) { @@ -767,6 +773,7 @@ class MessageTheme { avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, reactionsBackgroundColor: other.reactionsBackgroundColor, reactionsBorderColor: other.reactionsBorderColor, + reactionsMaskColor: other.reactionsMaskColor, ); } } diff --git a/lib/src/user_avatar.dart b/lib/src/user_avatar.dart index 688eff77..706c4d7e 100644 --- a/lib/src/user_avatar.dart +++ b/lib/src/user_avatar.dart @@ -92,18 +92,18 @@ class UserAvatar extends StatelessWidget { child: Material( type: MaterialType.circle, child: Container( - padding: const EdgeInsets.all(2.0), + margin: const EdgeInsets.all(2.0), constraints: onlineIndicatorConstraints ?? BoxConstraints.tightFor( - width: 12, - height: 12, + width: 8, + height: 8, ), child: Material( shape: CircleBorder(), color: streamChatTheme.colorTheme.accentGreen, ), ), - color: StreamChatTheme.of(context).colorTheme.white, + color: streamChatTheme.colorTheme.white, ), ), ), diff --git a/test/src/reaction_bubble_test.dart b/test/src/reaction_bubble_test.dart index c0bc8cd1..0b6c90ef 100644 --- a/test/src/reaction_bubble_test.dart +++ b/test/src/reaction_bubble_test.dart @@ -19,6 +19,7 @@ void main() { reactions: [], borderColor: Colors.black, backgroundColor: Colors.white, + maskColor: Colors.white, ), ), ), @@ -56,6 +57,7 @@ void main() { ], borderColor: Colors.black, backgroundColor: Colors.white, + maskColor: Colors.white, ), ), ), @@ -96,6 +98,7 @@ void main() { ], borderColor: Colors.black, backgroundColor: Colors.white, + maskColor: Colors.white, ), ), ),