diff --git a/packages/flutter_widgets/example/lib/group_info_screen.dart b/packages/flutter_widgets/example/lib/group_info_screen.dart index bde4c0df..cac6fc9c 100644 --- a/packages/flutter_widgets/example/lib/group_info_screen.dart +++ b/packages/flutter_widgets/example/lib/group_info_screen.dart @@ -612,7 +612,10 @@ class _GroupInfoScreenState extends State { body: UsersBloc( child: Column( children: [ - _buildTextInputSection(modalSetState), + Padding( + padding: const EdgeInsets.all(16), + child: _buildTextInputSection(modalSetState), + ), Expanded( child: UserListView( selectedUsers: {}, @@ -690,66 +693,57 @@ class _GroupInfoScreenState extends State { } Widget _buildTextInputSection(modalSetState) { + final theme = StreamChatTheme.of(context); return Column( children: [ - SizedBox( - height: 16.0, - ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SizedBox( - width: 16.0, - ), Expanded( - child: TextField( - controller: _searchController, - cursorColor: StreamChatTheme.of(context).colorTheme.black, - autofocus: true, - decoration: InputDecoration( - isDense: true, - prefixIconConstraints: BoxConstraints.tight(Size(36.0, 44.0)), - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - vertical: 2.0, horizontal: 6.0), - child: StreamSvgIcon.search( - color: StreamChatTheme.of(context).colorTheme.black, + child: Container( + height: 36, + child: TextField( + controller: _searchController, + cursorColor: theme.colorTheme.black, + autofocus: true, + decoration: InputDecoration( + hintText: 'Search', + hintStyle: theme.textTheme.body.copyWith( + color: theme.colorTheme.grey, ), + prefixIconConstraints: BoxConstraints.tight(Size(40, 24)), + prefixIcon: StreamSvgIcon.search( + color: theme.colorTheme.black, + size: 24, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(24.0), + borderSide: BorderSide( + color: theme.colorTheme.greyWhisper, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(24.0), + borderSide: BorderSide( + color: theme.colorTheme.greyWhisper, + )), + contentPadding: const EdgeInsets.all(0), ), - hintText: 'Search', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(32.0), - borderSide: BorderSide( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.08)), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(32.0), - borderSide: BorderSide( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.08)), - ), - contentPadding: EdgeInsets.zero, ), ), ), - SizedBox( - width: 8.0, - ), + SizedBox(width: 16.0), IconButton( icon: StreamSvgIcon.close_small( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), + color: theme.colorTheme.grey, ), - onPressed: () { - Navigator.pop(context); - }, + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + padding: EdgeInsets.zero, + splashRadius: 24, + onPressed: () => Navigator.pop(context), ) ], ), diff --git a/packages/flutter_widgets/example/lib/main.dart b/packages/flutter_widgets/example/lib/main.dart index 7b2f578a..2c2c5bab 100644 --- a/packages/flutter_widgets/example/lib/main.dart +++ b/packages/flutter_widgets/example/lib/main.dart @@ -457,133 +457,132 @@ class _ChannelListPageState extends State { }, child: ChannelsBloc( child: MessageSearchBloc( - child: Column( - children: [ - SearchTextField( - controller: _controller, - showCloseButton: _isSearchActive, - ), - Expanded( - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 350), - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onPanDown: (_) => FocusScope.of(context).unfocus(), - child: _isSearchActive - ? MessageSearchListView( - messageQuery: _channelQuery, - filters: { - 'members': { - r'$in': [user.id] - } - }, - sortOptions: [ - SortOption( - 'created_at', - direction: SortOption.ASC, - ), - ], - pullToRefresh: false, - paginationParams: PaginationParams(limit: 20), - emptyBuilder: (_, query) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: - viewportConstraints.maxHeight, - ), - child: Center( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(24), - child: StreamSvgIcon.search( - size: 96, - color: Colors.grey, - ), - ), - Text( - 'No results for \"$query\"...', - ), - ], - ), - ), - ), - ); - }, - ); - }, - onItemTap: (messageResponse) async { - final client = StreamChat.of(context).client; - final message = messageResponse.message; - final channel = client.channel( - messageResponse.channel.type, - id: messageResponse.channel.id, - ); - if (channel.state == null) { - await channel.watch(); - } - Navigator.pushNamed( - context, - Routes.CHANNEL_PAGE, - arguments: ChannelPageArgs( - channel: channel, - initialMessage: message, - ), - ); - }, - ) - : ChannelListView( - onStartChatPressed: () { - Navigator.pushNamed(context, Routes.NEW_CHAT); - }, - swipeToAction: true, - filter: { - 'members': { - r'$in': [user.id], - }, - }, - options: { - 'presence': true, - }, - pagination: PaginationParams( - limit: 20, - ), - channelWidget: ChannelPage(), - onViewInfoTap: (channel) { - if (channel.memberCount == 2 && - channel.isDistinct) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: ChatInfoScreen( - user: channel.state.members.first.user, - ), - ), - ), - ); - } else { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: GroupInfoScreen(), - ), - ), - ); - } - }, - ), - ), + child: NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: (_, __) => [ + SliverToBoxAdapter( + child: SearchTextField( + controller: _controller, + showCloseButton: _isSearchActive, ), ), ], + body: AnimatedSwitcher( + duration: const Duration(milliseconds: 350), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: _isSearchActive + ? MessageSearchListView( + messageQuery: _channelQuery, + filters: { + 'members': { + r'$in': [user.id] + } + }, + sortOptions: [ + SortOption( + 'created_at', + direction: SortOption.ASC, + ), + ], + pullToRefresh: false, + paginationParams: PaginationParams(limit: 20), + emptyBuilder: (_, query) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: StreamSvgIcon.search( + size: 96, + color: Colors.grey, + ), + ), + Text( + 'No results for \"$query\"...', + ), + ], + ), + ), + ), + ); + }, + ); + }, + onItemTap: (messageResponse) async { + final client = StreamChat.of(context).client; + final message = messageResponse.message; + final channel = client.channel( + messageResponse.channel.type, + id: messageResponse.channel.id, + ); + if (channel.state == null) { + await channel.watch(); + } + Navigator.pushNamed( + context, + Routes.CHANNEL_PAGE, + arguments: ChannelPageArgs( + channel: channel, + initialMessage: message, + ), + ); + }, + ) + : ChannelListView( + onStartChatPressed: () { + Navigator.pushNamed(context, Routes.NEW_CHAT); + }, + swipeToAction: true, + filter: { + 'members': { + r'$in': [user.id], + }, + }, + options: { + 'presence': true, + }, + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), + onViewInfoTap: (channel) { + if (channel.memberCount == 2 && channel.isDistinct) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: ChatInfoScreen( + user: channel.state.members.first.user, + ), + ), + ), + ); + } else { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: GroupInfoScreen(), + ), + ), + ); + } + }, + ), + ), + ), ), ), ), diff --git a/packages/flutter_widgets/example/pubspec.yaml b/packages/flutter_widgets/example/pubspec.yaml index f4197d99..38c25b65 100644 --- a/packages/flutter_widgets/example/pubspec.yaml +++ b/packages/flutter_widgets/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.1.1+3 +version: 1.1.2 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/packages/flutter_widgets/lib/src/channel_list_view.dart b/packages/flutter_widgets/lib/src/channel_list_view.dart index 1ab10934..2de00c0e 100644 --- a/packages/flutter_widgets/lib/src/channel_list_view.dart +++ b/packages/flutter_widgets/lib/src/channel_list_view.dart @@ -729,17 +729,11 @@ class _ChannelListViewState extends State } Widget _separatorBuilder(context, i) { - var effect = StreamChatTheme.of(context).colorTheme.borderTop; + var effect = StreamChatTheme.of(context).colorTheme.borderBottom; - return BackdropFilter( - filter: ui.ImageFilter.blur( - sigmaX: effect.sigmaX, - sigmaY: effect.sigmaY, - ), - child: Container( - height: 1, - color: effect.color.withOpacity(0.08), - ), + return Container( + height: 1, + color: effect.color.withOpacity(effect.alpha ?? 1.0), ); } diff --git a/packages/flutter_widgets/lib/src/extension.dart b/packages/flutter_widgets/lib/src/extension.dart index 3ffd0e24..ad26735a 100644 --- a/packages/flutter_widgets/lib/src/extension.dart +++ b/packages/flutter_widgets/lib/src/extension.dart @@ -15,13 +15,7 @@ extension StringExtension on String { final characters = this.trim().characters; if (characters.isEmpty) return false; if (characters.length > 3) return false; - return characters.every((c) { - return _emojis.firstWhere( - (Emoji emoji) => emoji.char.contains(c), - orElse: () => null, - ) != - null; - }); + return characters.every((c) => _emojis.map((e) => e.char).contains(c)); } } diff --git a/packages/flutter_widgets/lib/src/image_footer.dart b/packages/flutter_widgets/lib/src/image_footer.dart index 9daf9a5c..8d4ca8b5 100644 --- a/packages/flutter_widgets/lib/src/image_footer.dart +++ b/packages/flutter_widgets/lib/src/image_footer.dart @@ -174,117 +174,138 @@ class _ImageFooterState extends State { showModalBottomSheet( context: context, barrierColor: StreamChatTheme.of(context).colorTheme.overlay, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.0), + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: const BorderRadius.only( + topLeft: const Radius.circular(16.0), + topRight: const Radius.circular(16.0), + ), ), builder: (context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, - borderRadius: BorderRadius.only( - topRight: Radius.circular(16.0), - topLeft: Radius.circular(16.0), - )), - child: Stack( + final crossAxisCount = 3; + final noOfRowToShowInitially = + widget.mediaAttachments.length > crossAxisCount ? 2 : 1; + final size = MediaQuery.of(context).size; + final initialChildSize = + 48 + (size.width * noOfRowToShowInitially) / crossAxisCount; + return DraggableScrollableSheet( + expand: false, + initialChildSize: initialChildSize / size.height, + builder: (context, scrollController) { + return SingleChildScrollView( + controller: scrollController, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - 'Photos', - style: - StreamChatTheme.of(context).textTheme.headlineBold, + Stack( + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Photos', + style: StreamChatTheme.of(context) + .textTheme + .headlineBold, + ), + ), ), - ), + Align( + alignment: Alignment.centerRight, + child: IconButton( + icon: StreamSvgIcon.close( + color: StreamChatTheme.of(context).colorTheme.black, + ), + onPressed: () => Navigator.maybePop(context), + ), + ), + ], ), - Align( - alignment: Alignment.centerRight, - child: IconButton( - icon: StreamSvgIcon.close( - color: StreamChatTheme.of(context).colorTheme.black, + Flexible( + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.mediaAttachments.length, + padding: const EdgeInsets.all(1), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 2.0, + crossAxisSpacing: 2.0, ), - onPressed: () => Navigator.maybePop(context), + itemBuilder: (context, index) { + Widget media; + final attachment = widget.mediaAttachments[index]; + + if (attachment.type == 'video') { + var controllerPackage = widget.videoPackages[ + videoAttachments.indexOf(attachment)]; + + media = InkWell( + onTap: () => widget.mediaSelectedCallBack(index), + child: FittedBox( + fit: BoxFit.cover, + child: Chewie( + controller: controllerPackage.chewieController, + ), + ), + ); + } else { + media = InkWell( + onTap: () => widget.mediaSelectedCallBack(index), + child: AspectRatio( + child: CachedNetworkImage( + imageUrl: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + fit: BoxFit.cover, + ), + aspectRatio: 1.0, + ), + ); + } + + return Stack( + children: [ + media, + Padding( + padding: EdgeInsets.all(8.0), + child: Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: StreamChatTheme.of(context) + .colorTheme + .white + .withOpacity(0.6), + boxShadow: [ + BoxShadow( + blurRadius: 8.0, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.3), + ), + ], + ), + padding: const EdgeInsets.all(1), + child: UserAvatar( + user: widget.message.user, + constraints: + BoxConstraints.tight(Size(24, 24)), + showOnlineStatus: false, + ), + ), + ), + ], + ); + }, ), ), ], ), - ), - Container( - color: StreamChatTheme.of(context).colorTheme.white, - child: GridView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, position) { - Widget media; - - if (widget.mediaAttachments[position].type == 'video') { - var controllerPackage = widget.videoPackages[ - videoAttachments - .indexOf(widget.mediaAttachments[position])]; - - media = InkWell( - onTap: () { - widget.mediaSelectedCallBack(position); - }, - child: FittedBox( - fit: BoxFit.cover, - child: Chewie( - controller: controllerPackage.chewieController, - ), - ), - ); - } else { - media = InkWell( - onTap: () { - widget.mediaSelectedCallBack(position); - }, - child: Padding( - padding: const EdgeInsets.all(1.0), - child: AspectRatio( - child: CachedNetworkImage( - imageUrl: widget - .mediaAttachments[position].imageUrl ?? - widget.mediaAttachments[position].assetUrl ?? - widget.mediaAttachments[position].thumbUrl, - fit: BoxFit.cover, - ), - aspectRatio: 1.0, - ), - ), - ); - } - - return Stack( - children: [ - media, - Padding( - padding: EdgeInsets.all(8.0), - child: Align( - alignment: Alignment.topLeft, - child: UserAvatar( - user: widget.message.user, - constraints: BoxConstraints.tight( - Size( - 24, - 24, - ), - ), - showOnlineStatus: false, - ), - ), - ), - ], - ); - }, - itemCount: widget.mediaAttachments.length, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3), - ), - ), - ], + ); + }, ); }, ); diff --git a/packages/flutter_widgets/lib/src/message_actions_modal.dart b/packages/flutter_widgets/lib/src/message_actions_modal.dart index 9b93695e..c45c351d 100644 --- a/packages/flutter_widgets/lib/src/message_actions_modal.dart +++ b/packages/flutter_widgets/lib/src/message_actions_modal.dart @@ -32,6 +32,7 @@ class MessageActionsModal extends StatefulWidget { final bool showFlagButton; final bool reverse; final ShapeBorder messageShape; + final ShapeBorder attachmentShape; final DisplayWidget showUserAvatar; const MessageActionsModal({ @@ -51,6 +52,7 @@ class MessageActionsModal extends StatefulWidget { this.showUserAvatar = DisplayWidget.show, this.editMessageInputBuilder, this.messageShape, + this.attachmentShape, this.reverse = false, }) : super(key: key); @@ -173,6 +175,7 @@ class _MessageActionsModalState extends State { showInChannelIndicator: false, showSendingIndicator: false, shape: widget.messageShape, + attachmentShape: widget.attachmentShape, ), ), SizedBox(height: 8), @@ -461,7 +464,7 @@ class _MessageActionsModalState extends State { ), const SizedBox(width: 16), Text( - 'Flag', + 'Flag Message', style: StreamChatTheme.of(context).textTheme.body, ), ], diff --git a/packages/flutter_widgets/lib/src/message_reactions_modal.dart b/packages/flutter_widgets/lib/src/message_reactions_modal.dart index c96b3afa..e09e21db 100644 --- a/packages/flutter_widgets/lib/src/message_reactions_modal.dart +++ b/packages/flutter_widgets/lib/src/message_reactions_modal.dart @@ -20,6 +20,7 @@ class MessageReactionsModal extends StatelessWidget { final bool showReactions; final DisplayWidget showUserAvatar; final ShapeBorder messageShape; + final ShapeBorder attachmentShape; final void Function(User) onUserAvatarTap; const MessageReactionsModal({ @@ -30,6 +31,7 @@ class MessageReactionsModal extends StatelessWidget { this.onThreadTap, this.editMessageInputBuilder, this.messageShape, + this.attachmentShape, this.reverse = false, this.showUserAvatar = DisplayWidget.show, this.onUserAvatarTap, @@ -127,6 +129,7 @@ class MessageReactionsModal extends StatelessWidget { translateUserAvatar: false, showSendingIndicator: false, shape: messageShape, + attachmentShape: attachmentShape, padding: const EdgeInsets.all(0), attachmentPadding: EdgeInsets.all( hasFileAttachment ? 4 : 2, diff --git a/packages/flutter_widgets/lib/src/message_widget.dart b/packages/flutter_widgets/lib/src/message_widget.dart index 070bdad5..65817823 100644 --- a/packages/flutter_widgets/lib/src/message_widget.dart +++ b/packages/flutter_widgets/lib/src/message_widget.dart @@ -295,10 +295,6 @@ class _MessageWidgetState extends State { var leftPadding = widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; - final hasFiles = - widget.message.attachments?.any((element) => element.type == 'file') == - true; - return Material( type: MaterialType.transparency, child: Portal( @@ -406,29 +402,19 @@ class _MessageWidgetState extends State { BorderRadius.zero, ), color: _getBackgroundColor(), - child: Padding( - padding: EdgeInsets.all( - hasFiles ? 2.0 : 0.0), - child: Column( - crossAxisAlignment: - CrossAxisAlignment - .end, - mainAxisSize: - MainAxisSize.min, - children: [ - if (hasQuotedMessage) - _buildQuotedMessage(), - if (hasNonUrlAttachments) - _parseAttachments( - context), - if (widget.message.text - .trim() - .isNotEmpty && - !isGiphy) - _buildTextBubble( - context), - ], - ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + mainAxisSize: + MainAxisSize.min, + children: [ + if (hasQuotedMessage) + _buildQuotedMessage(), + if (hasNonUrlAttachments) + _parseAttachments(), + if (!isGiphy) + _buildTextBubble(), + ], ), ), ), @@ -536,7 +522,7 @@ class _MessageWidgetState extends State { var children = []; - final threadParticipants = widget.message.threadParticipants; + final threadParticipants = widget.message?.threadParticipants?.take(2); final showThreadParticipants = threadParticipants?.isNotEmpty == true; final replyCount = widget.message.replyCount; @@ -565,7 +551,7 @@ class _MessageWidgetState extends State { if (showThreadParticipants) SizedBox.fromSize( size: Size((threadParticipants.length * 8.0) + 8, 16), - child: _buildThreadParticipantsIndicator(), + child: _buildThreadParticipantsIndicator(threadParticipants), ), InkWell( onTap: widget.onThreadTap != null ? onThreadTap : null, @@ -642,10 +628,10 @@ class _MessageWidgetState extends State { ); } - Widget _buildThreadParticipantsIndicator() { + Widget _buildThreadParticipantsIndicator(Iterable threadParticipants) { var padding = 0.0; return Stack( - children: widget.message.threadParticipants.map((user) { + children: threadParticipants.map((user) { padding += 8.0; return Positioned( right: padding - 8, @@ -718,6 +704,8 @@ class _MessageWidgetState extends State { : DisplayWidget.show, messageTheme: widget.messageTheme, messageShape: widget.shape ?? _getDefaultShape(context), + attachmentShape: + widget.attachmentShape ?? _getDefaultAttachmentShape(context), reverse: widget.reverse, showDeleteMessage: widget.showDeleteMessage || isDeleteFailed, message: widget.message, @@ -763,6 +751,8 @@ class _MessageWidgetState extends State { onUserAvatarTap: widget.onUserAvatarTap, messageTheme: widget.messageTheme, messageShape: widget.shape ?? _getDefaultShape(context), + attachmentShape: + widget.attachmentShape ?? _getDefaultAttachmentShape(context), reverse: widget.reverse, message: widget.message, editMessageInputBuilder: widget.editMessageInputBuilder, @@ -773,20 +763,31 @@ class _MessageWidgetState extends State { }); } - ShapeBorder _getDefaultShape(BuildContext context) { + ShapeBorder _getDefaultAttachmentShape(BuildContext context) { + final hasFiles = + widget.message.attachments?.any((it) => it.type == 'file') == true; return RoundedRectangleBorder( - side: widget.attachmentBorderSide ?? - widget.borderSide ?? - BorderSide( - color: StreamChatTheme.of(context).colorTheme.greyWhisper, - ), - borderRadius: widget.attachmentBorderRadiusGeometry ?? - widget.borderRadiusGeometry ?? - BorderRadius.zero, + side: hasFiles + ? widget.attachmentBorderSide ?? + BorderSide( + color: StreamChatTheme.of(context).colorTheme.greyWhisper, + ) + : BorderSide.none, + borderRadius: widget.attachmentBorderRadiusGeometry ?? BorderRadius.zero, ); } - Widget _parseAttachments(BuildContext context) { + ShapeBorder _getDefaultShape(BuildContext context) { + return RoundedRectangleBorder( + side: widget.borderSide ?? + BorderSide( + color: StreamChatTheme.of(context).colorTheme.greyWhisper, + ), + borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero, + ); + } + + Widget _parseAttachments() { final images = widget.message.attachments ?.where((element) => element.type == 'image' && element.ogScrapeUrl == null) @@ -849,7 +850,7 @@ class _MessageWidgetState extends State { Attachment attachment, }) { final attachmentShape = - widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context); + widget.attachmentShape ?? _getDefaultAttachmentShape(context); return Material( clipBehavior: Clip.antiAlias, shape: attachmentShape, @@ -920,7 +921,8 @@ class _MessageWidgetState extends State { ), ); - Widget _buildTextBubble(BuildContext context) { + Widget _buildTextBubble() { + if (widget.message.text.trim().isEmpty) return Offstage(); return Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, diff --git a/packages/flutter_widgets/lib/src/stream_chat_theme.dart b/packages/flutter_widgets/lib/src/stream_chat_theme.dart index f9c81be7..8230ba5c 100644 --- a/packages/flutter_widgets/lib/src/stream_chat_theme.dart +++ b/packages/flutter_widgets/lib/src/stream_chat_theme.dart @@ -492,8 +492,8 @@ class ColorTheme { ), this.borderTop = const Effect( sigmaX: 0, sigmaY: -1, color: Color(0xff000000), blur: 0.0), - this.borderBottom = - const Effect(sigmaX: 0, sigmaY: 1, color: Color(0xff000000), blur: 0.0), + this.borderBottom = const Effect( + sigmaX: 0, sigmaY: 1, color: Color(0xff000000), blur: 0.0, alpha: 0.08), this.shadowIconButton = const Effect( sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0), this.modalShadow = const Effect( @@ -514,8 +514,8 @@ class ColorTheme { this.accentGreen = const Color(0xff20E070), this.borderTop = const Effect( sigmaX: 0, sigmaY: -1, color: Color(0xff141924), blur: 0.0), - this.borderBottom = - const Effect(sigmaX: 0, sigmaY: 1, color: Color(0xff141924), blur: 0.0), + this.borderBottom = const Effect( + sigmaX: 0, sigmaY: 1, color: Color(0xff141924), blur: 0.0, alpha: 1.0), this.shadowIconButton = const Effect( sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0), this.modalShadow = const Effect( diff --git a/packages/flutter_widgets/lib/src/user_list_view.dart b/packages/flutter_widgets/lib/src/user_list_view.dart index ef51ad92..6197a06e 100644 --- a/packages/flutter_widgets/lib/src/user_list_view.dart +++ b/packages/flutter_widgets/lib/src/user_list_view.dart @@ -416,6 +416,10 @@ class _UserListViewState extends State height: 64, width: 64, ), + onlineIndicatorConstraints: BoxConstraints.tightFor( + height: 12, + width: 12, + ), onTap: (user) => widget.onUserTap(user, widget.userWidget), onLongPress: widget.onUserLongPress,