add custom clipper to messagewidget

This commit is contained in:
Salvatore Giordano
2020-04-25 14:10:16 +02:00
parent 3ea81c2519
commit 3d72ea699c
7 changed files with 111 additions and 49 deletions
+1 -1
View File
@@ -91,7 +91,7 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
theme: ThemeData.light(), theme: ThemeData.light(),
darkTheme: ThemeData.dark(), darkTheme: ThemeData.dark(),
themeMode: ThemeMode.system, themeMode: ThemeMode.dark,
home: Container( home: Container(
child: StreamChat( child: StreamChat(
client: client, client: client,
-3
View File
@@ -157,9 +157,6 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
child: Icon( child: Icon(
Icons.arrow_back_ios, Icons.arrow_back_ios,
size: 15, size: 15,
color: Theme.of(context).brightness == Brightness.dark
? Colors.white
: Colors.black,
), ),
), ),
); );
-1
View File
@@ -476,7 +476,6 @@ class _MessageInputState extends State<MessageInput> {
child: Icon( child: Icon(
Icons.close, Icons.close,
size: 15, size: 15,
color: Colors.black,
), ),
), ),
), ),
+34 -1
View File
@@ -71,6 +71,8 @@ class MessageListView extends StatefulWidget {
this.onMessageActions, this.onMessageActions,
this.attachmentBuilders, this.attachmentBuilders,
this.dateDividerBuilder, this.dateDividerBuilder,
this.showAvatar = true,
this.customClipperBuilder,
}) : super(key: key); }) : super(key: key);
/// Function used to build a custom message widget /// Function used to build a custom message widget
@@ -110,6 +112,13 @@ class MessageListView extends StatefulWidget {
/// Builder used to render date dividers /// Builder used to render date dividers
final Widget Function(DateTime) dateDividerBuilder; final Widget Function(DateTime) dateDividerBuilder;
/// if true shows the user avatar
final bool showAvatar;
/// Custom clipper applied to the message bubble
final CustomClipper Function(BuildContext, Message, int index)
customClipperBuilder;
@override @override
_MessageListViewState createState() => _MessageListViewState(); _MessageListViewState createState() => _MessageListViewState();
} }
@@ -171,6 +180,14 @@ class _MessageListViewState extends State<MessageListView> {
onUserAvatarTap: widget.onUserAvatarTap, onUserAvatarTap: widget.onUserAvatarTap,
onMessageActions: widget.onMessageActions, onMessageActions: widget.onMessageActions,
attachmentBuilders: widget.attachmentBuilders, attachmentBuilders: widget.attachmentBuilders,
showAvatar: widget.showAvatar,
customClipperBuilder: (message) {
return widget.customClipperBuilder(
context,
message,
i,
);
},
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 32), padding: const EdgeInsets.symmetric(horizontal: 32),
@@ -235,6 +252,12 @@ class _MessageListViewState extends State<MessageListView> {
onUserAvatarTap: widget.onUserAvatarTap, onUserAvatarTap: widget.onUserAvatarTap,
onMessageActions: widget.onMessageActions, onMessageActions: widget.onMessageActions,
attachmentBuilders: widget.attachmentBuilders, attachmentBuilders: widget.attachmentBuilders,
showAvatar: widget.showAvatar,
customClipperBuilder: (message) {
if (widget.customClipperBuilder != null) {
return widget.customClipperBuilder(context, message, i);
}
},
); );
} }
} }
@@ -308,7 +331,8 @@ class _MessageListViewState extends State<MessageListView> {
if (widget.messageBuilder != null) { if (widget.messageBuilder != null) {
messageWidget = Builder( messageWidget = Builder(
key: ValueKey<String>('MESSAGE-${message.id}'), key: ValueKey<String>('MESSAGE-${message.id}'),
builder: (_) => widget.messageBuilder(context, message, 0), builder: (_) =>
widget.messageBuilder(context, message, _messages.length - 1),
); );
} else { } else {
messageWidget = MessageWidget( messageWidget = MessageWidget(
@@ -323,6 +347,11 @@ class _MessageListViewState extends State<MessageListView> {
onUserAvatarTap: widget.onUserAvatarTap, onUserAvatarTap: widget.onUserAvatarTap,
onMessageActions: widget.onMessageActions, onMessageActions: widget.onMessageActions,
attachmentBuilders: widget.attachmentBuilders, attachmentBuilders: widget.attachmentBuilders,
showAvatar: widget.showAvatar,
customClipperBuilder: (message) {
return widget.customClipperBuilder(
context, message, _messages.length - 1);
},
); );
} }
@@ -364,6 +393,10 @@ class _MessageListViewState extends State<MessageListView> {
onUserAvatarTap: widget.onUserAvatarTap, onUserAvatarTap: widget.onUserAvatarTap,
onMessageActions: widget.onMessageActions, onMessageActions: widget.onMessageActions,
attachmentBuilders: widget.attachmentBuilders, attachmentBuilders: widget.attachmentBuilders,
showAvatar: widget.showAvatar,
customClipperBuilder: (message) {
return widget.customClipperBuilder(context, message, 0);
},
); );
} }
+63 -42
View File
@@ -48,6 +48,8 @@ class MessageWidget extends StatefulWidget {
this.showOtherMessageUsername = false, this.showOtherMessageUsername = false,
this.showVideoFullScreen = true, this.showVideoFullScreen = true,
this.attachmentBuilders, this.attachmentBuilders,
this.showAvatar = true,
this.customClipperBuilder,
}) : super(key: key); }) : super(key: key);
/// Function called on mention tap /// Function called on mention tap
@@ -83,6 +85,12 @@ class MessageWidget extends StatefulWidget {
/// Map that defines a builder for an attachment type /// Map that defines a builder for an attachment type
final Map<String, AttachmentBuilder> attachmentBuilders; final Map<String, AttachmentBuilder> attachmentBuilders;
/// if true shows the user avatar
final bool showAvatar;
/// Custom clipper applied to the message bubble
final CustomClipper Function(Message) customClipperBuilder;
@override @override
_MessageWidgetState createState() => _MessageWidgetState(); _MessageWidgetState createState() => _MessageWidgetState();
} }
@@ -130,7 +138,7 @@ class _MessageWidgetState extends State<MessageWidget>
), ),
_isNextUser _isNextUser
? Container( ? Container(
width: 40, width: widget.showAvatar ? 40 : 8,
) )
: _buildUserAvatar(), : _buildUserAvatar(),
]); ]);
@@ -164,11 +172,13 @@ class _MessageWidgetState extends State<MessageWidget>
right: _isMyMessage ? 0 : 8.0, right: _isMyMessage ? 0 : 8.0,
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
UserAvatar( if (widget.showAvatar)
user: widget.message.user, UserAvatar(
onTap: widget.onUserAvatarTap, user: widget.message.user,
), onTap: widget.onUserAvatarTap,
),
if (_isMyMessage && widget.nextMessage == null) if (_isMyMessage && widget.nextMessage == null)
SendingIndicator( SendingIndicator(
message: widget.message, message: widget.message,
@@ -466,7 +476,7 @@ class _MessageWidgetState extends State<MessageWidget>
); );
} }
Padding _buildMessageText( Widget _buildMessageText(
int nOfAttachmentWidgets, int nOfAttachmentWidgets,
String text, String text,
BuildContext context, BuildContext context,
@@ -476,46 +486,57 @@ class _MessageWidgetState extends State<MessageWidget>
right: _isMyMessage ? 0.0 : 8.0, right: _isMyMessage ? 0.0 : 8.0,
left: _isMyMessage ? 8.0 : 0.0, left: _isMyMessage ? 8.0 : 0.0,
), ),
child: Container( child: ClipPath(
decoration: clipper: widget.customClipperBuilder != null
_buildBoxDecoration(_isLastUser || nOfAttachmentWidgets > 0), ? widget.customClipperBuilder(widget.message)
padding: EdgeInsets.all(10), : null,
constraints: BoxConstraints.loose( clipBehavior: Clip.hardEdge,
Size.fromWidth(MediaQuery.of(context).size.width * 0.7), child: Container(
), decoration:
child: _buildSendingError( _buildBoxDecoration(_isLastUser || nOfAttachmentWidgets > 0)
MarkdownBody( .copyWith(
data: text, borderRadius: widget.customClipperBuilder != null
onTapLink: (link) { ? BorderRadius.circular(0)
if (link.startsWith('@')) { : null),
final mentionedUser = widget.message.mentionedUsers.firstWhere( padding: EdgeInsets.all(10),
(u) => '@${u.name.replaceAll(' ', '')}' == link, constraints: BoxConstraints.loose(
orElse: () => null, Size.fromWidth(MediaQuery.of(context).size.width * 0.7),
); ),
child: _buildSendingError(
MarkdownBody(
data: text,
onTapLink: (link) {
if (link.startsWith('@')) {
final mentionedUser =
widget.message.mentionedUsers.firstWhere(
(u) => '@${u.name.replaceAll(' ', '')}' == link,
orElse: () => null,
);
if (widget.onMentionTap != null) { if (widget.onMentionTap != null) {
widget.onMentionTap(mentionedUser); widget.onMentionTap(mentionedUser);
} else {
print('tap on ${mentionedUser.name}');
}
} else { } else {
print('tap on ${mentionedUser.name}'); launchURL(context, link);
} }
} else { },
launchURL(context, link); styleSheet: MarkdownStyleSheet.fromTheme(
} Theme.of(context).copyWith(
}, textTheme: Theme.of(context).textTheme.apply(
styleSheet: MarkdownStyleSheet.fromTheme( bodyColor: _messageTheme.messageText.color,
Theme.of(context).copyWith( decoration: _messageTheme.messageText.decoration,
textTheme: Theme.of(context).textTheme.apply( decorationColor:
bodyColor: _messageTheme.messageText.color, _messageTheme.messageText.decorationColor,
decoration: _messageTheme.messageText.decoration, decorationStyle:
decorationColor: _messageTheme.messageText.decorationStyle,
_messageTheme.messageText.decorationColor, fontFamily: _messageTheme.messageText.fontFamily,
decorationStyle: ),
_messageTheme.messageText.decorationStyle, ),
fontFamily: _messageTheme.messageText.fontFamily, ).copyWith(
), p: _messageTheme.messageText,
), ),
).copyWith(
p: _messageTheme.messageText,
), ),
), ),
), ),
+4 -1
View File
@@ -73,9 +73,11 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
builder: (context) { builder: (context) {
final materialTheme = Theme.of(context); final materialTheme = Theme.of(context);
final isDark = materialTheme.brightness == Brightness.dark; final isDark = materialTheme.brightness == Brightness.dark;
final streamTheme = StreamChatTheme.of(context);
return Theme( return Theme(
data: materialTheme.copyWith( data: materialTheme.copyWith(
accentColor: StreamChatTheme.of(context).accentColor, primaryIconTheme: streamTheme.primaryIconTheme,
accentColor: streamTheme.accentColor,
scaffoldBackgroundColor: isDark ? Colors.black : Colors.white, scaffoldBackgroundColor: isDark ? Colors.black : Colors.white,
backgroundColor: isDark ? Colors.black : Colors.white, backgroundColor: isDark ? Colors.black : Colors.white,
), ),
@@ -113,6 +115,7 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
final theme = defaultTheme.copyWith( final theme = defaultTheme.copyWith(
primaryColor: themeData?.primaryColor, primaryColor: themeData?.primaryColor,
defaultChannelImage: themeData?.defaultChannelImage, defaultChannelImage: themeData?.defaultChannelImage,
primaryIconTheme: themeData?.primaryIconTheme,
defaultUserImage: themeData?.defaultUserImage, defaultUserImage: themeData?.defaultUserImage,
channelTheme: defaultTheme.channelTheme.copyWith( channelTheme: defaultTheme.channelTheme.copyWith(
channelHeaderTheme: channelHeaderTheme:
+9
View File
@@ -57,6 +57,9 @@ class StreamChatThemeData {
/// The widget that will be built when the user image is unavailable /// The widget that will be built when the user image is unavailable
final Widget Function(BuildContext, User) defaultUserImage; final Widget Function(BuildContext, User) defaultUserImage;
/// Primary icon theme
final IconThemeData primaryIconTheme;
/// Create a theme from scratch /// Create a theme from scratch
StreamChatThemeData({ StreamChatThemeData({
this.primaryColor, this.primaryColor,
@@ -68,6 +71,7 @@ class StreamChatThemeData {
this.ownMessageTheme, this.ownMessageTheme,
this.defaultChannelImage, this.defaultChannelImage,
this.defaultUserImage, this.defaultUserImage,
this.primaryIconTheme,
}); });
/// Create a theme from a Material [Theme] /// Create a theme from a Material [Theme]
@@ -76,6 +80,7 @@ class StreamChatThemeData {
return defaultTheme.copyWith( return defaultTheme.copyWith(
accentColor: theme.accentColor, accentColor: theme.accentColor,
primaryIconTheme: theme.primaryIconTheme,
primaryColor: theme.colorScheme.primary, primaryColor: theme.colorScheme.primary,
secondaryColor: theme.colorScheme.secondary, secondaryColor: theme.colorScheme.secondary,
channelTheme: ChannelTheme( channelTheme: ChannelTheme(
@@ -108,10 +113,12 @@ class StreamChatThemeData {
MessageTheme otherMessageTheme, MessageTheme otherMessageTheme,
Widget Function(BuildContext, Channel) defaultChannelImage, Widget Function(BuildContext, Channel) defaultChannelImage,
Widget Function(BuildContext, User) defaultUserImage, Widget Function(BuildContext, User) defaultUserImage,
IconThemeData primaryIconTheme,
}) => }) =>
StreamChatThemeData( StreamChatThemeData(
primaryColor: primaryColor ?? this.primaryColor, primaryColor: primaryColor ?? this.primaryColor,
secondaryColor: secondaryColor ?? this.secondaryColor, secondaryColor: secondaryColor ?? this.secondaryColor,
primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme,
accentColor: accentColor ?? this.accentColor, accentColor: accentColor ?? this.accentColor,
defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage, defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage,
defaultUserImage: defaultUserImage ?? this.defaultUserImage, defaultUserImage: defaultUserImage ?? this.defaultUserImage,
@@ -183,6 +190,8 @@ class StreamChatThemeData {
return StreamChatThemeData( return StreamChatThemeData(
accentColor: accentColor, accentColor: accentColor,
primaryColor: isDark ? Colors.black : Colors.white, primaryColor: isDark ? Colors.black : Colors.white,
primaryIconTheme:
IconThemeData(color: isDark ? Colors.white : Colors.black),
defaultChannelImage: (context, channel) => SizedBox(), defaultChannelImage: (context, channel) => SizedBox(),
defaultUserImage: (context, user) => Center( defaultUserImage: (context, user) => Center(
child: Text( child: Text(