From 435a88cd1b44b3947f722d8e88e2eb8540fbcfa1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 10 May 2021 16:01:20 +0200 Subject: [PATCH] migrate missing files --- .../lib/src/date_divider.dart | 9 +- .../lib/src/deleted_message.dart | 67 +- .../lib/src/group_image.dart | 15 +- .../lib/src/image_footer.dart | 30 +- .../lib/src/image_header.dart | 99 +- .../lib/src/info_tile.dart | 43 +- .../lib/src/media_list_view.dart | 16 +- .../lib/src/mention_tile.dart | 122 ++- .../lib/src/message_actions_modal.dart | 255 ++--- .../lib/src/message_input.dart | 869 +++++++++--------- .../lib/src/message_list_view.dart | 165 ++-- .../lib/src/message_reactions_modal.dart | 12 +- .../lib/src/message_search_item.dart | 37 +- .../lib/src/message_search_list_view.dart | 7 +- .../lib/src/message_text.dart | 19 +- .../lib/src/message_widget.dart | 120 +-- .../lib/src/option_list_tile.dart | 93 +- .../lib/src/reaction_bubble.dart | 22 +- .../lib/src/reaction_picker.dart | 12 +- .../lib/src/thread_header.dart | 107 +-- .../lib/src/url_attachment.dart | 151 ++- .../lib/src/user_avatar.dart | 5 +- .../lib/src/user_item.dart | 83 +- .../lib/src/user_list_view.dart | 29 +- .../stream_chat_flutter/lib/src/utils.dart | 241 +++-- 25 files changed, 1292 insertions(+), 1336 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/date_divider.dart b/packages/stream_chat_flutter/lib/src/date_divider.dart index f409872b..185683be 100644 --- a/packages/stream_chat_flutter/lib/src/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/date_divider.dart @@ -44,18 +44,19 @@ class DateDivider extends StatelessWidget { if (uppercase) dayInfo = dayInfo.toUpperCase(); + final chatThemeData = StreamChatTheme.of(context); return Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1), decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.overlayDark, + color: chatThemeData.colorTheme.overlayDark, borderRadius: BorderRadius.circular(8), ), child: Text( dayInfo, - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: StreamChatTheme.of(context).colorTheme.white, - ), + style: chatThemeData.textTheme.footnote.copyWith( + color: chatThemeData.colorTheme.white, + ), ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart index 96f5606a..93e70b12 100644 --- a/packages/stream_chat_flutter/lib/src/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart @@ -31,44 +31,41 @@ class DeletedMessage extends StatelessWidget { final bool reverse; @override - Widget build(BuildContext context) => Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: Material( - color: messageTheme.messageBackgroundColor, - shape: shape ?? - RoundedRectangleBorder( - borderRadius: borderRadiusGeometry ?? BorderRadius.zero, - side: borderSide ?? - BorderSide( - color: Theme.of(context).brightness == Brightness.dark - ? StreamChatTheme.of(context) - .colorTheme - .white - .withAlpha(24) - : StreamChatTheme.of(context) - .colorTheme - .black - .withAlpha(24), - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Material( + color: messageTheme.messageBackgroundColor, + shape: shape ?? + RoundedRectangleBorder( + borderRadius: borderRadiusGeometry ?? BorderRadius.zero, + side: borderSide ?? + BorderSide( + color: Theme.of(context).brightness == Brightness.dark + ? chatThemeData.colorTheme.white.withAlpha(24) + : chatThemeData.colorTheme.black.withAlpha(24), + ), ), - child: Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: Text( - 'Message deleted', - style: messageTheme.messageText?.copyWith( - fontStyle: FontStyle.italic, - color: messageTheme.createdAt?.color, - ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + child: Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Text( + 'Message deleted', + style: messageTheme.messageText?.copyWith( + fontStyle: FontStyle.italic, + color: messageTheme.createdAt?.color, ), ), ), ), - ); + ), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index e5c6ecd2..c6778a91 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -46,18 +46,12 @@ class GroupImage extends StatelessWidget { onTap: onTap, child: ClipRRect( borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - ?.borderRadius, + streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius, child: Container( constraints: constraints ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - ?.constraints, + streamChatTheme.ownMessageTheme.avatarTheme?.constraints, decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: streamChatTheme.colorTheme.accentBlue, ), child: Flex( direction: Axis.vertical, @@ -125,8 +119,7 @@ class GroupImage extends StatelessWidget { BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( - color: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, + color: selectionColor ?? streamChatTheme.colorTheme.accentBlue, height: 64, width: 64, child: Padding( diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/image_footer.dart index 9298e73b..cc79192b 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/image_footer.dart @@ -77,16 +77,18 @@ class _ImageFooterState extends State { @override Widget build(BuildContext context) { final showShareButton = !kIsWeb; + final mediaQueryData = MediaQuery.of(context); + final chatThemeData = StreamChatTheme.of(context); return SizedBox.fromSize( size: Size( - MediaQuery.of(context).size.width, - MediaQuery.of(context).padding.bottom + widget.preferredSize.height, + mediaQueryData.size.width, + mediaQueryData.padding.bottom + widget.preferredSize.height, ), child: MediaQuery.removePadding( context: context, removeTop: true, child: BottomAppBar( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -98,7 +100,7 @@ class _ImageFooterState extends State { IconButton( icon: StreamSvgIcon.iconShare( size: 24, - color: StreamChatTheme.of(context).colorTheme.black, + color: chatThemeData.colorTheme.black, ), onPressed: () async { final attachment = @@ -134,8 +136,7 @@ class _ImageFooterState extends State { children: [ Text( '${widget.currentPage + 1} of ${widget.totalPages}', - style: - StreamChatTheme.of(context).textTheme.headlineBold, + style: chatThemeData.textTheme.headlineBold, ), ], ), @@ -143,7 +144,7 @@ class _ImageFooterState extends State { ), IconButton( icon: StreamSvgIcon.iconGrid( - color: StreamChatTheme.of(context).colorTheme.black, + color: chatThemeData.colorTheme.black, ), onPressed: () => _showPhotosModal(context), ), @@ -155,10 +156,11 @@ class _ImageFooterState extends State { } void _showPhotosModal(context) { + final chatThemeData = StreamChatTheme.of(context); showModalBottomSheet( context: context, - barrierColor: StreamChatTheme.of(context).colorTheme.overlay, - backgroundColor: StreamChatTheme.of(context).colorTheme.white, + barrierColor: chatThemeData.colorTheme.overlay, + backgroundColor: chatThemeData.colorTheme.white, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -189,9 +191,7 @@ class _ImageFooterState extends State { padding: const EdgeInsets.all(16), child: Text( 'Photos', - style: StreamChatTheme.of(context) - .textTheme - .headlineBold, + style: chatThemeData.textTheme.headlineBold, ), ), ), @@ -199,7 +199,7 @@ class _ImageFooterState extends State { alignment: Alignment.centerRight, child: IconButton( icon: StreamSvgIcon.close( - color: StreamChatTheme.of(context).colorTheme.black, + color: chatThemeData.colorTheme.black, ), onPressed: () => Navigator.maybePop(context), ), @@ -262,9 +262,7 @@ class _ImageFooterState extends State { boxShadow: [ BoxShadow( blurRadius: 8, - color: StreamChatTheme.of(context) - .colorTheme - .black + color: chatThemeData.colorTheme.black .withOpacity(0.3), ), ], diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/image_header.dart index 0b876aa1..095d7534 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/image_header.dart @@ -50,59 +50,58 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { final int currentIndex; @override - Widget build(BuildContext context) => AppBar( - brightness: Theme.of(context).brightness, - elevation: 1, - leading: showBackButton - ? IconButton( - icon: StreamSvgIcon.close( - color: StreamChatTheme.of(context).colorTheme.black, - size: 24, - ), - onPressed: onBackPressed, - ) - : const SizedBox(), - backgroundColor: - StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, - actions: [ - if (message.type != 'ephemeral') - IconButton( - icon: StreamSvgIcon.iconMenuPoint( - color: StreamChatTheme.of(context).colorTheme.black, + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return AppBar( + brightness: Theme.of(context).brightness, + elevation: 1, + leading: showBackButton + ? IconButton( + icon: StreamSvgIcon.close( + color: chatThemeData.colorTheme.black, + size: 24, ), - onPressed: () { - _showMessageActionModalBottomSheet(context); - }, + onPressed: onBackPressed, + ) + : const SizedBox(), + backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color, + actions: [ + if (message.type != 'ephemeral') + IconButton( + icon: StreamSvgIcon.iconMenuPoint( + color: chatThemeData.colorTheme.black, ), - ], - centerTitle: true, - title: message.type != 'ephemeral' - ? InkWell( - onTap: onTitleTap, - child: SizedBox( - height: preferredSize.height, - width: preferredSize.width, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - userName, - style: - StreamChatTheme.of(context).textTheme.headlineBold, - ), - Text( - sentAt, - style: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle, - ), - ], - ), + onPressed: () { + _showMessageActionModalBottomSheet(context); + }, + ), + ], + centerTitle: true, + title: message.type != 'ephemeral' + ? InkWell( + onTap: onTitleTap, + child: SizedBox( + height: preferredSize.height, + width: preferredSize.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + userName, + style: chatThemeData.textTheme.headlineBold, + ), + Text( + sentAt, + style: chatThemeData.channelPreviewTheme.subtitle, + ), + ], ), - ) - : const SizedBox(), - ); + ), + ) + : const SizedBox(), + ); + } @override final Size preferredSize; diff --git a/packages/stream_chat_flutter/lib/src/info_tile.dart b/packages/stream_chat_flutter/lib/src/info_tile.dart index 81a701c0..1e935942 100644 --- a/packages/stream_chat_flutter/lib/src/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/info_tile.dart @@ -38,26 +38,29 @@ class InfoTile extends StatelessWidget { final Color? backgroundColor; @override - Widget build(BuildContext context) => PortalEntry( - visible: showMessage, - portalAnchor: tileAnchor ?? Alignment.topCenter, - childAnchor: childAnchor ?? Alignment.bottomCenter, - portal: Container( - height: 25, - color: backgroundColor ?? - StreamChatTheme.of(context).colorTheme.grey.withOpacity(0.9), - child: Center( - child: Text( - message, - style: textStyle ?? - StreamChatTheme.of(context).textTheme.body.copyWith( - color: Colors.white, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return PortalEntry( + visible: showMessage, + portalAnchor: tileAnchor ?? Alignment.topCenter, + childAnchor: childAnchor ?? Alignment.bottomCenter, + portal: Container( + height: 25, + color: + backgroundColor ?? chatThemeData.colorTheme.grey.withOpacity(0.9), + child: Center( + child: Text( + message, + style: textStyle ?? + chatThemeData.textTheme.body.copyWith( + color: Colors.white, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), ), - child: child, - ); + ), + child: child, + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/media_list_view.dart b/packages/stream_chat_flutter/lib/src/media_list_view.dart index e0d0c27e..fe0070f9 100644 --- a/packages/stream_chat_flutter/lib/src/media_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/media_list_view.dart @@ -56,6 +56,7 @@ class _MediaListViewState extends State { position, ) { final media = _media.elementAt(position); + final chatThemeData = StreamChatTheme.of(context); return Padding( padding: const EdgeInsets.symmetric(horizontal: 1, vertical: 1), child: InkWell( @@ -89,10 +90,8 @@ class _MediaListViewState extends State { ? 1.0 : 0.0, child: Container( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), + color: + chatThemeData.colorTheme.black.withOpacity(0.5), alignment: Alignment.topRight, padding: const EdgeInsets.only( top: 8, @@ -100,13 +99,10 @@ class _MediaListViewState extends State { ), child: CircleAvatar( radius: 12, - backgroundColor: - StreamChatTheme.of(context).colorTheme.white, + backgroundColor: chatThemeData.colorTheme.white, child: StreamSvgIcon.check( size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .black, + color: chatThemeData.colorTheme.black, ), ), ), @@ -128,7 +124,7 @@ class _MediaListViewState extends State { child: Text( media.videoDuration.format(), style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/mention_tile.dart b/packages/stream_chat_flutter/lib/src/mention_tile.dart index f3bed355..99b4b962 100644 --- a/packages/stream_chat_flutter/lib/src/mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/mention_tile.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; - import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// This widget is used for showing user tiles for mentions @@ -32,71 +31,70 @@ class MentionTile extends StatelessWidget { final Widget? trailing; @override - Widget build(BuildContext context) => SizedBox( - height: 56, - child: Row( - children: [ - const SizedBox( - width: 16, - ), - leading ?? - UserAvatar( - constraints: BoxConstraints.tight( - const Size( - 40, - 40, - ), + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return SizedBox( + height: 56, + child: Row( + children: [ + const SizedBox( + width: 16, + ), + leading ?? + UserAvatar( + constraints: BoxConstraints.tight( + const Size( + 40, + 40, ), - user: member.user!, ), - const SizedBox( - width: 8, - ), - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - title ?? - Text( - member.user!.name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context).textTheme.bodyBold, + user: member.user!, + ), + const SizedBox( + width: 8, + ), + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title ?? + Text( + member.user!.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: chatThemeData.textTheme.bodyBold, + ), + const SizedBox( + height: 2, + ), + subtitle ?? + Text( + '@${member.userId}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: chatThemeData.textTheme.footnoteBold.copyWith( + color: chatThemeData.colorTheme.grey, ), - const SizedBox( - height: 2, - ), - subtitle ?? - Text( - '@${member.userId}', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context) - .textTheme - .footnoteBold - .copyWith( - color: - StreamChatTheme.of(context).colorTheme.grey, - ), - ), - ], - ), + ), + ], ), ), - trailing ?? - Padding( - padding: const EdgeInsets.only( - right: 18, - left: 8, - ), - child: StreamSvgIcon.mentions( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ), + ), + trailing ?? + Padding( + padding: const EdgeInsets.only( + right: 18, + left: 8, ), - ], - ), - ); + child: StreamSvgIcon.mentions( + color: chatThemeData.colorTheme.accentBlue, + ), + ), + ], + ), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart index 767cdc76..82ce94e2 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -3,12 +3,12 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/message_action.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Constructs a modal with actions for a message class MessageActionsModal extends StatefulWidget { @@ -108,7 +108,8 @@ class _MessageActionsModalState extends State { Widget build(BuildContext context) => _showMessageOptionsModal(); Widget _showMessageOptionsModal() { - final size = MediaQuery.of(context).size; + final mediaQueryData = MediaQuery.of(context); + final size = mediaQueryData.size; final user = StreamChat.of(context).user; final roughMaxSize = 2 * size.width / 3; @@ -133,6 +134,7 @@ class _MessageActionsModalState extends State { final hasFileAttachment = widget.message.attachments.any((it) => it.type == 'file') == true; + final streamChatThemeData = StreamChatTheme.of(context); return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => Navigator.maybePop(context), @@ -145,7 +147,7 @@ class _MessageActionsModalState extends State { sigmaY: 10, ), child: Container( - color: StreamChatTheme.of(context).colorTheme.overlay, + color: streamChatThemeData.colorTheme.overlay, ), ), ), @@ -226,11 +228,9 @@ class _MessageActionsModalState extends State { left: widget.reverse ? 0 : 40, ), child: SizedBox( - width: MediaQuery.of(context).size.width * 0.75, + width: mediaQueryData.size.width * 0.75, child: Material( - color: StreamChatTheme.of(context) - .colorTheme - .whiteSnow, + color: streamChatThemeData.colorTheme.whiteSnow, clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -266,9 +266,8 @@ class _MessageActionsModalState extends State { ].insertBetween( Container( height: 1, - color: StreamChatTheme.of(context) - .colorTheme - .greyWhisper, + color: streamChatThemeData + .colorTheme.greyWhisper, ), ), ), @@ -310,11 +309,12 @@ class _MessageActionsModalState extends State { void _showFlagDialog() async { final client = StreamChat.of(context).client; + final streamChatThemeData = StreamChatTheme.of(context); final answer = await showConfirmationDialog( context, title: 'Flag Message', icon: StreamSvgIcon.flag( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: streamChatThemeData.colorTheme.accentRed, size: 24, ), question: @@ -324,7 +324,7 @@ class _MessageActionsModalState extends State { cancelText: 'CANCEL', ); - final theme = StreamChatTheme.of(context); + final theme = streamChatThemeData; if (answer == true) { try { await client.flagMessage(widget.message.id); @@ -400,48 +400,54 @@ class _MessageActionsModalState extends State { ); } - Widget _buildReplyButton(BuildContext context) => InkWell( - onTap: () { - Navigator.pop(context); - if (widget.onReplyTap != null) { - widget.onReplyTap!(widget.message); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.reply( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Reply', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildReplyButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: () { + Navigator.pop(context); + if (widget.onReplyTap != null) { + widget.onReplyTap!(widget.message); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.reply( + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Reply', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } - Widget _buildFlagButton(BuildContext context) => InkWell( - onTap: _showFlagDialog, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.iconFlag( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Flag Message', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildFlagButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: _showFlagDialog, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.iconFlag( + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Flag Message', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } Widget _buildDeleteButton(BuildContext context) { final isDeleteFailed = @@ -469,54 +475,61 @@ class _MessageActionsModalState extends State { ); } - Widget _buildCopyButton(BuildContext context) => InkWell( - onTap: () async { - widget.onCopyTap?.call(widget.message); - Navigator.pop(context); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - 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 _buildCopyButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: () async { + widget.onCopyTap?.call(widget.message); + Navigator.pop(context); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.copy( + size: 24, + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Copy Message', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } - Widget _buildEditMessage(BuildContext context) => InkWell( - onTap: () async { - Navigator.pop(context); - _showEditBottomSheet(context); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - 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 _buildEditMessage(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: () async { + Navigator.pop(context); + _showEditBottomSheet(context); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.edit( + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Edit Message', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } Widget _buildResendMessage(BuildContext context) { final isUpdateFailed = widget.message.status == MessageSendingStatus.failed_update; + final streamChatThemeData = StreamChatTheme.of(context); return InkWell( onTap: () { Navigator.pop(context); @@ -532,12 +545,12 @@ class _MessageActionsModalState extends State { child: Row( children: [ StreamSvgIcon.circleUp( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: streamChatThemeData.colorTheme.accentBlue, ), const SizedBox(width: 16), Text( isUpdateFailed ? 'Resend Edited Message' : 'Resend', - style: StreamChatTheme.of(context).textTheme.body, + style: streamChatThemeData.textTheme.body, ), ], ), @@ -547,13 +560,13 @@ class _MessageActionsModalState extends State { void _showEditBottomSheet(BuildContext context) { final channel = StreamChannel.of(context).channel; + final streamChatThemeData = StreamChatTheme.of(context); showModalBottomSheet( context: context, elevation: 2, clipBehavior: Clip.hardEdge, isScrollControlled: true, - backgroundColor: - StreamChatTheme.of(context).messageInputTheme.inputBackground, + backgroundColor: streamChatThemeData.messageInputTheme.inputBackground, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16), @@ -575,8 +588,7 @@ class _MessageActionsModalState extends State { Padding( padding: const EdgeInsets.all(8), child: StreamSvgIcon.edit( - color: - StreamChatTheme.of(context).colorTheme.greyGainsboro, + color: streamChatThemeData.colorTheme.greyGainsboro, ), ), const Text( @@ -608,27 +620,30 @@ class _MessageActionsModalState extends State { ); } - Widget _buildThreadReplyButton(BuildContext context) => InkWell( - onTap: () { - Navigator.pop(context); - if (widget.onThreadReplyTap != null) { - widget.onThreadReplyTap!(widget.message); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.thread( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Thread Reply', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildThreadReplyButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: () { + Navigator.pop(context); + if (widget.onThreadReplyTap != null) { + widget.onThreadReplyTap!(widget.message); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.thread( + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Thread Reply', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 44b54c24..a5ccdb6f 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -12,6 +12,7 @@ import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; @@ -19,11 +20,10 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:substring_highlight/substring_highlight.dart'; import 'package:video_compress/video_compress.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Builder for attachment thumbnails typedef AttachmentThumbnailBuilder = Widget Function( @@ -299,8 +299,9 @@ class MessageInputState extends State { @override Widget build(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); Widget child = Container( - color: StreamChatTheme.of(context).messageInputTheme.inputBackground, + color: streamChatThemeData.messageInputTheme.inputBackground, child: SafeArea( child: GestureDetector( onPanUpdate: (details) { @@ -325,9 +326,7 @@ class MessageInputState extends State { Padding( padding: const EdgeInsets.all(8), child: StreamSvgIcon.reply( - color: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, + color: streamChatThemeData.colorTheme.greyGainsboro, ), ), const Text( @@ -385,68 +384,65 @@ class MessageInputState extends State { ], ); - Widget _buildDmCheckbox() => Row( - children: [ - Container( - height: 16, - width: 16, - foregroundDecoration: BoxDecoration( - border: _sendAsDm - ? null - : Border.all( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - width: 2, - ), + Widget _buildDmCheckbox() { + final streamChatThemeData = StreamChatTheme.of(context); + return Row( + children: [ + Container( + height: 16, + width: 16, + foregroundDecoration: BoxDecoration( + border: _sendAsDm + ? null + : Border.all( + color: streamChatThemeData.colorTheme.black.withOpacity(.5), + width: 2, + ), + borderRadius: BorderRadius.circular(3), + ), + child: Center( + child: Material( borderRadius: BorderRadius.circular(3), - ), - child: Center( - child: Material( - borderRadius: BorderRadius.circular(3), - color: _sendAsDm - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context).colorTheme.white, - child: InkWell( - onTap: () { - setState(() { - _sendAsDm = !_sendAsDm; - }); - }, - child: AnimatedCrossFade( - duration: const Duration(milliseconds: 300), - reverseDuration: const Duration(milliseconds: 300), - crossFadeState: _sendAsDm - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: StreamSvgIcon.check( - size: 16, - color: StreamChatTheme.of(context).colorTheme.white, - ), - secondChild: const SizedBox( - height: 16, - width: 16, - ), + color: _sendAsDm + ? streamChatThemeData.colorTheme.accentBlue + : streamChatThemeData.colorTheme.white, + child: InkWell( + onTap: () { + setState(() { + _sendAsDm = !_sendAsDm; + }); + }, + child: AnimatedCrossFade( + duration: const Duration(milliseconds: 300), + reverseDuration: const Duration(milliseconds: 300), + crossFadeState: _sendAsDm + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: StreamSvgIcon.check( + size: 16, + color: streamChatThemeData.colorTheme.white, + ), + secondChild: const SizedBox( + height: 16, + width: 16, ), ), ), ), ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Text( - 'Also send as direct message', - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), - ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + 'Also send as direct message', + style: streamChatThemeData.textTheme.footnote.copyWith( + color: streamChatThemeData.colorTheme.black.withOpacity(0.5), ), ), - ], - ); + ), + ], + ); + } Widget _animateSendButton(BuildContext context) { final sendButton = widget.activeSendButton != null @@ -636,12 +632,9 @@ class MessageInputState extends State { ), Text( _chosenCommand?.name.toUpperCase() ?? '', - style: StreamChatTheme.of(context) - .textTheme - .footnoteBold - .copyWith( - color: Colors.white, - ), + style: theme.textTheme.footnoteBold.copyWith( + color: Colors.white, + ), ), ], ), @@ -839,116 +832,110 @@ class MessageInputState extends State { tween: Tween(begin: 0, end: 1), duration: const Duration(milliseconds: 300), curve: Curves.easeInOutExpo, - builder: (context, val, wid) => Transform.scale( - scale: val, - child: Padding( - padding: const EdgeInsets.all(8), - child: Card( - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - color: StreamChatTheme.of(context).colorTheme.white, - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose( - const Size.fromHeight(400)), - decoration: BoxDecoration( - color: StreamChatTheme.of(context) - .colorTheme - .white, - borderRadius: BorderRadius.circular(8)), - child: ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - if (commands.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - ), - child: StreamSvgIcon.lightning( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), + builder: (context, val, wid) { + final streamChatThemeData = StreamChatTheme.of(context); + return Transform.scale( + scale: val, + child: Padding( + padding: const EdgeInsets.all(8), + child: Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + color: streamChatThemeData.colorTheme.white, + clipBehavior: Clip.antiAlias, + child: Container( + constraints: BoxConstraints.loose( + const Size.fromHeight(400)), + decoration: BoxDecoration( + color: streamChatThemeData.colorTheme.white, + borderRadius: BorderRadius.circular(8)), + child: ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + children: [ + if (commands.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, ), - Text( - 'Instant Commands', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - ) - ], - ), - ), - const SizedBox( - height: 10, - ), - ...commands - .map( - (c) => InkWell( - onTap: () { - _setCommand(c); - }, - child: SizedBox( - height: 40, - child: Row( - children: [ - const SizedBox( - width: 16, - ), - _buildCommandIcon(c.name), - const SizedBox( - width: 8, - ), - Text.rich( - TextSpan( - text: c.name.capitalize(), - style: const TextStyle( - fontWeight: - FontWeight.bold), - children: [ - TextSpan( - text: - ' /${c.name} ${c.args}', - style: - StreamChatTheme.of( - context) - .textTheme - .body - .copyWith( - // ignore: lines_longer_than_80_chars - color: StreamChatTheme.of( - // ignore: lines_longer_than_80_chars - context) - // ignore: lines_longer_than_80_chars - .colorTheme - .grey, - ), - ), - ], - ), - ), - ], - ), + child: StreamSvgIcon.lightning( + color: streamChatThemeData + .colorTheme.accentBlue, ), ), - ) - .toList(), - ], - ), + Text( + 'Instant Commands', + style: TextStyle( + color: streamChatThemeData + .colorTheme.black + .withOpacity(.5), + ), + ) + ], + ), + ), + const SizedBox( + height: 10, + ), + ...commands + .map( + (c) => InkWell( + onTap: () { + _setCommand(c); + }, + child: SizedBox( + height: 40, + child: Row( + children: [ + const SizedBox( + width: 16, + ), + _buildCommandIcon(c.name), + const SizedBox( + width: 8, + ), + Text.rich( + TextSpan( + text: c.name.capitalize(), + style: const TextStyle( + fontWeight: + FontWeight.bold), + children: [ + TextSpan( + text: + ' /${c.name} ${c.args}', + style: streamChatThemeData + .textTheme.body + .copyWith( + // ignore: lines_longer_than_80_chars + color: streamChatThemeData + // ignore: lines_longer_than_80_chars + .colorTheme + .grey, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ) + .toList(), + ], ), ), ), - )), + ), + ); + }), )); } @@ -956,37 +943,30 @@ class MessageInputState extends State { final _attachmentContainsFile = _attachments.values.any((it) => it.type == 'file'); + final chatThemeData = StreamChatTheme.of(context); Color _getIconColor(int index) { + final streamChatThemeData = chatThemeData; switch (index) { case 0: return _attachments.isEmpty - ? StreamChatTheme.of(context).colorTheme.accentBlue + ? streamChatThemeData.colorTheme.accentBlue : (!_attachmentContainsFile - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.2)); + ? streamChatThemeData.colorTheme.accentBlue + : streamChatThemeData.colorTheme.black.withOpacity(0.2)); case 1: return _attachmentContainsFile - ? StreamChatTheme.of(context).colorTheme.accentBlue + ? streamChatThemeData.colorTheme.accentBlue : (_attachments.isEmpty - ? StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5) - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.2)); + ? streamChatThemeData.colorTheme.black.withOpacity(0.5) + : streamChatThemeData.colorTheme.black.withOpacity(0.2)); case 2: return _attachmentContainsFile && _attachments.isNotEmpty - ? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) - : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); + ? streamChatThemeData.colorTheme.black.withOpacity(0.2) + : streamChatThemeData.colorTheme.black.withOpacity(0.5); case 3: return _attachmentContainsFile && _attachments.isNotEmpty - ? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) - : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); + ? streamChatThemeData.colorTheme.black.withOpacity(0.2) + : streamChatThemeData.colorTheme.black.withOpacity(0.5); default: return Colors.black; } @@ -997,7 +977,7 @@ class MessageInputState extends State { _animateContainer ? const Duration(milliseconds: 300) : Duration.zero, height: _openFilePickerSection ? _filePickerSize : 0, child: Material( - color: StreamChatTheme.of(context).colorTheme.whiteSmoke, + color: chatThemeData.colorTheme.whiteSmoke, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -1061,7 +1041,7 @@ class MessageInputState extends State { }, child: Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), @@ -1076,8 +1056,7 @@ class MessageInputState extends State { width: 40, height: 4, decoration: BoxDecoration( - color: - StreamChatTheme.of(context).colorTheme.whiteSmoke, + color: chatThemeData.colorTheme.whiteSmoke, borderRadius: BorderRadius.circular(4), ), ), @@ -1090,7 +1069,7 @@ class MessageInputState extends State { Expanded( child: Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, borderRadius: BorderRadius.circular(8), ), child: _PickerWidget( @@ -1166,6 +1145,7 @@ class MessageInputState extends State { } Widget _buildCommandIcon(String iconType) { + final chatThemeData = StreamChatTheme.of(context); switch (iconType) { case 'giphy': return CircleAvatar( @@ -1176,7 +1156,7 @@ class MessageInputState extends State { ); case 'ban': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.iconUserDelete( size: 16, @@ -1185,7 +1165,7 @@ class MessageInputState extends State { ); case 'flag': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.flag( size: 14, @@ -1194,7 +1174,7 @@ class MessageInputState extends State { ); case 'imgur': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: ClipOval( child: StreamSvgIcon.imgur( @@ -1204,7 +1184,7 @@ class MessageInputState extends State { ); case 'mute': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.mute( size: 16, @@ -1213,7 +1193,7 @@ class MessageInputState extends State { ); case 'unban': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.userAdd( size: 16, @@ -1222,7 +1202,7 @@ class MessageInputState extends State { ); case 'unmute': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.volumeUp( size: 16, @@ -1231,7 +1211,7 @@ class MessageInputState extends State { ); default: return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.lightning( size: 16, @@ -1249,17 +1229,14 @@ class MessageInputState extends State { Future>? queryMembers; + final channelState = StreamChannel.of(context); if (query.isNotEmpty) { - queryMembers = StreamChannel.of(context) - .channel + queryMembers = channelState.channel .queryMembers(filter: Filter.autoComplete('name', query)) .then((res) => res.members); } - final members = StreamChannel.of(context) - .channel - .state - ?.members + final members = channelState.channel.state?.members .where((m) => m.user?.name.toLowerCase().contains(query) == true) .toList() ?? []; @@ -1281,76 +1258,78 @@ class MessageInputState extends State { tween: Tween(begin: 0, end: 1), duration: const Duration(milliseconds: 300), curve: Curves.easeInOutExpo, - builder: (context, val, wid) => Transform.scale( - scale: val, - child: Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: StreamChatTheme.of(context).colorTheme.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose(const Size.fromHeight(240)), - decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + builder: (context, val, wid) { + final chatThemeData = StreamChatTheme.of(context); + return Transform.scale( + scale: val, + child: Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: chatThemeData.colorTheme.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), ), - child: FutureBuilder>( - future: queryMembers ?? Future.value(members), - initialData: members, - builder: (context, snapshot) => ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - const SizedBox( - height: 8, - ), - ...snapshot.data! - .where((it) => it.user != null) - .map( - (m) => Material( - color: - StreamChatTheme.of(context).colorTheme.white, - child: InkWell( - onTap: () { - if (m.user != null) { - _mentionedUsers.add(m.user!); - } + clipBehavior: Clip.antiAlias, + child: Container( + constraints: BoxConstraints.loose(const Size.fromHeight(240)), + decoration: BoxDecoration( + color: chatThemeData.colorTheme.white, + ), + child: FutureBuilder>( + future: queryMembers ?? Future.value(members), + initialData: members, + builder: (context, snapshot) => ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + children: [ + const SizedBox( + height: 8, + ), + ...snapshot.data! + .where((it) => it.user != null) + .map( + (m) => Material( + color: chatThemeData.colorTheme.white, + child: InkWell( + onTap: () { + if (m.user != null) { + _mentionedUsers.add(m.user!); + } - splits[splits.length - 1] = m.user!.name; - final rejoin = splits.join('@'); + splits[splits.length - 1] = m.user!.name; + final rejoin = splits.join('@'); - textEditingController.value = - TextEditingValue( - text: rejoin + - textEditingController.text.substring( - textEditingController - .selection.start), - selection: TextSelection.collapsed( - offset: rejoin.length, - ), - ); - _debounce!.cancel(); - _mentionsOverlay?.remove(); - _mentionsOverlay = null; - }, - child: widget.mentionsTileBuilder != null - ? widget.mentionsTileBuilder!(context, m) - : MentionTile(m), + textEditingController.value = + TextEditingValue( + text: rejoin + + textEditingController.text.substring( + textEditingController + .selection.start), + selection: TextSelection.collapsed( + offset: rejoin.length, + ), + ); + _debounce!.cancel(); + _mentionsOverlay?.remove(); + _mentionsOverlay = null; + }, + child: widget.mentionsTileBuilder != null + ? widget.mentionsTileBuilder!(context, m) + : MentionTile(m), + ), ), - ), - ) - .toList(), - const SizedBox( - height: 8, - ), - ], + ) + .toList(), + const SizedBox( + height: 8, + ), + ], + ), ), ), ), - ), - ), + ); + }, ), ), ); @@ -1379,91 +1358,88 @@ class MessageInputState extends State { final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; - return OverlayEntry( - builder: (context) => Positioned( - bottom: size.height + MediaQuery.of(context).viewInsets.bottom, - left: 0, - right: 0, - child: Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: StreamChatTheme.of(context).colorTheme.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + return OverlayEntry(builder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return Positioned( + bottom: size.height + MediaQuery.of(context).viewInsets.bottom, + left: 0, + right: 0, + child: Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: chatThemeData.colorTheme.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.antiAlias, + child: Container( + constraints: BoxConstraints.loose(const Size.fromHeight(200)), + decoration: BoxDecoration( + boxShadow: const [ + BoxShadow( + spreadRadius: -8, + blurRadius: 5, + offset: Offset(0, -4), ), - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose(const Size.fromHeight(200)), - decoration: BoxDecoration( - boxShadow: const [ - BoxShadow( - spreadRadius: -8, - blurRadius: 5, - offset: Offset(0, -4), - ), - ], - color: StreamChatTheme.of(context).colorTheme.white, - ), - child: ListView.builder( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - itemCount: emojis.length + 1, - itemBuilder: (context, i) { - if (i == 0) { - return Padding( - padding: const EdgeInsets.only(left: 8, top: 8), - child: Row( - children: [ - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 8), - child: StreamSvgIcon.smile( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), - ), - Flexible( - child: Text( - 'Emoji matching "$query"', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - ), - ) - ], + ], + color: chatThemeData.colorTheme.white, + ), + child: ListView.builder( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + itemCount: emojis.length + 1, + itemBuilder: (context, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.only(left: 8, top: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: StreamSvgIcon.smile( + color: chatThemeData.colorTheme.accentBlue, ), - ); - } - - final emoji = emojis.elementAt(i - 1)!; - return ListTile( - title: SubstringHighlight( - text: - // ignore: lines_longer_than_80_chars - "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", - term: query, - textStyleHighlight: - Theme.of(context).textTheme.headline6!.copyWith( - fontSize: 14.5, - fontWeight: FontWeight.bold, - ), - textStyle: - Theme.of(context).textTheme.headline6!.copyWith( - fontSize: 14.5, - ), ), - onTap: () { - _chooseEmoji(splits, emoji); - }, - ); - }), - ), - ), - )); + Flexible( + child: Text( + 'Emoji matching "$query"', + style: TextStyle( + color: chatThemeData.colorTheme.black + .withOpacity(.5), + ), + ), + ) + ], + ), + ); + } + + final emoji = emojis.elementAt(i - 1)!; + final themeData = Theme.of(context); + return ListTile( + title: SubstringHighlight( + text: + // ignore: lines_longer_than_80_chars + "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", + term: query, + textStyleHighlight: + themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + fontWeight: FontWeight.bold, + ), + textStyle: themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + ), + ), + onTap: () { + _chooseEmoji(splits, emoji); + }, + ); + }), + ), + ), + ); + }); } void _chooseEmoji(List splits, Emoji emoji) { @@ -1589,30 +1565,32 @@ class MessageInputState extends State { ); } - Widget _buildRemoveButton(Attachment attachment) => SizedBox( - height: 24, - width: 24, - child: RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - hoverElevation: 0, - onPressed: () { - setState(() => _attachments.remove(attachment.id)); - }, - fillColor: - StreamChatTheme.of(context).colorTheme.black.withOpacity(.5), - child: Center( - child: StreamSvgIcon.close( - size: 24, - color: StreamChatTheme.of(context).colorTheme.white, - ), + Widget _buildRemoveButton(Attachment attachment) { + final chatThemeData = StreamChatTheme.of(context); + return SizedBox( + height: 24, + width: 24, + child: RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + hoverElevation: 0, + onPressed: () { + setState(() => _attachments.remove(attachment.id)); + }, + fillColor: chatThemeData.colorTheme.black.withOpacity(.5), + child: Center( + child: StreamSvgIcon.close( + size: 24, + color: chatThemeData.colorTheme.white, ), ), - ); + ), + ); + } Widget _buildAttachment(Attachment attachment) { if (widget.attachmentThumbnailBuilders?.containsKey(attachment.type) == @@ -1642,18 +1620,18 @@ class MessageInputState extends State { fit: BoxFit.cover, errorWidget: (_, obj, trace) => getFileTypeImage(attachment.extraData['other'] as String?), - progressIndicatorBuilder: (context, _, progress) => - Shimmer.fromColors( - baseColor: - StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - child: Image.asset( - 'images/placeholder.png', - fit: BoxFit.cover, - package: 'stream_chat_flutter', - ), - ), + progressIndicatorBuilder: (context, _, progress) { + final chatThemeData = StreamChatTheme.of(context); + return Shimmer.fromColors( + baseColor: chatThemeData.colorTheme.greyGainsboro, + highlightColor: chatThemeData.colorTheme.whiteSmoke, + child: Image.asset( + 'images/placeholder.png', + fit: BoxFit.cover, + package: 'stream_chat_flutter', + ), + ); + }, ); case 'video': return Stack( @@ -1685,17 +1663,14 @@ class MessageInputState extends State { Widget _buildCommandButton() { final s = textEditingController.text.trim(); + final chatThemeData = StreamChatTheme.of(context); return IconButton( icon: StreamSvgIcon.lightning( color: s.isNotEmpty - ? StreamChatTheme.of(context).colorTheme.greyGainsboro + ? chatThemeData.colorTheme.greyGainsboro : (_commandsOverlay != null - ? StreamChatTheme.of(context) - .messageInputTheme - .actionButtonColor - : StreamChatTheme.of(context) - .messageInputTheme - .actionButtonIdleColor), + ? chatThemeData.messageInputTheme.actionButtonColor + : chatThemeData.messageInputTheme.actionButtonIdleColor), ), padding: const EdgeInsets.all(0), constraints: const BoxConstraints.tightFor( @@ -1730,39 +1705,40 @@ class MessageInputState extends State { ); } - Widget _buildAttachmentButton() => IconButton( - icon: StreamSvgIcon.attach( - color: _openFilePickerSection - ? StreamChatTheme.of(context).messageInputTheme.actionButtonColor - : StreamChatTheme.of(context) - .messageInputTheme - .actionButtonIdleColor, - ), - padding: const EdgeInsets.all(0), - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), - splashRadius: 24, - onPressed: () async { - _emojiOverlay?.remove(); - _emojiOverlay = null; - _commandsOverlay?.remove(); - _commandsOverlay = null; - _mentionsOverlay?.remove(); - _mentionsOverlay = null; + Widget _buildAttachmentButton() { + final chatThemeData = StreamChatTheme.of(context); + return IconButton( + icon: StreamSvgIcon.attach( + color: _openFilePickerSection + ? chatThemeData.messageInputTheme.actionButtonColor + : chatThemeData.messageInputTheme.actionButtonIdleColor, + ), + padding: const EdgeInsets.all(0), + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, + onPressed: () async { + _emojiOverlay?.remove(); + _emojiOverlay = null; + _commandsOverlay?.remove(); + _commandsOverlay = null; + _mentionsOverlay?.remove(); + _mentionsOverlay = null; - if (_openFilePickerSection) { - setState(() { - _animateContainer = true; - _openFilePickerSection = false; - _filePickerSize = _kMinMediaPickerSize; - }); - } else { - showAttachmentModal(); - } - }, - ); + if (_openFilePickerSection) { + setState(() { + _animateContainer = true; + _openFilePickerSection = false; + _filePickerSize = _kMinMediaPickerSize; + }); + } else { + showAttachmentModal(); + } + }, + ); + } /// Show the attachment modal, making the user choose where to /// pick a media from @@ -2103,8 +2079,9 @@ class MessageInputState extends State { StreamSubscription? _keyboardListener; void _showErrorAlert(String description) { + final chatThemeData = StreamChatTheme.of(context); showModalBottomSheet( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, + backgroundColor: chatThemeData.colorTheme.white, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -2118,7 +2095,7 @@ class MessageInputState extends State { height: 26, ), StreamSvgIcon.error( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: chatThemeData.colorTheme.accentRed, size: 24, ), const SizedBox( @@ -2126,7 +2103,7 @@ class MessageInputState extends State { ), Text( 'Something went wrong', - style: StreamChatTheme.of(context).textTheme.headlineBold, + style: chatThemeData.textTheme.headlineBold, ), const SizedBox( height: 7, @@ -2142,8 +2119,7 @@ class MessageInputState extends State { height: 36, ), Container( - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), + color: chatThemeData.colorTheme.black.withOpacity(.08), height: 1, ), Row( @@ -2155,13 +2131,8 @@ class MessageInputState extends State { }, child: Text( 'OK', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue), + style: chatThemeData.textTheme.bodyBold + .copyWith(color: chatThemeData.colorTheme.accentBlue), ), ), ], @@ -2291,6 +2262,7 @@ class __PickerWidgetState extends State<_PickerWidget> { return const Center(child: CircularProgressIndicator()); } + final chatThemeData = StreamChatTheme.of(context); if (snapshot.data!) { if (widget.containsFile) { return GestureDetector( @@ -2299,12 +2271,12 @@ class __PickerWidgetState extends State<_PickerWidget> { }, child: Container( constraints: const BoxConstraints.expand(), - color: StreamChatTheme.of(context).colorTheme.whiteSmoke, + color: chatThemeData.colorTheme.whiteSmoke, alignment: Alignment.center, child: Text( 'Add more files', style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentBlue, fontWeight: FontWeight.bold, ), ), @@ -2322,7 +2294,7 @@ class __PickerWidgetState extends State<_PickerWidget> { PhotoManager.openSetting(); }, child: Container( - color: StreamChatTheme.of(context).colorTheme.whiteSmoke, + color: chatThemeData.colorTheme.whiteSmoke, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -2331,27 +2303,22 @@ class __PickerWidgetState extends State<_PickerWidget> { 'svgs/icon_picture_empty_state.svg', package: 'stream_chat_flutter', height: 140, - color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + color: chatThemeData.colorTheme.greyGainsboro, ), Text( // ignore: lines_longer_than_80_chars 'Please enable access to your photos \nand videos so you can share them with friends.', - style: StreamChatTheme.of(context).textTheme.body.copyWith( - color: StreamChatTheme.of(context).colorTheme.grey), + style: chatThemeData.textTheme.body + .copyWith(color: chatThemeData.colorTheme.grey), textAlign: TextAlign.center, ), const SizedBox(height: 6), Center( child: Text( 'Allow access to your gallery', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.accentBlue, + ), ), ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 2800a041..661ce885 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -6,15 +6,15 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:rxdart/rxdart.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/message_widget.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; +import 'package:stream_chat_flutter/src/swipeable.dart'; import 'package:stream_chat_flutter/src/system_message.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:visibility_detector/visibility_detector.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; -import 'package:stream_chat_flutter/src/swipeable.dart'; /// Widget builder for message typedef MessageBuilder = Widget Function( @@ -274,15 +274,15 @@ class _MessageListViewState extends State { bool _showScrollToBottom = false; late final ItemPositionsListener _itemPositionListener; int? _messageListLength; - StreamChannelState? streamChannel; + late StreamChannelState streamChannel; int? get _initialIndex { if (widget.initialScrollIndex != null) return widget.initialScrollIndex; - if (streamChannel!.initialMessageId != null) { - final messages = streamChannel!.channel.state!.messages; + if (streamChannel.initialMessageId != null) { + final messages = streamChannel.channel.state!.messages; final totalMessages = messages.length; final messageIndex = - messages.indexWhere((e) => e.id == streamChannel!.initialMessageId); + messages.indexWhere((e) => e.id == streamChannel.initialMessageId); final index = totalMessages - messageIndex; if (index != 0) return index - 1; return index; @@ -295,9 +295,9 @@ class _MessageListViewState extends State { return 0; } - bool _isInitialMessage(String id) => streamChannel!.initialMessageId == id; + bool _isInitialMessage(String id) => streamChannel.initialMessageId == id; - bool get _upToDate => streamChannel!.channel.state!.isUpToDate; + bool get _upToDate => streamChannel.channel.state!.isUpToDate; bool get _isThreadConversation => widget.parentMessage != null; @@ -316,46 +316,37 @@ class _MessageListViewState extends State { final MessageListController _messageListController = MessageListController(); @override - Widget build(BuildContext context) => MessageListCore( - messageFilter: widget.messageFilter, - loadingBuilder: widget.loadingBuilder ?? - (context) => const Center( - child: CircularProgressIndicator(), + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return MessageListCore( + messageFilter: widget.messageFilter, + loadingBuilder: widget.loadingBuilder ?? + (context) => const Center( + child: CircularProgressIndicator(), + ), + emptyBuilder: widget.emptyBuilder ?? + (context) => Center( + child: Text( + 'No chats here yet...', + style: chatThemeData.textTheme.footnote.copyWith( + color: chatThemeData.colorTheme.black.withOpacity(.5)), ), - emptyBuilder: widget.emptyBuilder ?? - (context) => Center( - child: Text( - 'No chats here yet...', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5)), - ), + ), + messageListBuilder: + widget.messageListBuilder ?? (context, list) => _buildListView(list), + messageListController: _messageListController, + parentMessage: widget.parentMessage, + showScrollToBottom: widget.showScrollToBottom, + errorWidgetBuilder: widget.errorWidgetBuilder ?? + (BuildContext context, Object error) => Center( + child: Text( + 'Something went wrong', + style: chatThemeData.textTheme.footnote.copyWith( + color: chatThemeData.colorTheme.black.withOpacity(.5)), ), - messageListBuilder: widget.messageListBuilder ?? - (context, list) => _buildListView(list), - messageListController: _messageListController, - parentMessage: widget.parentMessage, - showScrollToBottom: widget.showScrollToBottom, - errorWidgetBuilder: widget.errorWidgetBuilder ?? - (BuildContext context, Object error) => Center( - child: Text( - 'Something went wrong', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5)), - ), - ), - ); + ), + ); + } Widget _buildListView(List data) { messages = data; @@ -448,10 +439,10 @@ class _MessageListViewState extends State { if (i == 0) return const SizedBox(height: 30); if (i == messages.length + 1) { final replyCount = widget.parentMessage!.replyCount; + final chatThemeData = StreamChatTheme.of(context); return Container( decoration: BoxDecoration( - gradient: - StreamChatTheme.of(context).colorTheme.bgGradient, + gradient: chatThemeData.colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.all(8), @@ -459,10 +450,8 @@ class _MessageListViewState extends State { // ignore: lines_longer_than_80_chars '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', textAlign: TextAlign.center, - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .subtitle, + style: chatThemeData + .channelTheme.channelHeaderTheme.subtitle, ), ), ); @@ -616,8 +605,8 @@ class _MessageListViewState extends State { Widget _buildScrollToBottom() => StreamBuilder>( stream: Rx.combineLatest2( - streamChannel!.channel.state!.isUpToDateStream, - streamChannel!.channel.state!.unreadCountStream, + streamChannel.channel.state!.isUpToDateStream, + streamChannel.channel.state!.unreadCountStream, (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), ), builder: (_, snapshot) { @@ -633,8 +622,9 @@ class _MessageListViewState extends State { } final unreadCount = snapshot.data!.item2; final showUnreadCount = unreadCount > 0 && - streamChannel!.channel.state!.members.any((e) => - e.userId == streamChannel!.channel.client.state.user!.id); + streamChannel.channel.state!.members.any((e) => + e.userId == streamChannel.channel.client.state.user!.id); + final chatThemeData = StreamChatTheme.of(context); return Positioned( bottom: 8, right: 8, @@ -644,15 +634,15 @@ class _MessageListViewState extends State { clipBehavior: Clip.none, children: [ FloatingActionButton( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, + backgroundColor: chatThemeData.colorTheme.white, onPressed: () { if (unreadCount > 0) { - streamChannel!.channel.markRead(); + streamChannel.channel.markRead(); } if (!_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; - streamChannel!.reloadChannel(); + streamChannel.reloadChannel(); } else { setState(() => _showScrollToBottom = false); _scrollController!.scrollTo( @@ -663,7 +653,7 @@ class _MessageListViewState extends State { } }, child: StreamSvgIcon.down( - color: StreamChatTheme.of(context).colorTheme.black, + color: chatThemeData.colorTheme.black, ), ), if (showUnreadCount) @@ -692,12 +682,12 @@ class _MessageListViewState extends State { ); Widget _buildLoadingIndicator( - StreamChannelState? streamChannel, + StreamChannelState streamChannel, QueryDirection direction, ) { final stream = direction == QueryDirection.top - ? streamChannel!.queryTopMessages - : streamChannel!.queryBottomMessages; + ? streamChannel.queryTopMessages + : streamChannel.queryBottomMessages; return StreamBuilder( key: const Key('LOADING-INDICATOR'), stream: stream, @@ -764,7 +754,7 @@ class _MessageListViewState extends State { BuildContext context, Message message, List messages, - StreamChannelState? streamChannel, + StreamChannelState streamChannel, ) { Widget messageWidget; if (widget.messageBuilder != null) { @@ -790,7 +780,7 @@ class _MessageListViewState extends State { onVisibilityChanged: (visibility) { final isVisible = visibility.visibleBounds != Rect.zero; if (isVisible) { - final channel = streamChannel!.channel; + final channel = streamChannel.channel; if (_upToDate && channel.config?.readEvents == true && channel.state!.unreadCount! > 0) { @@ -811,6 +801,7 @@ class _MessageListViewState extends State { final isMyMessage = message.user!.id == StreamChat.of(context).user!.id; final isOnlyEmoji = message.text!.isOnlyEmoji; + final chatThemeData = StreamChatTheme.of(context); return MessageWidget( showReplyMessage: false, showResendMessage: false, @@ -837,8 +828,8 @@ class _MessageListViewState extends State { borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null, showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, messageTheme: isMyMessage - ? StreamChatTheme.of(context).ownMessageTheme - : StreamChatTheme.of(context).otherMessageTheme, + ? chatThemeData.ownMessageTheme + : chatThemeData.otherMessageTheme, onShowMessage: widget.onShowMessage, onReturnAction: (action) { switch (action) { @@ -897,7 +888,7 @@ class _MessageListViewState extends State { ); } - final channel = streamChannel!.channel; + final channel = streamChannel.channel; final readList = channel.state?.read?.where((read) { if (read.user.id == userId) return false; return read.lastRead.isAfter(message.createdAt) || @@ -946,6 +937,7 @@ class _MessageListViewState extends State { ? BorderSide.none : null; + final chatThemeData = StreamChatTheme.of(context); Widget child = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), message: message, @@ -970,7 +962,7 @@ class _MessageListViewState extends State { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); } else { - await streamChannel!.loadChannelAtMessage(quotedMessageId).then((_) { + await streamChannel.loadChannelAtMessage(quotedMessageId).then((_) { WidgetsBinding.instance!.addPostFrameCallback((_) { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); @@ -1013,8 +1005,8 @@ class _MessageListViewState extends State { horizontal: isOnlyEmoji ? 0 : 16.0, ), messageTheme: isMyMessage - ? StreamChatTheme.of(context).ownMessageTheme - : StreamChatTheme.of(context).otherMessageTheme, + ? chatThemeData.ownMessageTheme + : chatThemeData.otherMessageTheme, readList: readList, allRead: allRead, onShowMessage: widget.onShowMessage, @@ -1054,7 +1046,7 @@ class _MessageListViewState extends State { widget.onMessageSwiped?.call(message); }, backgroundIcon: StreamSvgIcon.reply( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentBlue, ), child: child, ), @@ -1064,7 +1056,7 @@ class _MessageListViewState extends State { if (!initialMessageHighlightComplete && widget.highlightInitialMessage && _isInitialMessage(message.id)) { - final colorTheme = StreamChatTheme.of(context).colorTheme; + final colorTheme = chatThemeData.colorTheme; final highlightColor = widget.messageHighlightColor ?? colorTheme.highlight; child = TweenAnimationBuilder( @@ -1100,14 +1092,23 @@ class _MessageListViewState extends State { initialIndex = _initialIndex; initialAlignment = _initialAlignment; + _getOnThreadTap(); + super.initState(); + } + + @override + void didChangeDependencies() { + streamChannel = StreamChannel.of(context); + + _messageNewListener?.cancel(); _messageNewListener = - streamChannel!.channel.on(EventType.messageNew).listen((event) { + streamChannel.channel.on(EventType.messageNew).listen((event) { if (_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; } if (event.message!.user!.id == - streamChannel!.channel.client.state.user!.id) { + streamChannel.channel.client.state.user!.id) { WidgetsBinding.instance!.addPostFrameCallback((_) { _scrollController?.jumpTo( index: 0, @@ -1117,11 +1118,9 @@ class _MessageListViewState extends State { }); if (_isThreadConversation) { - streamChannel!.getReplies(widget.parentMessage!.id); + streamChannel.getReplies(widget.parentMessage!.id); } - - _getOnThreadTap(); - super.initState(); + super.didChangeDependencies(); } void _getOnThreadTap() { @@ -1139,12 +1138,12 @@ class _MessageListViewState extends State { context, MaterialPageRoute( builder: (_) => StreamBuilder( - stream: streamChannel!.channel.state!.messagesStream.map( + stream: streamChannel.channel.state!.messagesStream.map( (messages) => messages!.firstWhere((m) => m.id == message.id)), initialData: message, builder: (_, snapshot) => StreamChannel( - channel: streamChannel!.channel, + channel: streamChannel.channel, child: widget.threadBuilder!(context, snapshot.data), ), ), @@ -1157,7 +1156,7 @@ class _MessageListViewState extends State { @override void dispose() { if (!_upToDate) { - streamChannel!.reloadChannel(); + streamChannel.reloadChannel(); } _messageNewListener?.cancel(); super.dispose(); diff --git a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart index e2392b50..76ce215c 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -1,13 +1,13 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Modal widget for displaying message reactions class MessageReactionsModal extends StatelessWidget { @@ -176,8 +176,9 @@ class MessageReactionsModal extends StatelessWidget { Widget _buildReactionCard(BuildContext context) { final currentUser = StreamChat.of(context).user; + final chatThemeData = StreamChatTheme.of(context); return Card( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -190,7 +191,7 @@ class MessageReactionsModal extends StatelessWidget { children: [ Text( 'Message Reactions', - style: StreamChatTheme.of(context).textTheme.headlineBold, + style: chatThemeData.textTheme.headlineBold, ), const SizedBox(height: 16), Flexible( @@ -220,6 +221,7 @@ class MessageReactionsModal extends StatelessWidget { BuildContext context, ) { final isCurrentUser = reaction.user?.id == currentUser.id; + final chatThemeData = StreamChatTheme.of(context); return ConstrainedBox( constraints: BoxConstraints.loose(const Size( 64, @@ -258,7 +260,7 @@ class MessageReactionsModal extends StatelessWidget { messageTheme.reactionsBorderColor ?? Colors.transparent, backgroundColor: messageTheme.reactionsBackgroundColor ?? Colors.transparent, - maskColor: StreamChatTheme.of(context).colorTheme.white, + maskColor: chatThemeData.colorTheme.white, tailCirclesSpacing: 1, highlightOwnReactions: false, ), @@ -269,7 +271,7 @@ class MessageReactionsModal extends StatelessWidget { const SizedBox(height: 8), Text( reaction.user!.name.split(' ')[0], - style: StreamChatTheme.of(context).textTheme.footnoteBold, + style: chatThemeData.textTheme.footnoteBold, textAlign: TextAlign.center, ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_search_item.dart b/packages/stream_chat_flutter/lib/src/message_search_item.dart index dc276cb3..1f8a31ea 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_item.dart @@ -35,6 +35,7 @@ class MessageSearchItem extends StatelessWidget { final channel = getMessageResponse.channel; final channelName = channel?.extraData['name']; final user = message.user!; + final chatThemeData = StreamChatTheme.of(context); return ListTile( onTap: onTap, leading: UserAvatar( @@ -49,21 +50,18 @@ class MessageSearchItem extends StatelessWidget { children: [ Text( user.id == StreamChat.of(context).user?.id ? 'You' : user.name, - style: StreamChatTheme.of(context).channelPreviewTheme.title, + style: chatThemeData.channelPreviewTheme.title, ), if (channelName != null) ...[ Text( ' in ', - style: StreamChatTheme.of(context) - .channelPreviewTheme - .title - ?.copyWith( - fontWeight: FontWeight.normal, - ), + style: chatThemeData.channelPreviewTheme.title?.copyWith( + fontWeight: FontWeight.normal, + ), ), Text( channelName as String, - style: StreamChatTheme.of(context).channelPreviewTheme.title, + style: chatThemeData.channelPreviewTheme.title, ), ], ], @@ -121,22 +119,23 @@ class MessageSearchItem extends StatelessWidget { text = parts.join(' '); } + final chatThemeData = StreamChatTheme.of(context); 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, - ), + chatThemeData.channelPreviewTheme.subtitle?.copyWith( + fontStyle: (message.isSystem || message.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + ), + chatThemeData.channelPreviewTheme.subtitle?.copyWith( + fontStyle: (message.isSystem || message.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + fontWeight: FontWeight.bold, + ), ), maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart index 1d9ef272..8b8a8dec 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/message_search_item.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Callback called when tapping on a user typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); @@ -270,12 +270,13 @@ class _MessageSearchListViewState extends State { ); if (widget.showResultCount) { + final chatThemeData = StreamChatTheme.of(context); child = Column( children: [ Container( width: double.maxFinite, decoration: BoxDecoration( - gradient: StreamChatTheme.of(context).colorTheme.bgGradient, + gradient: chatThemeData.colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.symmetric( @@ -285,7 +286,7 @@ class _MessageSearchListViewState extends State { child: Text( '${items.length} results', style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.grey, + color: chatThemeData.colorTheme.grey, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index 24a32a09..e5abede0 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -1,8 +1,8 @@ import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Text widget to display in message class MessageText extends StatelessWidget { @@ -31,6 +31,7 @@ class MessageText extends StatelessWidget { Widget build(BuildContext context) { final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\\\n'); + final themeData = Theme.of(context); return MarkdownBody( data: text, onTapLink: ( @@ -60,14 +61,14 @@ class MessageText extends StatelessWidget { } }, 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, - ), + themeData.copyWith( + textTheme: themeData.textTheme.apply( + bodyColor: messageTheme.messageText?.color, + decoration: messageTheme.messageText?.decoration, + decorationColor: messageTheme.messageText?.decorationColor, + decorationStyle: messageTheme.messageText?.decorationStyle, + fontFamily: messageTheme.messageText?.fontFamily, + ), ), ).copyWith( a: messageTheme.messageLinks, diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 1866da0b..85caf4a6 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -8,6 +8,8 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/image_group.dart'; import 'package:stream_chat_flutter/src/message_action.dart'; import 'package:stream_chat_flutter/src/message_actions_modal.dart'; import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; @@ -15,8 +17,6 @@ import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/image_group.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Widget builder for building attachments typedef AttachmentBuilder = Widget Function( @@ -109,6 +109,7 @@ class MessageWidget extends StatefulWidget { borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, ); + final mediaQueryData = MediaQuery.of(context); if (attachments.length > 1) { return Padding( padding: attachmentPadding, @@ -118,8 +119,8 @@ class MessageWidget extends StatefulWidget { color: messageTheme.messageBackgroundColor, child: ImageGroup( size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, ), images: attachments, message: message, @@ -142,8 +143,8 @@ class MessageWidget extends StatefulWidget { message: message, messageTheme: messageTheme, size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, ), onShowMessage: onShowMessage, onReturnAction: onReturnAction, @@ -167,24 +168,25 @@ class MessageWidget extends StatefulWidget { return wrapAttachmentWidget( context, Column( - children: attachments - .map((attachment) => VideoAttachment( - attachment: attachment, - messageTheme: messageTheme, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), - message: message, - onShowMessage: onShowMessage, - onReturnAction: onReturnAction, - onAttachmentTap: onAttachmentTap != null - ? () { - onAttachmentTap(message, attachment); - } - : null, - )) - .toList(), + children: attachments.map((attachment) { + final mediaQueryData = MediaQuery.of(context); + return VideoAttachment( + attachment: attachment, + messageTheme: messageTheme, + size: Size( + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, + ), + message: message, + onShowMessage: onShowMessage, + onReturnAction: onReturnAction, + onAttachmentTap: onAttachmentTap != null + ? () { + onAttachmentTap(message, attachment); + } + : null, + ); + }).toList(), ), border, reverse, @@ -200,18 +202,19 @@ class MessageWidget extends StatefulWidget { return wrapAttachmentWidget( context, Column( - children: attachments - .map((attachment) => GiphyAttachment( - attachment: attachment, - message: message, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), - onShowMessage: onShowMessage, - onReturnAction: onReturnAction, - )) - .toList(), + children: attachments.map((attachment) { + final mediaQueryData = MediaQuery.of(context); + return GiphyAttachment( + attachment: attachment, + message: message, + size: Size( + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, + ), + onShowMessage: onShowMessage, + onReturnAction: onReturnAction, + ); + }).toList(), ), border, reverse, @@ -230,21 +233,24 @@ class MessageWidget extends StatefulWidget { return Column( children: attachments - .map((attachment) => wrapAttachmentWidget( - context, - FileAttachment( - message: message, - attachment: attachment, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), + .map((attachment) { + final mediaQueryData = MediaQuery.of(context); + return wrapAttachmentWidget( + context, + FileAttachment( + message: message, + attachment: attachment, + size: Size( + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, ), - border, - reverse, - attachmentBorderRadiusGeometry as BorderRadius? ?? - BorderRadius.zero, - )) + ), + border, + reverse, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, + ); + }) .insertBetween(SizedBox( height: attachmentPadding.vertical / 2, )) @@ -658,12 +664,13 @@ class _MessageWidgetState extends State widget.onQuotedMessageTap != null ? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId) : null; + final chatThemeData = StreamChatTheme.of(context); return QuotedMessageWidget( onTap: onTap, message: widget.message.quotedMessage!, messageTheme: isMyMessage - ? StreamChatTheme.of(context).otherMessageTheme - : StreamChatTheme.of(context).ownMessageTheme, + ? chatThemeData.otherMessageTheme + : chatThemeData.ownMessageTheme, reverse: widget.reverse, padding: EdgeInsets.only( right: 8, left: 8, top: 8, bottom: hasNonUrlAttachments ? 8 : 0), @@ -672,6 +679,7 @@ class _MessageWidgetState extends State Widget get _bottomRow { if (isDeleted) { + final chatThemeData = StreamChatTheme.of(context); return Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, @@ -679,16 +687,14 @@ class _MessageWidgetState extends State mainAxisSize: MainAxisSize.min, children: [ StreamSvgIcon.eye( - color: StreamChatTheme.of(context).colorTheme.grey, + color: chatThemeData.colorTheme.grey, size: 16, ), const SizedBox(width: 8), Text( 'Only visible to you', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith(color: StreamChatTheme.of(context).colorTheme.grey), + style: chatThemeData.textTheme.footnote + .copyWith(color: chatThemeData.colorTheme.grey), ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/option_list_tile.dart b/packages/stream_chat_flutter/lib/src/option_list_tile.dart index 9a11fd7a..10ae1b5f 100644 --- a/packages/stream_chat_flutter/lib/src/option_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/option_list_tile.dart @@ -41,56 +41,55 @@ class OptionListTile extends StatelessWidget { final TextStyle? titleTextStyle; @override - Widget build(BuildContext context) => Column( - children: [ - Container( - color: separatorColor ?? - StreamChatTheme.of(context).colorTheme.greyGainsboro, - height: 1, - ), - Material( - color: tileColor ?? StreamChatTheme.of(context).colorTheme.white, - child: SizedBox( - height: 63, - child: InkWell( - onTap: onTap, - child: Row( - children: [ - if (leading != null) Center(child: leading), - if (leading == null) - const SizedBox( - width: 16, - ), - Expanded( - flex: 4, - child: Text( - title!, - style: titleTextStyle ?? - (titleColor == null - ? StreamChatTheme.of(context).textTheme.bodyBold - : StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: titleColor, - )), + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return Column( + children: [ + Container( + color: separatorColor ?? chatThemeData.colorTheme.greyGainsboro, + height: 1, + ), + Material( + color: tileColor ?? chatThemeData.colorTheme.white, + child: SizedBox( + height: 63, + child: InkWell( + onTap: onTap, + child: Row( + children: [ + if (leading != null) Center(child: leading), + if (leading == null) + const SizedBox( + width: 16, + ), + Expanded( + flex: 4, + child: Text( + title!, + style: titleTextStyle ?? + (titleColor == null + ? chatThemeData.textTheme.bodyBold + : chatThemeData.textTheme.bodyBold.copyWith( + color: titleColor, + )), + ), + ), + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.only(right: 16), + child: Align( + alignment: Alignment.centerRight, + child: trailing ?? Container(), ), ), - Expanded( - flex: 2, - child: Padding( - padding: const EdgeInsets.only(right: 16), - child: Align( - alignment: Alignment.centerRight, - child: trailing ?? Container(), - ), - ), - ), - ], - ), + ), + ], ), ), ), - ], - ); + ), + ], + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index 9b45c248..82ebc6a9 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -126,6 +126,8 @@ class ReactionBubble extends StatelessWidget { (r) => r.type == reaction.type, ); + final chatThemeData = StreamChatTheme.of(context); + final userId = StreamChat.of(context).user?.id; return Padding( padding: const EdgeInsets.symmetric( horizontal: 4, @@ -135,24 +137,16 @@ class ReactionBubble extends StatelessWidget { assetName: reactionIcon.assetName, width: 16, height: 16, - color: (!highlightOwnReactions || - reaction.user?.id == StreamChat.of(context).user?.id) - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), + color: (!highlightOwnReactions || reaction.user?.id == userId) + ? chatThemeData.colorTheme.accentBlue + : chatThemeData.colorTheme.black.withOpacity(.5), ) : Icon( Icons.help_outline_rounded, size: 16, - color: (!highlightOwnReactions || - reaction.user?.id == StreamChat.of(context).user?.id) - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), + color: (!highlightOwnReactions || reaction.user?.id == userId) + ? chatThemeData.colorTheme.accentBlue + : chatThemeData.colorTheme.black.withOpacity(.5), ), ); } diff --git a/packages/stream_chat_flutter/lib/src/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reaction_picker.dart index 1308876d..8a89b8c3 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_picker.dart @@ -2,8 +2,8 @@ import 'dart:math'; import 'package:ezanimation/ezanimation.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png) @@ -33,7 +33,8 @@ class _ReactionPickerState extends State @override Widget build(BuildContext context) { - final reactionIcons = StreamChatTheme.of(context).reactionIcons; + final chatThemeData = StreamChatTheme.of(context); + final reactionIcons = chatThemeData.reactionIcons; if (animations.isEmpty && reactionIcons.isNotEmpty) { reactionIcons.forEach((element) { @@ -57,7 +58,7 @@ class _ReactionPickerState extends State scale: val, child: Material( borderRadius: BorderRadius.circular(24), - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, clipBehavior: Clip.hardEdge, child: Padding( padding: const EdgeInsets.symmetric( @@ -119,9 +120,8 @@ class _ReactionPickerState extends State animations[index].value * 24.0, ), color: ownReactionIndex != -1 - ? StreamChatTheme.of(context) - .colorTheme - .accentBlue + ? chatThemeData + .colorTheme.accentBlue : Theme.of(context) .iconTheme .color! diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart index ba94642f..347fc59d 100644 --- a/packages/stream_chat_flutter/lib/src/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/thread_header.dart @@ -97,65 +97,60 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { final List? actions; @override - Widget build(BuildContext context) => AppBar( - automaticallyImplyLeading: false, - brightness: Theme.of(context).brightness, - elevation: 1, - leading: leading ?? - (showBackButton - ? StreamBackButton( - cid: StreamChannel.of(context).channel.cid, - onPressed: onBackPressed, - showUnreads: true, - ) - : const SizedBox()), - backgroundColor: - StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, - centerTitle: true, - actions: actions, - title: InkWell( - onTap: onTitleTap, - child: SizedBox( - height: preferredSize.height, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - title ?? - Text( - 'Thread Reply', - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .title, - ), - const SizedBox(height: 2), - subtitle ?? - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'with ', - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .subtitle, + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return AppBar( + automaticallyImplyLeading: false, + brightness: Theme.of(context).brightness, + elevation: 1, + leading: leading ?? + (showBackButton + ? StreamBackButton( + cid: StreamChannel.of(context).channel.cid, + onPressed: onBackPressed, + showUnreads: true, + ) + : const SizedBox()), + backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color, + centerTitle: true, + actions: actions, + title: InkWell( + onTap: onTitleTap, + child: SizedBox( + height: preferredSize.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + title ?? + Text( + 'Thread Reply', + style: chatThemeData.channelTheme.channelHeaderTheme.title, + ), + const SizedBox(height: 2), + subtitle ?? + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'with ', + style: chatThemeData + .channelTheme.channelHeaderTheme.subtitle, + ), + Flexible( + child: ChannelName( + textStyle: chatThemeData + .channelTheme.channelHeaderTheme.subtitle, ), - Flexible( - child: ChannelName( - textStyle: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .subtitle, - ), - ), - ], - ), - ], - ), + ), + ], + ), + ], ), ), - ); + ), + ); + } @override final Size preferredSize; diff --git a/packages/stream_chat_flutter/lib/src/url_attachment.dart b/packages/stream_chat_flutter/lib/src/url_attachment.dart index 0b91c2ee..995475a8 100644 --- a/packages/stream_chat_flutter/lib/src/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/url_attachment.dart @@ -26,91 +26,84 @@ class UrlAttachment extends StatelessWidget { final EdgeInsets textPadding; @override - Widget build(BuildContext context) => GestureDetector( - onTap: () { - launchURL( - context, - urlAttachment.ogScrapeUrl, - ); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (urlAttachment.imageUrl != null) - Container( - clipBehavior: Clip.antiAliasWithSaveLayer, - margin: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - ), - child: Stack( - children: [ - CachedNetworkImage( - width: double.infinity, - imageUrl: urlAttachment.imageUrl!, - fit: BoxFit.cover, - ), - Positioned( - left: 0, - bottom: -1, - child: Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topRight: Radius.circular(16), - ), - color: - StreamChatTheme.of(context).colorTheme.blueAlice, + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return GestureDetector( + onTap: () { + launchURL( + context, + urlAttachment.ogScrapeUrl, + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (urlAttachment.imageUrl != null) + Container( + clipBehavior: Clip.antiAliasWithSaveLayer, + margin: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: Stack( + children: [ + CachedNetworkImage( + width: double.infinity, + imageUrl: urlAttachment.imageUrl!, + fit: BoxFit.cover, + ), + Positioned( + left: 0, + bottom: -1, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(16), ), - child: Padding( - padding: const EdgeInsets.only( - top: 8, - left: 8, - right: 8, - ), - child: Text( - hostDisplayName, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), + color: chatThemeData.colorTheme.blueAlice, + ), + child: Padding( + padding: const EdgeInsets.only( + top: 8, + left: 8, + right: 8, + ), + child: Text( + hostDisplayName, + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.accentBlue, ), ), ), ), - ], - ), - ), - Padding( - padding: textPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (urlAttachment.title != null) - Text( - urlAttachment.title!.trim(), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith(fontWeight: FontWeight.w700), - ), - if (urlAttachment.text != null) - Text( - urlAttachment.text!, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith(fontWeight: FontWeight.w400), - ), + ), ], ), ), - ], - ), - ); + Padding( + padding: textPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (urlAttachment.title != null) + Text( + urlAttachment.title!.trim(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: chatThemeData.textTheme.body + .copyWith(fontWeight: FontWeight.w700), + ), + if (urlAttachment.text != null) + Text( + urlAttachment.text!, + style: chatThemeData.textTheme.body + .copyWith(fontWeight: FontWeight.w400), + ), + ], + ), + ), + ], + ), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/user_avatar.dart b/packages/stream_chat_flutter/lib/src/user_avatar.dart index cb968527..ecf1783f 100644 --- a/packages/stream_chat_flutter/lib/src/user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/user_avatar.dart @@ -1,7 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget that displays a user avatar class UserAvatar extends StatelessWidget { @@ -95,8 +95,7 @@ class UserAvatar extends StatelessWidget { child: Container( constraints: constraints ?? streamChatTheme.ownMessageTheme.avatarTheme?.constraints, - color: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, + color: selectionColor ?? streamChatTheme.colorTheme.accentBlue, child: Padding( padding: EdgeInsets.all(selectionThickness), child: avatar, diff --git a/packages/stream_chat_flutter/lib/src/user_item.dart b/packages/stream_chat_flutter/lib/src/user_item.dart index 290612e1..e9d9f7c0 100644 --- a/packages/stream_chat_flutter/lib/src/user_item.dart +++ b/packages/stream_chat_flutter/lib/src/user_item.dart @@ -48,47 +48,52 @@ class UserItem extends StatelessWidget { final bool showLastOnline; @override - Widget build(BuildContext context) => ListTile( - onTap: () { - if (onTap != null) { - onTap!(user); + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return ListTile( + onTap: () { + if (onTap != null) { + onTap!(user); + } + }, + onLongPress: () { + if (onLongPress != null) { + onLongPress!(user); + } + }, + leading: UserAvatar( + user: user, + onTap: (user) { + if (onImageTap != null) { + onImageTap!(user); } }, - onLongPress: () { - if (onLongPress != null) { - onLongPress!(user); - } - }, - leading: UserAvatar( - user: user, - onTap: (user) { - if (onImageTap != null) { - onImageTap!(user); - } - }, - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), + constraints: const BoxConstraints.tightFor( + height: 40, + width: 40, ), - trailing: selected - ? StreamSvgIcon.checkSend( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ) - : null, - title: Text( - user.name, - style: StreamChatTheme.of(context).textTheme.bodyBold, - ), - subtitle: showLastOnline ? _buildLastActive(context) : null, - ); + ), + trailing: selected + ? StreamSvgIcon.checkSend( + color: chatThemeData.colorTheme.accentBlue, + ) + : null, + title: Text( + user.name, + style: chatThemeData.textTheme.bodyBold, + ), + subtitle: showLastOnline ? _buildLastActive(context) : null, + ); + } - Widget _buildLastActive(context) => Text( - user.online == true - ? 'Online' - : 'Last online ${Jiffy(user.lastActive).fromNow()}', - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(.5)), - ); + Widget _buildLastActive(context) { + final chatTheme = StreamChatTheme.of(context); + return Text( + user.online == true + ? 'Online' + : 'Last online ${Jiffy(user.lastActive).fromNow()}', + style: chatTheme.textTheme.footnote + .copyWith(color: chatTheme.colorTheme.black.withOpacity(.5)), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/user_list_view.dart b/packages/stream_chat_flutter/lib/src/user_list_view.dart index 4d5d202a..3b1efd04 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -295,21 +295,24 @@ class _UserListViewState extends State if (i < items.length) { final item = items[i]; return item.when( - headerItem: (header) => Container( - key: ValueKey('HEADER-$header'), - color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), - child: Text( - header, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14.5, - color: StreamChatTheme.of(context).colorTheme.grey, + headerItem: (header) { + final chatThemeData = StreamChatTheme.of(context); + return Container( + key: ValueKey('HEADER-$header'), + color: chatThemeData.colorTheme.black.withOpacity(0.05), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + child: Text( + header, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14.5, + color: chatThemeData.colorTheme.grey, + ), ), ), - ), - ), + ); + }, userItem: (user) { final selected = widget.selectedUsers?.contains(user) ?? false; return Container( diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index cdd87e2d..ede3e2bd 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -1,9 +1,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Launch URL Future launchURL(BuildContext context, String? url) async { @@ -27,89 +27,81 @@ Future showConfirmationDialog( Widget? icon, String? question, String? cancelText, -}) => - showModalBottomSheet( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - )), - builder: (context) { - final effect = StreamChatTheme.of(context).colorTheme.borderTop; - return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 26), - if (icon != null) icon, - const SizedBox(height: 26), +}) { + final chatThemeData = StreamChatTheme.of(context); + return showModalBottomSheet( + backgroundColor: chatThemeData.colorTheme.white, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + )), + builder: (context) { + final effect = chatThemeData.colorTheme.borderTop; + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 26), + if (icon != null) icon, + const SizedBox(height: 26), + Text( + title, + style: chatThemeData.textTheme.headlineBold, + ), + const SizedBox(height: 7), + if (question != null) Text( - title, - style: StreamChatTheme.of(context).textTheme.headlineBold, + question, + textAlign: TextAlign.center, ), - const SizedBox(height: 7), - if (question != null) - Text( - question, - textAlign: TextAlign.center, - ), - const SizedBox(height: 36), - Container( - color: effect.color!.withOpacity(effect.alpha ?? 1), - height: 1, - ), - Row( - children: [ - if (cancelText != null) - Flexible( - child: Container( - alignment: Alignment.center, - child: TextButton( - onPressed: () { - Navigator.of(context).pop(false); - }, - child: Text( - cancelText, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5)), - ), - ), - ), - ), + const SizedBox(height: 36), + Container( + color: effect.color!.withOpacity(effect.alpha ?? 1), + height: 1, + ), + Row( + children: [ + if (cancelText != null) Flexible( child: Container( alignment: Alignment.center, child: TextButton( onPressed: () { - Navigator.pop(context, true); + Navigator.of(context).pop(false); }, child: Text( - okText, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentRed), + cancelText, + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.black + .withOpacity(0.5)), ), ), ), ), - ], - ), - ], - ), - ); - }); + Flexible( + child: Container( + alignment: Alignment.center, + child: TextButton( + onPressed: () { + Navigator.pop(context, true); + }, + child: Text( + okText, + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.accentRed), + ), + ), + ), + ), + ], + ), + ], + ), + ); + }); +} /// Shows info dialog Future showInfoDialog( @@ -119,63 +111,64 @@ Future showInfoDialog( Widget? icon, String? details, StreamChatThemeData? theme, -}) => - showModalBottomSheet( - backgroundColor: theme?.colorTheme.white ?? - StreamChatTheme.of(context).colorTheme.white, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - )), - builder: (context) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox( - height: 26, - ), - if (icon != null) icon, - const SizedBox( - height: 26, - ), - Text( - title, - style: theme?.textTheme.headlineBold ?? - StreamChatTheme.of(context).textTheme.headlineBold, - ), - const SizedBox( - height: 7, - ), - if (details != null) Text(details), - const SizedBox( - height: 36, - ), - Container( - color: theme?.colorTheme.black.withOpacity(.08) ?? - StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), - height: 1, - ), - Center( - child: TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - okText, - style: TextStyle( - color: theme?.colorTheme.black.withOpacity(0.5) ?? - StreamChatTheme.of(context).colorTheme.accentBlue, - fontWeight: FontWeight.w400, - ), +}) { + final chatThemeData = StreamChatTheme.of(context); + return showModalBottomSheet( + backgroundColor: theme?.colorTheme.white ?? chatThemeData.colorTheme.white, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + )), + builder: (context) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 26, + ), + if (icon != null) icon, + const SizedBox( + height: 26, + ), + Text( + title, + style: theme?.textTheme.headlineBold ?? + chatThemeData.textTheme.headlineBold, + ), + const SizedBox( + height: 7, + ), + if (details != null) Text(details), + const SizedBox( + height: 36, + ), + Container( + color: theme?.colorTheme.black.withOpacity(.08) ?? + chatThemeData.colorTheme.black.withOpacity(.08), + height: 1, + ), + Center( + child: TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + okText, + style: TextStyle( + color: theme?.colorTheme.black.withOpacity(0.5) ?? + chatThemeData.colorTheme.accentBlue, + fontWeight: FontWeight.w400, ), ), ), - ], - ), + ), + ], ), - ); + ), + ); +} /// Get random png with initials String getRandomPicUrl(User user) =>