add custom clipper to messagewidget
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -476,7 +476,6 @@ class _MessageInputState extends State<MessageInput> {
|
|||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.close,
|
Icons.close,
|
||||||
size: 15,
|
size: 15,
|
||||||
color: Colors.black,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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
@@ -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,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user