diff --git a/lib/src/channel_bottom_sheet.dart b/lib/src/channel_bottom_sheet.dart index 21f85430..d03f233c 100644 --- a/lib/src/channel_bottom_sheet.dart +++ b/lib/src/channel_bottom_sheet.dart @@ -73,6 +73,8 @@ class _ChannelBottomSheetState extends State { maxWidth: 64.0, ), borderRadius: BorderRadius.circular(32.0), + onlineIndicatorConstraints: + BoxConstraints.tight(Size(16.0, 16.0)), ), SizedBox( height: 6.0, diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 6c4e967e..d8e1006b 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -9,6 +9,7 @@ import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/channels_bloc.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; +import 'dart:ui' as ui; import '../stream_chat_flutter.dart'; import 'channel_bottom_sheet.dart'; @@ -157,6 +158,7 @@ class ChannelListView extends StatefulWidget { class _ChannelListViewState extends State with WidgetsBindingObserver { final ScrollController _scrollController = ScrollController(); + final SlidableController _slideController = SlidableController(); @override Widget build(BuildContext context) { @@ -562,6 +564,7 @@ class _ChannelListViewState extends State final backgroundColor = StreamChatTheme.of(context).colorTheme.whiteSmoke; child = Slidable( + controller: _slideController, enabled: widget.swipeToAction, actionPane: SlidableBehindActionPane(), actionExtentRatio: 0.12, @@ -726,9 +729,17 @@ class _ChannelListViewState extends State } Widget _separatorBuilder(context, i) { - return Container( - height: 1, - color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.08), + var effect = StreamChatTheme.of(context).colorTheme.borderTop; + + return BackdropFilter( + filter: ui.ImageFilter.blur( + sigmaX: effect.sigmaX, + sigmaY: effect.sigmaY, + ), + child: Container( + height: 1, + color: effect.color.withOpacity(0.08), + ), ); } diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index fe602b4a..c1536653 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -104,8 +104,7 @@ class ChannelPreview extends StatelessWidget { message: channel.state.lastMessage, size: StreamChatTheme.of(context) .channelPreviewTheme - .lastMessageAt - .fontSize, + .indicatorIconSize, isMessageRead: channel.state.read ?.where((element) => element.user.id != @@ -220,7 +219,9 @@ class ChannelPreview extends StatelessWidget { } else if (e.type == 'giphy') { return '[GIF]'; } - return null; + return e == lastMessage.attachments.last + ? (e.title ?? 'File') + : '${e.title ?? 'File'} , '; }).where((e) => e != null), lastMessage.text ?? '', ]; @@ -228,22 +229,69 @@ class ChannelPreview extends StatelessWidget { text = parts.join(' '); } - return Text( - text, + return Text.rich( + _getDisplayText( + text, + lastMessage.mentionedUsers, + lastMessage.attachments, + StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( + color: StreamChatTheme.of(context) + .channelPreviewTheme + .subtitle + .color, + fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) + ? FontStyle.italic + : FontStyle.normal), + StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( + color: StreamChatTheme.of(context) + .channelPreviewTheme + .subtitle + .color, + fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + fontWeight: FontWeight.bold), + ), maxLines: 1, overflow: TextOverflow.ellipsis, - style: - StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( - color: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - .color, - fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) - ? FontStyle.italic - : FontStyle.normal, - ), ); }, ); } + + TextSpan _getDisplayText( + String text, + List mentions, + List attachments, + TextStyle normalTextStyle, + TextStyle mentionsTextStyle) { + var textList = text.split(' '); + List resList = []; + for (var e in textList) { + if (mentions != null && + mentions.isNotEmpty && + mentions.any((element) => '@${element.name}' == e)) { + resList.add(TextSpan( + text: '$e ', + style: mentionsTextStyle, + )); + } else if (attachments != null && + attachments.isNotEmpty && + attachments + .where((e) => e.title != null) + .any((element) => element.title == e)) { + resList.add(TextSpan( + text: '$e ', + style: normalTextStyle.copyWith(fontStyle: FontStyle.italic), + )); + } else { + resList.add(TextSpan( + text: e == textList.last ? '$e' : '$e ', + style: normalTextStyle, + )); + } + } + + return TextSpan(children: resList); + } } diff --git a/lib/src/message_search_item.dart b/lib/src/message_search_item.dart index 851659e1..9c951a56 100644 --- a/lib/src/message_search_item.dart +++ b/lib/src/message_search_item.dart @@ -113,7 +113,9 @@ class MessageSearchItem extends StatelessWidget { } else if (e.type == 'giphy') { return '[GIF]'; } - return null; + return e == message.attachments.last + ? (e.title ?? 'File') + : '${e.title ?? 'File'} , '; }).where((e) => e != null), message.text ?? '', ]; @@ -121,15 +123,61 @@ class MessageSearchItem extends StatelessWidget { text = parts.join(' '); } - return Text( - text, + return Text.rich( + _getDisplayText( + text, + message.mentionedUsers, + message.attachments, + StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( + fontStyle: (message.isSystem || message.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + ), + StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( + fontStyle: (message.isSystem || message.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + fontWeight: FontWeight.bold, + ), + ), maxLines: 1, overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( - fontStyle: (message.isSystem || message.isDeleted) - ? FontStyle.italic - : FontStyle.normal, - ), ); } + + TextSpan _getDisplayText( + String text, + List mentions, + List attachments, + TextStyle normalTextStyle, + TextStyle mentionsTextStyle) { + var textList = text.split(' '); + List resList = []; + for (var e in textList) { + if (mentions != null && + mentions.isNotEmpty && + mentions.any((element) => '@${element.name}' == e)) { + resList.add(TextSpan( + text: '$e ', + style: mentionsTextStyle, + )); + } else if (attachments != null && + attachments.isNotEmpty && + attachments + .where((e) => e.title != null) + .any((element) => element.title == e)) { + resList.add(TextSpan( + text: '$e ', + style: normalTextStyle.copyWith(fontStyle: FontStyle.italic), + )); + } else { + resList.add(TextSpan( + text: e == textList.last ? '$e' : '$e ', + style: normalTextStyle, + )); + } + } + + return TextSpan(children: resList); + } } diff --git a/lib/src/sending_indicator.dart b/lib/src/sending_indicator.dart index e3a9df06..83e02bb3 100644 --- a/lib/src/sending_indicator.dart +++ b/lib/src/sending_indicator.dart @@ -17,15 +17,13 @@ class SendingIndicator extends StatelessWidget { @override Widget build(BuildContext context) { if (isMessageRead) { - return Icon( - Icons.done_all_rounded, + return StreamSvgIcon.checkAll( size: size, color: StreamChatTheme.of(context).colorTheme.accentBlue, ); } if (message.status == MessageSendingStatus.SENT || message.status == null) { - return Icon( - Icons.done, + return StreamSvgIcon.check( size: size, color: IconTheme.of(context).color.withOpacity(0.5), ); diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index 8640467c..439bd5ef 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -187,22 +187,22 @@ class StreamChatThemeData { ), ), channelPreviewTheme: ChannelPreviewTheme( - unreadCounterColor: colorTheme.accentRed, - avatarTheme: AvatarTheme( - borderRadius: BorderRadius.circular(20), - constraints: BoxConstraints.tightFor( - height: 40, - width: 40, + unreadCounterColor: colorTheme.accentRed, + avatarTheme: AvatarTheme( + borderRadius: BorderRadius.circular(20), + constraints: BoxConstraints.tightFor( + height: 40, + width: 40, + ), ), - ), - title: textTheme.bodyBold, - subtitle: textTheme.footnote.copyWith( - color: Color(0xff7A7A7A), - ), - lastMessageAt: textTheme.footnote.copyWith( - color: colorTheme.black.withOpacity(.5), - ), - ), + title: textTheme.bodyBold, + subtitle: textTheme.footnote.copyWith( + color: Color(0xff7A7A7A), + ), + lastMessageAt: textTheme.footnote.copyWith( + color: colorTheme.black.withOpacity(.5), + ), + indicatorIconSize: 16.0), channelTheme: ChannelTheme( messageInputButtonIconTheme: theme.iconTheme.copyWith( color: accentColor, @@ -478,6 +478,10 @@ class ColorTheme { final Color accentBlue; final Color accentRed; final Color accentGreen; + final Effect borderTop; + final Effect borderBottom; + final Effect shadowIconButton; + final Effect modalShadow; final Color highlight; final Color overlay; final Color overlayDark; @@ -504,6 +508,14 @@ class ColorTheme { colors: [const Color(0xfff7f7f7), const Color(0xfffcfcfc)], stops: [0, 1], ), + 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.shadowIconButton = const Effect( + sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0), + this.modalShadow = const Effect( + sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8.0), }); ColorTheme.dark({ @@ -518,6 +530,14 @@ class ColorTheme { this.accentBlue = const Color(0xff005FFF), this.accentRed = const Color(0xffFF3742), 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.shadowIconButton = const Effect( + sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0), + this.modalShadow = const Effect( + sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8.0), this.highlight = const Color(0xff302d22), this.overlay = const Color.fromRGBO(0, 0, 0, 0.4), this.overlayDark = const Color.fromRGBO(255, 255, 255, 0.6), @@ -542,6 +562,10 @@ class ColorTheme { Color accentBlue, Color accentRed, Color accentGreen, + Effect borderTop, + Effect borderBottom, + Effect shadowIconButton, + Effect modalShadow, Color highlight, Color overlay, Color overlayDark, @@ -560,6 +584,10 @@ class ColorTheme { accentBlue: accentBlue ?? this.accentBlue, accentRed: accentRed ?? this.accentRed, accentGreen: accentGreen ?? this.accentGreen, + borderTop: borderTop ?? this.borderTop, + borderBottom: borderBottom ?? this.borderBottom, + shadowIconButton: shadowIconButton ?? this.shadowIconButton, + modalShadow: modalShadow ?? this.modalShadow, highlight: highlight ?? this.highlight, overlay: overlay ?? this.overlay, overlayDark: overlayDark ?? this.overlayDark, @@ -577,6 +605,10 @@ class ColorTheme { accentBlue: accentBlue ?? this.accentBlue, accentRed: accentRed ?? this.accentRed, accentGreen: accentGreen ?? this.accentGreen, + borderTop: borderTop ?? this.borderTop, + borderBottom: borderBottom ?? this.borderBottom, + shadowIconButton: shadowIconButton ?? this.shadowIconButton, + modalShadow: modalShadow ?? this.modalShadow, highlight: highlight ?? this.highlight, overlay: overlay ?? this.overlay, overlayDark: overlayDark ?? this.overlayDark, @@ -602,6 +634,10 @@ class ColorTheme { overlay: other.overlay, overlayDark: other.overlayDark, bgGradient: other.bgGradient, + borderTop: other.borderTop, + borderBottom: other.borderBottom, + shadowIconButton: other.shadowIconButton, + modalShadow: other.modalShadow, ); } } @@ -761,6 +797,7 @@ class ChannelPreviewTheme { final TextStyle lastMessageAt; final AvatarTheme avatarTheme; final Color unreadCounterColor; + final double indicatorIconSize; const ChannelPreviewTheme({ this.title, @@ -768,6 +805,7 @@ class ChannelPreviewTheme { this.lastMessageAt, this.avatarTheme, this.unreadCounterColor, + this.indicatorIconSize, }); ChannelPreviewTheme copyWith({ @@ -776,6 +814,7 @@ class ChannelPreviewTheme { TextStyle lastMessageAt, AvatarTheme avatarTheme, Color unreadCounterColor, + double indicatorIconSize, }) => ChannelPreviewTheme( title: title ?? this.title, @@ -783,6 +822,7 @@ class ChannelPreviewTheme { lastMessageAt: lastMessageAt ?? this.lastMessageAt, avatarTheme: avatarTheme ?? this.avatarTheme, unreadCounterColor: unreadCounterColor ?? this.unreadCounterColor, + indicatorIconSize: indicatorIconSize ?? this.indicatorIconSize, ); ChannelPreviewTheme merge(ChannelPreviewTheme other) { @@ -835,3 +875,34 @@ class ChannelHeaderTheme { ); } } + +class Effect { + final double sigmaX; + final double sigmaY; + final Color color; + final double alpha; + final double blur; + + const Effect({ + this.sigmaX, + this.sigmaY, + this.color, + this.alpha, + this.blur, + }); + + Effect copyWith({ + double sigmaX, + double sigmaY, + Color color, + double alpha, + double blur, + }) => + Effect( + sigmaX: sigmaX ?? this.sigmaX, + sigmaY: sigmaY ?? this.sigmaY, + color: color ?? this.color, + alpha: color ?? this.alpha, + blur: blur ?? this.blur, + ); +} diff --git a/lib/src/stream_svg_icon.dart b/lib/src/stream_svg_icon.dart index edf1a697..ea5a53a5 100644 --- a/lib/src/stream_svg_icon.dart +++ b/lib/src/stream_svg_icon.dart @@ -194,6 +194,18 @@ class StreamSvgIcon extends StatelessWidget { ); } + factory StreamSvgIcon.checkAll({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_check_all.svg', + color: color, + width: size, + height: size, + ); + } + factory StreamSvgIcon.checkSend({ double size, Color color,