Merge pull request #474 from GetStream/feat/customReaction

feat: let users use custom reaction builders
This commit is contained in:
Salvatore Giordano
2021-06-14 17:50:35 +02:00
committed by GitHub
8 changed files with 147 additions and 26 deletions
@@ -5,6 +5,7 @@
- Performance improvements - Performance improvements
- Added pinMessage ui support - Added pinMessage ui support
- Added `MessageListView.threadSeparatorBuilder` property - Added `MessageListView.threadSeparatorBuilder` property
## 2.0.0-nullsafety.4 ## 2.0.0-nullsafety.4
- Minor fixes and improvements - Minor fixes and improvements
@@ -148,6 +148,10 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
final streamChatThemeData = StreamChatTheme.of(context); final streamChatThemeData = StreamChatTheme.of(context);
final numberOfReactions = streamChatThemeData.reactionIcons.length;
final shiftFactor =
numberOfReactions < 5 ? (5 - numberOfReactions) * 0.1 : 0.0;
final child = Center( final child = Center(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Padding( child: Padding(
@@ -162,8 +166,12 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
Align( Align(
alignment: Alignment( alignment: Alignment(
user?.id == widget.message.user?.id user?.id == widget.message.user?.id
? (divFactor >= 1.0 ? -0.2 : (1.2 - divFactor)) ? (divFactor >= 1.0
: (divFactor >= 1.0 ? 0.2 : -(1.2 - divFactor)), ? -0.2 - shiftFactor
: (1.2 - divFactor))
: (divFactor >= 1.0
? 0.2 + shiftFactor
: -(1.2 - divFactor)),
0), 0),
child: ReactionPicker( child: ReactionPicker(
message: widget.message, message: widget.message,
@@ -80,6 +80,10 @@ class MessageReactionsModal extends StatelessWidget {
final hasFileAttachment = final hasFileAttachment =
message.attachments.any((it) => it.type == 'file') == true; message.attachments.any((it) => it.type == 'file') == true;
final numberOfReactions = StreamChatTheme.of(context).reactionIcons.length;
final shiftFactor =
numberOfReactions < 5 ? (5 - numberOfReactions) * 0.1 : 0.0;
final child = Center( final child = Center(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Padding( child: Padding(
@@ -93,8 +97,12 @@ class MessageReactionsModal extends StatelessWidget {
Align( Align(
alignment: Alignment( alignment: Alignment(
user!.id == message.user!.id user!.id == message.user!.id
? (divFactor >= 1.0 ? -0.2 : (1.2 - divFactor)) ? (divFactor >= 1.0
: (divFactor >= 1.0 ? 0.2 : -(1.2 - divFactor)), ? -0.2 - shiftFactor
: (1.2 - divFactor))
: (divFactor >= 1.0
? 0.2 + shiftFactor
: -(1.2 - divFactor)),
0), 0),
child: ReactionPicker( child: ReactionPicker(
message: message, message: message,
@@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:stream_chat_flutter/src/reaction_icon.dart'; import 'package:stream_chat_flutter/src/reaction_icon.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Creates reaction bubble widget for displaying over messages /// Creates reaction bubble widget for displaying over messages
@@ -130,13 +129,13 @@ class ReactionBubble extends StatelessWidget {
horizontal: 4, horizontal: 4,
), ),
child: reactionIcon != null child: reactionIcon != null
? StreamSvgIcon( ? ConstrainedBox(
assetName: reactionIcon.assetName, constraints: BoxConstraints.tight(const Size.square(16)),
width: 16, child: reactionIcon.builder(
height: 16, context,
color: (!highlightOwnReactions || reaction.user?.id == userId) !highlightOwnReactions || reaction.user?.id == userId,
? chatThemeData.colorTheme.accentBlue 16,
: chatThemeData.colorTheme.black.withOpacity(.5), ),
) )
: Icon( : Icon(
Icons.help_outline_rounded, Icons.help_outline_rounded,
@@ -1,14 +1,20 @@
import 'package:flutter/material.dart';
/// Reaction icon data /// Reaction icon data
class ReactionIcon { class ReactionIcon {
/// Constructor for creating [ReactionIcon] /// Constructor for creating [ReactionIcon]
ReactionIcon({ ReactionIcon({
required this.type, required this.type,
required this.assetName, required this.builder,
}); });
/// Type of reaction /// Type of reaction
final String type; final String type;
/// Asset to display for reaction /// Asset to display for reaction
final String assetName; final Widget Function(
BuildContext,
bool highlighted,
double size,
) builder;
} }
@@ -1,7 +1,6 @@
import 'package:ezanimation/ezanimation.dart'; import 'package:ezanimation/ezanimation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.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'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png)
@@ -69,11 +68,10 @@ class _ReactionPickerState extends State<ReactionPicker>
-1; -1;
final index = reactionIcons.indexOf(reactionIcon); final index = reactionIcons.indexOf(reactionIcon);
final child = StreamSvgIcon( final child = reactionIcon.builder(
assetName: reactionIcon.assetName, context,
color: ownReactionIndex != -1 ownReactionIndex != -1,
? chatThemeData.colorTheme.accentBlue 24,
: Theme.of(context).iconTheme.color!.withOpacity(.5),
); );
return ConstrainedBox( return ConstrainedBox(
@@ -217,10 +217,11 @@ class StreamChatThemeData {
TextTheme textTheme, TextTheme textTheme,
) { ) {
final accentColor = colorTheme.accentBlue; final accentColor = colorTheme.accentBlue;
final iconTheme = IconThemeData(color: colorTheme.black.withOpacity(.5));
return StreamChatThemeData.raw( return StreamChatThemeData.raw(
textTheme: textTheme, textTheme: textTheme,
colorTheme: colorTheme, colorTheme: colorTheme,
primaryIconTheme: IconThemeData(color: colorTheme.black.withOpacity(.5)), primaryIconTheme: iconTheme,
defaultChannelImage: (context, channel) => const SizedBox(), defaultChannelImage: (context, channel) => const SizedBox(),
defaultUserImage: (context, user) => Center( defaultUserImage: (context, user) => Center(
child: CachedNetworkImage( child: CachedNetworkImage(
@@ -342,23 +343,63 @@ class StreamChatThemeData {
reactionIcons: [ reactionIcons: [
ReactionIcon( ReactionIcon(
type: 'love', type: 'love',
assetName: 'Icon_love_reaction.svg', builder: (context, highlighted, size) {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.loveReaction(
color: highlighted
? theme.colorTheme.accentBlue
: theme.primaryIconTheme.color!.withOpacity(.5),
size: size,
);
},
), ),
ReactionIcon( ReactionIcon(
type: 'like', type: 'like',
assetName: 'Icon_thumbs_up_reaction.svg', builder: (context, highlighted, size) {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.thumbsUpReaction(
color: highlighted
? theme.colorTheme.accentBlue
: theme.primaryIconTheme.color!.withOpacity(.5),
size: size,
);
},
), ),
ReactionIcon( ReactionIcon(
type: 'sad', type: 'sad',
assetName: 'Icon_thumbs_down_reaction.svg', builder: (context, highlighted, size) {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.thumbsDownReaction(
color: highlighted
? theme.colorTheme.accentBlue
: theme.primaryIconTheme.color!.withOpacity(.5),
size: size,
);
},
), ),
ReactionIcon( ReactionIcon(
type: 'haha', type: 'haha',
assetName: 'Icon_LOL_reaction.svg', builder: (context, highlighted, size) {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.lolReaction(
color: highlighted
? theme.colorTheme.accentBlue
: theme.primaryIconTheme.color!.withOpacity(.5),
size: size,
);
},
), ),
ReactionIcon( ReactionIcon(
type: 'wow', type: 'wow',
assetName: 'Icon_wut_reaction.svg', builder: (context, highlighted, size) {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.wutReaction(
color: highlighted
? theme.colorTheme.accentBlue
: theme.primaryIconTheme.color!.withOpacity(.5),
size: size,
);
},
), ),
], ],
); );
@@ -49,6 +49,66 @@ class StreamSvgIcon extends StatelessWidget {
height: size, height: size,
); );
/// [StreamSvgIcon] type
factory StreamSvgIcon.loveReaction({
double? size,
Color? color,
}) =>
StreamSvgIcon(
assetName: 'Icon_love_reaction.svg',
color: color,
width: size,
height: size,
);
/// [StreamSvgIcon] type
factory StreamSvgIcon.thumbsUpReaction({
double? size,
Color? color,
}) =>
StreamSvgIcon(
assetName: 'Icon_thumbs_up_reaction.svg',
color: color,
width: size,
height: size,
);
/// [StreamSvgIcon] type
factory StreamSvgIcon.thumbsDownReaction({
double? size,
Color? color,
}) =>
StreamSvgIcon(
assetName: 'Icon_thumbs_down_reaction.svg',
color: color,
width: size,
height: size,
);
/// [StreamSvgIcon] type
factory StreamSvgIcon.lolReaction({
double? size,
Color? color,
}) =>
StreamSvgIcon(
assetName: 'Icon_LOL_reaction.svg',
color: color,
width: size,
height: size,
);
/// [StreamSvgIcon] type
factory StreamSvgIcon.wutReaction({
double? size,
Color? color,
}) =>
StreamSvgIcon(
assetName: 'Icon_wut_reaction.svg',
color: color,
width: size,
height: size,
);
/// [StreamSvgIcon] type /// [StreamSvgIcon] type
factory StreamSvgIcon.smile({ factory StreamSvgIcon.smile({
double? size, double? size,