Merge pull request #1524 from GetStream/fix/own-message-border-color
@@ -997,26 +997,24 @@ class Channel {
|
|||||||
) async {
|
) async {
|
||||||
final type = reaction.type;
|
final type = reaction.type;
|
||||||
|
|
||||||
final reactionCounts = {...message.reactionCounts ?? <String, int>{}};
|
final reactionCounts = {...?message.reactionCounts};
|
||||||
if (reactionCounts.containsKey(type)) {
|
if (reactionCounts.containsKey(type)) {
|
||||||
reactionCounts.update(type, (value) => value - 1);
|
reactionCounts.update(type, (value) => value - 1);
|
||||||
}
|
}
|
||||||
final reactionScores = {...message.reactionScores ?? <String, int>{}};
|
final reactionScores = {...?message.reactionScores};
|
||||||
if (reactionScores.containsKey(type)) {
|
if (reactionScores.containsKey(type)) {
|
||||||
reactionScores.update(type, (value) => value - 1);
|
reactionScores.update(type, (value) => value - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
final latestReactions = [...message.latestReactions ?? <Reaction>[]]
|
final latestReactions = [...?message.latestReactions]..removeWhere((r) =>
|
||||||
..removeWhere((r) =>
|
r.userId == reaction.userId &&
|
||||||
r.userId == reaction.userId &&
|
r.type == reaction.type &&
|
||||||
r.type == reaction.type &&
|
r.messageId == reaction.messageId);
|
||||||
r.messageId == reaction.messageId);
|
|
||||||
|
|
||||||
final ownReactions = message.ownReactions
|
final ownReactions = [...?message.ownReactions]..removeWhere((r) =>
|
||||||
?..removeWhere((r) =>
|
r.userId == reaction.userId &&
|
||||||
r.userId == reaction.userId &&
|
r.type == reaction.type &&
|
||||||
r.type == reaction.type &&
|
r.messageId == reaction.messageId);
|
||||||
r.messageId == reaction.messageId);
|
|
||||||
|
|
||||||
final newMessage = message.copyWith(
|
final newMessage = message.copyWith(
|
||||||
reactionCounts: reactionCounts..removeWhere((_, value) => value == 0),
|
reactionCounts: reactionCounts..removeWhere((_, value) => value == 0),
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
works only for otherUser.
|
works only for otherUser.
|
||||||
- [[#1490]](https://github.com/GetStream/stream-chat-flutter/issues/1490) Fixed `editMessageInputBuilder` property not
|
- [[#1490]](https://github.com/GetStream/stream-chat-flutter/issues/1490) Fixed `editMessageInputBuilder` property not
|
||||||
used in message edit widget.
|
used in message edit widget.
|
||||||
|
- [[#1523]](https://github.com/GetStream/stream-chat-flutter/issues/1523) Fixed `StreamMessageThemeData` not being
|
||||||
|
applied correctly.
|
||||||
|
|
||||||
✅ Added
|
✅ Added
|
||||||
|
|
||||||
|
|||||||
@@ -893,6 +893,11 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
|
|||||||
final currentUserMember =
|
final currentUserMember =
|
||||||
members.firstWhereOrNull((e) => e.user!.id == currentUser!.id);
|
members.firstWhereOrNull((e) => e.user!.id == currentUser!.id);
|
||||||
|
|
||||||
|
final hasUrlAttachment =
|
||||||
|
message.attachments.any((it) => it.ogScrapeUrl != null);
|
||||||
|
|
||||||
|
final borderSide = isOnlyEmoji || hasUrlAttachment ? BorderSide.none : null;
|
||||||
|
|
||||||
final defaultMessageWidget = StreamMessageWidget(
|
final defaultMessageWidget = StreamMessageWidget(
|
||||||
showReplyMessage: false,
|
showReplyMessage: false,
|
||||||
showResendMessage: false,
|
showResendMessage: false,
|
||||||
@@ -917,7 +922,7 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
|
|||||||
vertical: 8,
|
vertical: 8,
|
||||||
horizontal: isOnlyEmoji ? 0 : 16.0,
|
horizontal: isOnlyEmoji ? 0 : 16.0,
|
||||||
),
|
),
|
||||||
borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null,
|
borderSide: borderSide,
|
||||||
showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show,
|
showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show,
|
||||||
messageTheme: isMyMessage
|
messageTheme: isMyMessage
|
||||||
? _streamTheme.ownMessageTheme
|
? _streamTheme.ownMessageTheme
|
||||||
@@ -1096,10 +1101,7 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
|
|||||||
final hasUrlAttachment =
|
final hasUrlAttachment =
|
||||||
message.attachments.any((it) => it.ogScrapeUrl != null);
|
message.attachments.any((it) => it.ogScrapeUrl != null);
|
||||||
|
|
||||||
final borderSide =
|
final borderSide = isOnlyEmoji || hasUrlAttachment ? BorderSide.none : null;
|
||||||
isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment)
|
|
||||||
? BorderSide.none
|
|
||||||
: null;
|
|
||||||
|
|
||||||
final currentUser = StreamChat.of(context).currentUser;
|
final currentUser = StreamChat.of(context).currentUser;
|
||||||
final members = StreamChannel.of(context).channel.state?.members ?? [];
|
final members = StreamChannel.of(context).channel.state?.members ?? [];
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ class StreamDeletedMessage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final chatThemeData = StreamChatTheme.of(context);
|
|
||||||
return Material(
|
return Material(
|
||||||
color: messageTheme.messageBackgroundColor,
|
color: messageTheme.messageBackgroundColor,
|
||||||
shape: shape ??
|
shape: shape ??
|
||||||
@@ -40,9 +39,7 @@ class StreamDeletedMessage extends StatelessWidget {
|
|||||||
borderRadius: borderRadiusGeometry ?? BorderRadius.zero,
|
borderRadius: borderRadiusGeometry ?? BorderRadius.zero,
|
||||||
side: borderSide ??
|
side: borderSide ??
|
||||||
BorderSide(
|
BorderSide(
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: messageTheme.messageBorderColor ?? Colors.transparent,
|
||||||
? chatThemeData.colorTheme.barsBg.withAlpha(24)
|
|
||||||
: chatThemeData.colorTheme.textHighEmphasis.withAlpha(24),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|||||||
@@ -139,7 +139,8 @@ class _MessageCardState extends State<MessageCard> {
|
|||||||
RoundedRectangleBorder(
|
RoundedRectangleBorder(
|
||||||
side: widget.borderSide ??
|
side: widget.borderSide ??
|
||||||
BorderSide(
|
BorderSide(
|
||||||
color: widget.messageTheme.messageBorderColor ?? Colors.grey,
|
color: widget.messageTheme.messageBorderColor ??
|
||||||
|
Colors.transparent,
|
||||||
),
|
),
|
||||||
borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero,
|
borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_portal/flutter_portal.dart';
|
import 'package:flutter_portal/flutter_portal.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_card.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
// ignore_for_file: cascade_invocations
|
// ignore_for_file: cascade_invocations
|
||||||
@@ -75,6 +76,7 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final streamChat = StreamChat.of(context);
|
final streamChat = StreamChat.of(context);
|
||||||
|
final currentUser = streamChat.currentUser!;
|
||||||
final reactionIcons = StreamChatConfiguration.of(context).reactionIcons;
|
final reactionIcons = StreamChatConfiguration.of(context).reactionIcons;
|
||||||
final streamChatTheme = StreamChatTheme.of(context);
|
final streamChatTheme = StreamChatTheme.of(context);
|
||||||
|
|
||||||
@@ -83,13 +85,13 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
|
|||||||
if (widget.shouldShowReactions) {
|
if (widget.shouldShowReactions) {
|
||||||
widget.message.latestReactions?.forEach((element) {
|
widget.message.latestReactions?.forEach((element) {
|
||||||
if (!reactionsMap.containsKey(element.type) ||
|
if (!reactionsMap.containsKey(element.type) ||
|
||||||
element.user!.id == streamChat.currentUser?.id) {
|
element.user!.id == currentUser.id) {
|
||||||
reactionsMap[element.type] = element;
|
reactionsMap[element.type] = element;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
reactionsList = reactionsMap.values.toList()
|
reactionsList = reactionsMap.values.toList()
|
||||||
..sort((a, b) => a.user!.id == streamChat.currentUser?.id ? 1 : -1);
|
..sort((a, b) => a.user!.id == currentUser.id ? 1 : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PortalTarget(
|
return PortalTarget(
|
||||||
@@ -114,44 +116,10 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
|
|||||||
maxWidth: 336,
|
maxWidth: 336,
|
||||||
maxHeight: 342,
|
maxHeight: 342,
|
||||||
),
|
),
|
||||||
child: Card(
|
child: ReactionsCard(
|
||||||
color: streamChatTheme.colorTheme.barsBg,
|
currentUser: currentUser,
|
||||||
shape: RoundedRectangleBorder(
|
message: widget.message,
|
||||||
borderRadius: BorderRadius.circular(16),
|
messageTheme: widget.messageTheme,
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Text(
|
|
||||||
'''${widget.message.latestReactions!.length} ${context.translations.messageReactionsLabel}''',
|
|
||||||
style: streamChatTheme.textTheme.headlineBold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Wrap(
|
|
||||||
spacing: 16,
|
|
||||||
runSpacing: 16,
|
|
||||||
children: [
|
|
||||||
...widget.message.latestReactions!.map((reaction) {
|
|
||||||
final reactionIcon = reactionIcons.firstWhereOrNull(
|
|
||||||
(r) => r.type == reaction.type,
|
|
||||||
);
|
|
||||||
return _StackedReaction(
|
|
||||||
reaction: reaction,
|
|
||||||
streamChatTheme: streamChatTheme,
|
|
||||||
reactionIcon: reactionIcon,
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -163,23 +131,32 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
|
|||||||
onExit: (event) {
|
onExit: (event) {
|
||||||
setState(() => _showReactionsPopup = !_showReactionsPopup);
|
setState(() => _showReactionsPopup = !_showReactionsPopup);
|
||||||
},
|
},
|
||||||
child: Wrap(
|
child: Padding(
|
||||||
children: [
|
padding: EdgeInsets.symmetric(
|
||||||
...reactionsList.map((reaction) {
|
vertical: 2,
|
||||||
final reactionIcon = reactionIcons.firstWhereOrNull(
|
horizontal: widget.reverse ? 0 : 4,
|
||||||
(r) => r.type == reaction.type,
|
),
|
||||||
);
|
child: Wrap(
|
||||||
|
spacing: 4,
|
||||||
|
runSpacing: 4,
|
||||||
|
children: [
|
||||||
|
...reactionsList.map((reaction) {
|
||||||
|
final reactionIcon = reactionIcons.firstWhereOrNull(
|
||||||
|
(r) => r.type == reaction.type,
|
||||||
|
);
|
||||||
|
|
||||||
return _BottomReaction(
|
return _BottomReaction(
|
||||||
reaction: reaction,
|
currentUser: currentUser,
|
||||||
message: widget.message,
|
reaction: reaction,
|
||||||
borderSide: widget.borderSide,
|
message: widget.message,
|
||||||
messageTheme: widget.messageTheme,
|
borderSide: widget.borderSide,
|
||||||
reactionIcon: reactionIcon,
|
messageTheme: widget.messageTheme,
|
||||||
streamChatTheme: streamChatTheme,
|
reactionIcon: reactionIcon,
|
||||||
);
|
streamChatTheme: streamChatTheme,
|
||||||
}).toList(),
|
);
|
||||||
],
|
}).toList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -188,6 +165,7 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
|
|||||||
|
|
||||||
class _BottomReaction extends StatelessWidget {
|
class _BottomReaction extends StatelessWidget {
|
||||||
const _BottomReaction({
|
const _BottomReaction({
|
||||||
|
required this.currentUser,
|
||||||
required this.reaction,
|
required this.reaction,
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.borderSide,
|
required this.borderSide,
|
||||||
@@ -196,6 +174,7 @@ class _BottomReaction extends StatelessWidget {
|
|||||||
required this.streamChatTheme,
|
required this.streamChatTheme,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final User currentUser;
|
||||||
final Reaction reaction;
|
final Reaction reaction;
|
||||||
final Message message;
|
final Message message;
|
||||||
final BorderSide? borderSide;
|
final BorderSide? borderSide;
|
||||||
@@ -205,7 +184,10 @@ class _BottomReaction extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final userId = StreamChat.of(context).currentUser?.id;
|
final userId = currentUser.id;
|
||||||
|
|
||||||
|
final backgroundColor = messageTheme?.reactionsBackgroundColor;
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -225,37 +207,38 @@ class _BottomReaction extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Card(
|
child: Card(
|
||||||
shape: StadiumBorder(
|
margin: EdgeInsets.zero,
|
||||||
|
// Setting elevation as null when background color is transparent.
|
||||||
|
// This is done to avoid shadow when background color is transparent.
|
||||||
|
elevation: backgroundColor == Colors.transparent ? 0 : null,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
side: borderSide ??
|
side: borderSide ??
|
||||||
BorderSide(
|
BorderSide(
|
||||||
color: messageTheme?.messageBorderColor ?? Colors.grey,
|
color: messageTheme?.reactionsBorderColor ?? Colors.transparent,
|
||||||
),
|
),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
color: messageTheme?.messageBackgroundColor,
|
color: backgroundColor,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
horizontal: 6,
|
|
||||||
vertical: 2,
|
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: BoxConstraints.tight(
|
constraints: BoxConstraints.tight(
|
||||||
const Size.square(16),
|
const Size.square(14),
|
||||||
),
|
),
|
||||||
child: reactionIcon?.builder(
|
child: reactionIcon?.builder(
|
||||||
context,
|
context,
|
||||||
reaction.user?.id == userId,
|
reaction.user?.id == userId,
|
||||||
16,
|
14,
|
||||||
) ??
|
) ??
|
||||||
Icon(
|
Icon(
|
||||||
Icons.help_outline_rounded,
|
Icons.help_outline_rounded,
|
||||||
size: 16,
|
size: 14,
|
||||||
color: reaction.user?.id == userId
|
color: reaction.user?.id == userId
|
||||||
? streamChatTheme.colorTheme.accentPrimary
|
? streamChatTheme.colorTheme.accentPrimary
|
||||||
: streamChatTheme.colorTheme.textHighEmphasis
|
: streamChatTheme.colorTheme.textLowEmphasis,
|
||||||
.withOpacity(0.5),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
@@ -280,87 +263,3 @@ class _BottomReaction extends StatelessWidget {
|
|||||||
properties.add(DiagnosticsProperty<Message>('message', message));
|
properties.add(DiagnosticsProperty<Message>('message', message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _StackedReaction extends StatelessWidget {
|
|
||||||
const _StackedReaction({
|
|
||||||
required this.reaction,
|
|
||||||
required this.streamChatTheme,
|
|
||||||
required this.reactionIcon,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Reaction reaction;
|
|
||||||
final StreamChatThemeData streamChatTheme;
|
|
||||||
final StreamReactionIcon? reactionIcon;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final userId = StreamChat.of(context).currentUser?.id;
|
|
||||||
return SizedBox(
|
|
||||||
width: 80,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Stack(
|
|
||||||
children: [
|
|
||||||
StreamUserAvatar(
|
|
||||||
user: reaction.user!,
|
|
||||||
constraints: const BoxConstraints.tightFor(
|
|
||||||
height: 64,
|
|
||||||
width: 64,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(32),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
child: DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: streamChatTheme.colorTheme.inputBg,
|
|
||||||
border: Border.all(
|
|
||||||
color: streamChatTheme.colorTheme.barsBg,
|
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: reactionIcon?.builder(
|
|
||||||
context,
|
|
||||||
reaction.userId == userId,
|
|
||||||
16,
|
|
||||||
) ??
|
|
||||||
Icon(
|
|
||||||
Icons.help_outline_rounded,
|
|
||||||
size: 16,
|
|
||||||
color: reaction.user?.id == userId
|
|
||||||
? streamChatTheme.colorTheme.accentPrimary
|
|
||||||
: streamChatTheme.colorTheme.textHighEmphasis
|
|
||||||
.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
userId == reaction.user!.name ? 'You' : reaction.user!.name,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
||||||
super.debugFillProperties(properties);
|
|
||||||
properties.add(
|
|
||||||
DiagnosticsProperty<Reaction>('reaction', reaction),
|
|
||||||
);
|
|
||||||
properties.add(
|
|
||||||
DiagnosticsProperty<StreamReactionIcon?>(
|
|
||||||
'reactionIcon',
|
|
||||||
reactionIcon,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_widget/reactions/reaction_bubble.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_align.dart';
|
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_align.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_card.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
/// {@template streamMessageReactionsModal}
|
/// {@template streamMessageReactionsModal}
|
||||||
@@ -83,9 +83,10 @@ class StreamMessageReactionsModal extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
if (message.latestReactions?.isNotEmpty == true) ...[
|
if (message.latestReactions?.isNotEmpty == true) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildReactionCard(
|
ReactionsCard(
|
||||||
context,
|
currentUser: user!,
|
||||||
user,
|
message: message,
|
||||||
|
messageTheme: messageTheme,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -126,109 +127,4 @@ class StreamMessageReactionsModal extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildReactionCard(BuildContext context, User? user) {
|
|
||||||
final chatThemeData = StreamChatTheme.of(context);
|
|
||||||
return Card(
|
|
||||||
color: chatThemeData.colorTheme.barsBg,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
context.translations.messageReactionsLabel,
|
|
||||||
style: chatThemeData.textTheme.headlineBold,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Flexible(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Wrap(
|
|
||||||
spacing: 16,
|
|
||||||
runSpacing: 16,
|
|
||||||
children: message.latestReactions!
|
|
||||||
.map((e) => _buildReaction(
|
|
||||||
e,
|
|
||||||
user!,
|
|
||||||
context,
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildReaction(
|
|
||||||
Reaction reaction,
|
|
||||||
User currentUser,
|
|
||||||
BuildContext context,
|
|
||||||
) {
|
|
||||||
final isCurrentUser = reaction.user?.id == currentUser.id;
|
|
||||||
final chatThemeData = StreamChatTheme.of(context);
|
|
||||||
return ConstrainedBox(
|
|
||||||
constraints: BoxConstraints.loose(
|
|
||||||
const Size(64, 100),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Stack(
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
children: [
|
|
||||||
StreamUserAvatar(
|
|
||||||
onTap: onUserAvatarTap,
|
|
||||||
user: reaction.user!,
|
|
||||||
constraints: const BoxConstraints.tightFor(
|
|
||||||
height: 64,
|
|
||||||
width: 64,
|
|
||||||
),
|
|
||||||
onlineIndicatorConstraints: const BoxConstraints.tightFor(
|
|
||||||
height: 12,
|
|
||||||
width: 12,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(32),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
bottom: 6,
|
|
||||||
left: isCurrentUser ? -3 : null,
|
|
||||||
right: isCurrentUser ? -3 : null,
|
|
||||||
child: Align(
|
|
||||||
alignment:
|
|
||||||
reverse ? Alignment.centerRight : Alignment.centerLeft,
|
|
||||||
child: StreamReactionBubble(
|
|
||||||
reactions: [reaction],
|
|
||||||
flipTail: !reverse,
|
|
||||||
borderColor:
|
|
||||||
messageTheme.reactionsBorderColor ?? Colors.transparent,
|
|
||||||
backgroundColor: messageTheme.reactionsBackgroundColor ??
|
|
||||||
Colors.transparent,
|
|
||||||
maskColor: chatThemeData.colorTheme.barsBg,
|
|
||||||
tailCirclesSpacing: 1,
|
|
||||||
highlightOwnReactions: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
reaction.user!.name.split(' ')[0],
|
|
||||||
style: chatThemeData.textTheme.footnoteBold,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,24 +124,22 @@ class StreamReactionBubble extends StatelessWidget {
|
|||||||
final chatThemeData = StreamChatTheme.of(context);
|
final chatThemeData = StreamChatTheme.of(context);
|
||||||
final userId = StreamChat.of(context).currentUser?.id;
|
final userId = StreamChat.of(context).currentUser?.id;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
horizontal: 4,
|
|
||||||
),
|
|
||||||
child: reactionIcon != null
|
child: reactionIcon != null
|
||||||
? ConstrainedBox(
|
? ConstrainedBox(
|
||||||
constraints: BoxConstraints.tight(const Size.square(16)),
|
constraints: BoxConstraints.tight(const Size.square(14)),
|
||||||
child: reactionIcon.builder(
|
child: reactionIcon.builder(
|
||||||
context,
|
context,
|
||||||
!highlightOwnReactions || reaction.user?.id == userId,
|
highlightOwnReactions && reaction.user?.id == userId,
|
||||||
16,
|
16,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Icon(
|
: Icon(
|
||||||
Icons.help_outline_rounded,
|
Icons.help_outline_rounded,
|
||||||
size: 16,
|
size: 14,
|
||||||
color: (!highlightOwnReactions || reaction.user?.id == userId)
|
color: (highlightOwnReactions && reaction.user?.id == userId)
|
||||||
? chatThemeData.colorTheme.accentPrimary
|
? chatThemeData.colorTheme.accentPrimary
|
||||||
: chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.5),
|
: chatThemeData.colorTheme.textLowEmphasis,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/avatars/user_avatar.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/message_widget/reactions/reaction_bubble.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/theme/message_theme.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/utils/extensions.dart';
|
||||||
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
|
||||||
|
/// {@template reactionsCard}
|
||||||
|
/// A card that displays the reactions to a message.
|
||||||
|
///
|
||||||
|
/// Used in [StreamMessageReactionsModal] and [DesktopReactionsBuilder].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class ReactionsCard extends StatelessWidget {
|
||||||
|
/// {@macro reactionsCard}
|
||||||
|
const ReactionsCard({
|
||||||
|
super.key,
|
||||||
|
required this.currentUser,
|
||||||
|
required this.message,
|
||||||
|
required this.messageTheme,
|
||||||
|
this.onUserAvatarTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Current logged in user.
|
||||||
|
final User currentUser;
|
||||||
|
|
||||||
|
/// Message to display reactions of.
|
||||||
|
final Message message;
|
||||||
|
|
||||||
|
/// [StreamMessageThemeData] to apply to [message].
|
||||||
|
final StreamMessageThemeData messageTheme;
|
||||||
|
|
||||||
|
/// {@macro onUserAvatarTap}
|
||||||
|
final void Function(User)? onUserAvatarTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final chatThemeData = StreamChatTheme.of(context);
|
||||||
|
return Card(
|
||||||
|
color: chatThemeData.colorTheme.barsBg,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.translations.messageReactionsLabel,
|
||||||
|
style: chatThemeData.textTheme.headlineBold,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Flexible(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 16,
|
||||||
|
runSpacing: 16,
|
||||||
|
children: message.latestReactions!
|
||||||
|
.map((e) => _buildReaction(
|
||||||
|
e,
|
||||||
|
currentUser,
|
||||||
|
context,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildReaction(
|
||||||
|
Reaction reaction,
|
||||||
|
User currentUser,
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
final isCurrentUser = reaction.user?.id == currentUser.id;
|
||||||
|
final chatThemeData = StreamChatTheme.of(context);
|
||||||
|
final reverse = !isCurrentUser;
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: BoxConstraints.loose(
|
||||||
|
const Size(64, 100),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
StreamUserAvatar(
|
||||||
|
onTap: onUserAvatarTap,
|
||||||
|
user: reaction.user!,
|
||||||
|
constraints: const BoxConstraints.tightFor(
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
),
|
||||||
|
onlineIndicatorConstraints: const BoxConstraints.tightFor(
|
||||||
|
height: 12,
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(32),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 6,
|
||||||
|
left: !reverse ? -3 : null,
|
||||||
|
right: reverse ? -3 : null,
|
||||||
|
child: Align(
|
||||||
|
alignment:
|
||||||
|
reverse ? Alignment.centerRight : Alignment.centerLeft,
|
||||||
|
child: StreamReactionBubble(
|
||||||
|
reactions: [reaction],
|
||||||
|
reverse: !reverse,
|
||||||
|
flipTail: !reverse,
|
||||||
|
borderColor:
|
||||||
|
messageTheme.reactionsBorderColor ?? Colors.transparent,
|
||||||
|
backgroundColor: messageTheme.reactionsBackgroundColor ??
|
||||||
|
Colors.transparent,
|
||||||
|
maskColor: chatThemeData.colorTheme.barsBg,
|
||||||
|
tailCirclesSpacing: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
reaction.user!.name.split(' ')[0],
|
||||||
|
style: chatThemeData.textTheme.footnoteBold,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// {@template reactionIconBuilder}
|
||||||
|
/// Signature for a function that builds a reaction icon.
|
||||||
|
/// {@endtemplate}
|
||||||
|
typedef ReactionIconBuilder = Widget Function(
|
||||||
|
BuildContext context,
|
||||||
|
bool isHighlighted,
|
||||||
|
double iconSize,
|
||||||
|
);
|
||||||
|
|
||||||
/// {@template streamReactionIcon}
|
/// {@template streamReactionIcon}
|
||||||
/// Reaction icon data
|
/// Reaction icon data
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
@@ -13,10 +22,6 @@ class StreamReactionIcon {
|
|||||||
/// Type of reaction
|
/// Type of reaction
|
||||||
final String type;
|
final String type;
|
||||||
|
|
||||||
/// Asset to display for reaction
|
/// {@macro reactionIconBuilder}
|
||||||
final Widget Function(
|
final ReactionIconBuilder builder;
|
||||||
BuildContext,
|
|
||||||
bool highlighted,
|
|
||||||
double size,
|
|
||||||
) builder;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ class StreamChatConfigurationData {
|
|||||||
return StreamSvgIcon.loveReaction(
|
return StreamSvgIcon.loveReaction(
|
||||||
color: highlighted
|
color: highlighted
|
||||||
? theme.colorTheme.accentPrimary
|
? theme.colorTheme.accentPrimary
|
||||||
: theme.primaryIconTheme.color!.withOpacity(0.5),
|
: theme.primaryIconTheme.color,
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -177,7 +177,7 @@ class StreamChatConfigurationData {
|
|||||||
return StreamSvgIcon.thumbsUpReaction(
|
return StreamSvgIcon.thumbsUpReaction(
|
||||||
color: highlighted
|
color: highlighted
|
||||||
? theme.colorTheme.accentPrimary
|
? theme.colorTheme.accentPrimary
|
||||||
: theme.primaryIconTheme.color!.withOpacity(0.5),
|
: theme.primaryIconTheme.color,
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -189,7 +189,7 @@ class StreamChatConfigurationData {
|
|||||||
return StreamSvgIcon.thumbsDownReaction(
|
return StreamSvgIcon.thumbsDownReaction(
|
||||||
color: highlighted
|
color: highlighted
|
||||||
? theme.colorTheme.accentPrimary
|
? theme.colorTheme.accentPrimary
|
||||||
: theme.primaryIconTheme.color!.withOpacity(0.5),
|
: theme.primaryIconTheme.color,
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -201,7 +201,7 @@ class StreamChatConfigurationData {
|
|||||||
return StreamSvgIcon.lolReaction(
|
return StreamSvgIcon.lolReaction(
|
||||||
color: highlighted
|
color: highlighted
|
||||||
? theme.colorTheme.accentPrimary
|
? theme.colorTheme.accentPrimary
|
||||||
: theme.primaryIconTheme.color!.withOpacity(0.5),
|
: theme.primaryIconTheme.color,
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -213,7 +213,7 @@ class StreamChatConfigurationData {
|
|||||||
return StreamSvgIcon.wutReaction(
|
return StreamSvgIcon.wutReaction(
|
||||||
color: highlighted
|
color: highlighted
|
||||||
? theme.colorTheme.accentPrimary
|
? theme.colorTheme.accentPrimary
|
||||||
: theme.primaryIconTheme.color!.withOpacity(0.5),
|
: theme.primaryIconTheme.color,
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -126,8 +126,7 @@ class StreamChatThemeData {
|
|||||||
StreamTextTheme textTheme,
|
StreamTextTheme textTheme,
|
||||||
) {
|
) {
|
||||||
final accentColor = colorTheme.accentPrimary;
|
final accentColor = colorTheme.accentPrimary;
|
||||||
final iconTheme =
|
final iconTheme = IconThemeData(color: colorTheme.textLowEmphasis);
|
||||||
IconThemeData(color: colorTheme.textHighEmphasis.withOpacity(0.5));
|
|
||||||
final channelHeaderTheme = StreamChannelHeaderThemeData(
|
final channelHeaderTheme = StreamChannelHeaderThemeData(
|
||||||
avatarTheme: StreamAvatarThemeData(
|
avatarTheme: StreamAvatarThemeData(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
@@ -184,11 +183,11 @@ class StreamChatThemeData {
|
|||||||
createdAtStyle:
|
createdAtStyle:
|
||||||
textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis),
|
textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis),
|
||||||
repliesStyle: textTheme.footnoteBold.copyWith(color: accentColor),
|
repliesStyle: textTheme.footnoteBold.copyWith(color: accentColor),
|
||||||
messageBackgroundColor: colorTheme.disabled,
|
messageBackgroundColor: colorTheme.borders,
|
||||||
|
messageBorderColor: colorTheme.borders,
|
||||||
reactionsBackgroundColor: colorTheme.barsBg,
|
reactionsBackgroundColor: colorTheme.barsBg,
|
||||||
reactionsBorderColor: colorTheme.borders,
|
reactionsBorderColor: colorTheme.borders,
|
||||||
reactionsMaskColor: colorTheme.appBg,
|
reactionsMaskColor: colorTheme.appBg,
|
||||||
messageBorderColor: colorTheme.disabled,
|
|
||||||
avatarTheme: StreamAvatarThemeData(
|
avatarTheme: StreamAvatarThemeData(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
constraints: const BoxConstraints.tightFor(
|
constraints: const BoxConstraints.tightFor(
|
||||||
@@ -207,8 +206,8 @@ class StreamChatThemeData {
|
|||||||
textTheme.body.copyWith(fontWeight: FontWeight.w400),
|
textTheme.body.copyWith(fontWeight: FontWeight.w400),
|
||||||
),
|
),
|
||||||
otherMessageTheme: StreamMessageThemeData(
|
otherMessageTheme: StreamMessageThemeData(
|
||||||
reactionsBackgroundColor: colorTheme.disabled,
|
reactionsBackgroundColor: colorTheme.borders,
|
||||||
reactionsBorderColor: colorTheme.barsBg,
|
reactionsBorderColor: colorTheme.borders,
|
||||||
reactionsMaskColor: colorTheme.appBg,
|
reactionsMaskColor: colorTheme.appBg,
|
||||||
messageTextStyle: textTheme.body,
|
messageTextStyle: textTheme.body,
|
||||||
createdAtStyle:
|
createdAtStyle:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
|||||||
bool get isWeb => CurrentPlatform.isWeb;
|
bool get isWeb => CurrentPlatform.isWeb;
|
||||||
|
|
||||||
/// Returns true if the app is running in a mobile device.
|
/// Returns true if the app is running in a mobile device.
|
||||||
bool get isMobileDevice => CurrentPlatform.isIos || CurrentPlatform.isAndroid;
|
bool get isMobileDevice => true;
|
||||||
|
|
||||||
/// Returns true if the app is running in a desktop device.
|
/// Returns true if the app is running in a desktop device.
|
||||||
bool get isDesktopDevice =>
|
bool get isDesktopDevice =>
|
||||||
@@ -22,7 +22,7 @@ bool get isDesktopVideoPlayerSupported =>
|
|||||||
bool get isMobileDeviceOrWeb => isWeb || isMobileDevice;
|
bool get isMobileDeviceOrWeb => isWeb || isMobileDevice;
|
||||||
|
|
||||||
/// Returns true if the app is running in a desktop or web.
|
/// Returns true if the app is running in a desktop or web.
|
||||||
bool get isDesktopDeviceOrWeb => isWeb || isDesktopDevice;
|
bool get isDesktopDeviceOrWeb => false;
|
||||||
|
|
||||||
/// Returns true if the app is running in a flutter test environment.
|
/// Returns true if the app is running in a flutter test environment.
|
||||||
bool get isTestEnvironment => CurrentPlatform.isFlutterTest;
|
bool get isTestEnvironment => CurrentPlatform.isFlutterTest;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |