fix: fix reaction alignment and theming.

Signed-off-by: xsahil03x <xdsahil@gmail.com>
This commit is contained in:
Sahil Kumar
2023-05-08 20:27:39 +05:30
committed by xsahil03x
parent 5ace429216
commit 161057ef4c
7 changed files with 187 additions and 259 deletions
@@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.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';
// ignore_for_file: cascade_invocations
@@ -75,6 +76,7 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
@override
Widget build(BuildContext context) {
final streamChat = StreamChat.of(context);
final currentUser = streamChat.currentUser!;
final reactionIcons = StreamChatConfiguration.of(context).reactionIcons;
final streamChatTheme = StreamChatTheme.of(context);
@@ -83,13 +85,13 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
if (widget.shouldShowReactions) {
widget.message.latestReactions?.forEach((element) {
if (!reactionsMap.containsKey(element.type) ||
element.user!.id == streamChat.currentUser?.id) {
element.user!.id == currentUser.id) {
reactionsMap[element.type] = element;
}
});
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(
@@ -114,44 +116,10 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
maxWidth: 336,
maxHeight: 342,
),
child: Card(
color: streamChatTheme.colorTheme.barsBg,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
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(),
],
),
),
),
],
),
child: ReactionsCard(
currentUser: currentUser,
message: widget.message,
messageTheme: widget.messageTheme,
),
),
),
@@ -164,7 +132,10 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
setState(() => _showReactionsPopup = !_showReactionsPopup);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
padding: EdgeInsets.symmetric(
vertical: 2,
horizontal: widget.reverse ? 0 : 4,
),
child: Wrap(
spacing: 4,
runSpacing: 4,
@@ -175,6 +146,7 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
);
return _BottomReaction(
currentUser: currentUser,
reaction: reaction,
message: widget.message,
borderSide: widget.borderSide,
@@ -193,6 +165,7 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
class _BottomReaction extends StatelessWidget {
const _BottomReaction({
required this.currentUser,
required this.reaction,
required this.message,
required this.borderSide,
@@ -201,6 +174,7 @@ class _BottomReaction extends StatelessWidget {
required this.streamChatTheme,
});
final User currentUser;
final Reaction reaction;
final Message message;
final BorderSide? borderSide;
@@ -210,7 +184,7 @@ class _BottomReaction extends StatelessWidget {
@override
Widget build(BuildContext context) {
final userId = StreamChat.of(context).currentUser?.id;
final userId = currentUser.id;
final backgroundColor = messageTheme?.reactionsBackgroundColor;
@@ -264,8 +238,7 @@ class _BottomReaction extends StatelessWidget {
size: 14,
color: reaction.user?.id == userId
? streamChatTheme.colorTheme.accentPrimary
: streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(0.5),
: streamChatTheme.colorTheme.textLowEmphasis,
),
),
const SizedBox(width: 4),
@@ -290,87 +263,3 @@ class _BottomReaction extends StatelessWidget {
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 '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_card.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// {@template streamMessageReactionsModal}
@@ -83,9 +83,10 @@ class StreamMessageReactionsModal extends StatelessWidget {
),
if (message.latestReactions?.isNotEmpty == true) ...[
const SizedBox(height: 8),
_buildReactionCard(
context,
user,
ReactionsCard(
currentUser: user!,
message: message,
messageTheme: messageTheme,
),
],
],
@@ -126,110 +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);
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,
),
],
),
);
}
}
@@ -124,24 +124,22 @@ class StreamReactionBubble extends StatelessWidget {
final chatThemeData = StreamChatTheme.of(context);
final userId = StreamChat.of(context).currentUser?.id;
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4,
),
padding: const EdgeInsets.symmetric(horizontal: 4),
child: reactionIcon != null
? ConstrainedBox(
constraints: BoxConstraints.tight(const Size.square(16)),
constraints: BoxConstraints.tight(const Size.square(14)),
child: reactionIcon.builder(
context,
!highlightOwnReactions || reaction.user?.id == userId,
highlightOwnReactions && reaction.user?.id == userId,
16,
),
)
: Icon(
Icons.help_outline_rounded,
size: 16,
color: (!highlightOwnReactions || reaction.user?.id == userId)
size: 14,
color: (highlightOwnReactions && reaction.user?.id == userId)
? 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';
/// {@template reactionIconBuilder}
/// Signature for a function that builds a reaction icon.
/// {@endtemplate}
typedef ReactionIconBuilder = Widget Function(
BuildContext context,
bool isHighlighted,
double iconSize,
);
/// {@template streamReactionIcon}
/// Reaction icon data
/// {@endtemplate}
@@ -13,10 +22,6 @@ class StreamReactionIcon {
/// Type of reaction
final String type;
/// Asset to display for reaction
final Widget Function(
BuildContext,
bool highlighted,
double size,
) builder;
/// {@macro reactionIconBuilder}
final ReactionIconBuilder builder;
}
@@ -165,7 +165,7 @@ class StreamChatConfigurationData {
return StreamSvgIcon.loveReaction(
color: highlighted
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(0.5),
: theme.primaryIconTheme.color,
size: size,
);
},
@@ -177,7 +177,7 @@ class StreamChatConfigurationData {
return StreamSvgIcon.thumbsUpReaction(
color: highlighted
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(0.5),
: theme.primaryIconTheme.color,
size: size,
);
},
@@ -189,7 +189,7 @@ class StreamChatConfigurationData {
return StreamSvgIcon.thumbsDownReaction(
color: highlighted
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(0.5),
: theme.primaryIconTheme.color,
size: size,
);
},
@@ -201,7 +201,7 @@ class StreamChatConfigurationData {
return StreamSvgIcon.lolReaction(
color: highlighted
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(0.5),
: theme.primaryIconTheme.color,
size: size,
);
},
@@ -213,7 +213,7 @@ class StreamChatConfigurationData {
return StreamSvgIcon.wutReaction(
color: highlighted
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(0.5),
: theme.primaryIconTheme.color,
size: size,
);
},
@@ -126,8 +126,7 @@ class StreamChatThemeData {
StreamTextTheme textTheme,
) {
final accentColor = colorTheme.accentPrimary;
final iconTheme =
IconThemeData(color: colorTheme.textHighEmphasis.withOpacity(0.5));
final iconTheme = IconThemeData(color: colorTheme.textLowEmphasis);
final channelHeaderTheme = StreamChannelHeaderThemeData(
avatarTheme: StreamAvatarThemeData(
borderRadius: BorderRadius.circular(20),