diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift
index 69a7daf4..e024c1ef 100644
--- a/example/ios/Runner/AppDelegate.swift
+++ b/example/ios/Runner/AppDelegate.swift
@@ -14,6 +14,10 @@ import Flutter
sharedDefaults?.removeObject(forKey: "messageQueue")
}
+ if #available(iOS 10.0, *) {
+ UNUserNotificationCenter.current().delegate = self
+ }
+
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index 6c2c8e27..b41f1c19 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -58,5 +58,9 @@
remote-notification
+ UIUserInterfaceStyle
+ Light
+ UIViewControllerBasedStatusBarAppearance
+
diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart
deleted file mode 100644
index 747db1da..00000000
--- a/example/test/widget_test.dart
+++ /dev/null
@@ -1,30 +0,0 @@
-// This is a basic Flutter widget test.
-//
-// To perform an interaction with a widget in your test, use the WidgetTester
-// utility that Flutter provides. For example, you can send tap and scroll
-// gestures. You can also use WidgetTester to find child widgets in the widget
-// tree, read text, and verify that the values of widget properties are correct.
-
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import 'package:example/main.dart';
-
-void main() {
- testWidgets('Counter increments smoke test', (WidgetTester tester) async {
- // Build our app and trigger a frame.
- await tester.pumpWidget(MyApp());
-
- // Verify that our counter starts at 0.
- expect(find.text('0'), findsOneWidget);
- expect(find.text('1'), findsNothing);
-
- // Tap the '+' icon and trigger a frame.
- await tester.tap(find.byIcon(Icons.add));
- await tester.pump();
-
- // Verify that our counter has incremented.
- expect(find.text('0'), findsNothing);
- expect(find.text('1'), findsOneWidget);
- });
-}
diff --git a/lib/src/channel_header.dart b/lib/src/channel_header.dart
index 16aa70fc..81f95a87 100644
--- a/lib/src/channel_header.dart
+++ b/lib/src/channel_header.dart
@@ -78,6 +78,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
Widget build(BuildContext context) {
final channel = StreamChannel.of(context).channel;
return AppBar(
+ brightness: Theme.of(context).brightness,
elevation: 1,
leading: showBackButton
? StreamBackButton(onPressed: onBackPressed)
diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart
index 93e1c0db..00354a49 100644
--- a/lib/src/message_actions_modal.dart
+++ b/lib/src/message_actions_modal.dart
@@ -25,6 +25,7 @@ class MessageActionsModal extends StatelessWidget {
final bool showReply;
final bool reverse;
final ShapeBorder messageShape;
+ final DisplayWidget showUserAvatar;
const MessageActionsModal({
Key key,
@@ -36,6 +37,7 @@ class MessageActionsModal extends StatelessWidget {
this.onThreadTap,
this.showCopyMessage = true,
this.showReply = true,
+ this.showUserAvatar = DisplayWidget.show,
this.editMessageInputBuilder,
this.messageShape,
this.reverse = false,
@@ -43,7 +45,15 @@ class MessageActionsModal extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final ownId = StreamChat.of(context).user.id;
+ var size = MediaQuery.of(context).size;
+ var user = StreamChat.of(context).user;
+
+ var roughMaxSize = 2 * size.width / 3;
+ var roughSentenceSize =
+ message.text.length * messageTheme.messageText.fontSize * 1.2;
+ var divFactor =
+ roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize);
+
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
@@ -58,7 +68,7 @@ class MessageActionsModal extends StatelessWidget {
sigmaY: 10.8731,
),
child: Container(
- color: Colors.black.withOpacity(0.2),
+ color: Colors.black.withOpacity(0.1),
),
),
),
@@ -73,7 +83,12 @@ class MessageActionsModal extends StatelessWidget {
if (showReactions &&
(message.status == MessageSendingStatus.SENT ||
message.status == null))
- Center(
+ Align(
+ alignment: Alignment(
+ user.id == message.user.id
+ ? (divFactor > 1.0 ? 0.0 : (1.0 - divFactor))
+ : (divFactor > 1.0 ? 0.0 : -(1.0 - divFactor)),
+ 0.0),
child: ReactionPicker(
message: message,
messageTheme: messageTheme,
@@ -92,10 +107,10 @@ class MessageActionsModal extends StatelessWidget {
showReactions: false,
showUsername: false,
showReplyIndicator: false,
- showUserAvatar: message.user.id == ownId
- ? DisplayWidget.gone
- : DisplayWidget.show,
+ showUserAvatar: showUserAvatar,
showTimestamp: false,
+ translateUserAvatar: false,
+ showReactionPickerIndicator: true,
showSendingIndicator: DisplayWidget.gone,
shape: messageShape,
),
diff --git a/lib/src/message_reactions_modal.dart b/lib/src/message_reactions_modal.dart
index 375da94e..fc3f68a4 100644
--- a/lib/src/message_reactions_modal.dart
+++ b/lib/src/message_reactions_modal.dart
@@ -17,6 +17,7 @@ class MessageReactionsModal extends StatelessWidget {
final MessageTheme messageTheme;
final bool reverse;
final bool showReactions;
+ final DisplayWidget showUserAvatar;
final ShapeBorder messageShape;
final void Function(User) onUserAvatarTap;
@@ -29,12 +30,21 @@ class MessageReactionsModal extends StatelessWidget {
this.editMessageInputBuilder,
this.messageShape,
this.reverse = false,
+ this.showUserAvatar = DisplayWidget.show,
this.onUserAvatarTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
- final ownId = StreamChat.of(context).user.id;
+ var size = MediaQuery.of(context).size;
+ var user = StreamChat.of(context).user;
+
+ var roughMaxSize = 2 * size.width / 3;
+ var roughSentenceSize =
+ message.text.length * messageTheme.messageText.fontSize * 1.2;
+ var divFactor =
+ roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize);
+
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
@@ -49,7 +59,7 @@ class MessageReactionsModal extends StatelessWidget {
sigmaY: 10.8731,
),
child: Container(
- color: Colors.black.withOpacity(0.2),
+ color: Colors.black.withOpacity(0.1),
),
),
),
@@ -64,7 +74,12 @@ class MessageReactionsModal extends StatelessWidget {
if (showReactions &&
(message.status == MessageSendingStatus.SENT ||
message.status == null))
- Center(
+ Align(
+ alignment: Alignment(
+ user.id == message.user.id
+ ? (divFactor > 1.0 ? 0.0 : (1.0 - divFactor))
+ : (divFactor > 1.0 ? 0.0 : -(1.0 - divFactor)),
+ 0.0),
child: ReactionPicker(
message: message,
messageTheme: messageTheme,
@@ -82,13 +97,13 @@ class MessageReactionsModal extends StatelessWidget {
messageTheme: messageTheme,
showReactions: false,
showUsername: false,
- showUserAvatar: message.user.id == ownId
- ? DisplayWidget.gone
- : DisplayWidget.show,
+ showUserAvatar: showUserAvatar,
showReplyIndicator: false,
showTimestamp: false,
+ translateUserAvatar: false,
showSendingIndicator: DisplayWidget.gone,
shape: messageShape,
+ showReactionPickerIndicator: true,
),
),
SizedBox(
diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart
index 53be9ae6..d57ef0f9 100644
--- a/lib/src/message_widget.dart
+++ b/lib/src/message_widget.dart
@@ -106,6 +106,9 @@ class MessageWidget extends StatefulWidget {
/// The function called when tapping on a link
final void Function(String) onLinkTap;
+ /// Used in [MessageReactionsModal] and [MessageActionsModal]
+ final bool showReactionPickerIndicator;
+
final List readList;
/// If true show the users username next to the timestamp of the message
@@ -115,11 +118,15 @@ class MessageWidget extends StatefulWidget {
final bool showEditMessage;
final Map attachmentBuilders;
+ /// Center user avatar with bottom of the message
+ final bool translateUserAvatar;
+
MessageWidget({
Key key,
@required this.message,
@required this.messageTheme,
this.reverse = false,
+ this.translateUserAvatar = true,
this.shape,
this.attachmentShape,
this.borderSide,
@@ -127,6 +134,7 @@ class MessageWidget extends StatefulWidget {
this.borderRadiusGeometry,
this.attachmentBorderRadiusGeometry,
this.onMentionTap,
+ this.showReactionPickerIndicator = false,
this.showUserAvatar = DisplayWidget.show,
this.showSendingIndicator = DisplayWidget.show,
this.showReplyIndicator = true,
@@ -253,25 +261,58 @@ class _MessageWidgetState extends State {
),
portalAnchor: Alignment(-1.0, -1.0),
childAnchor: Alignment(1, -1.0),
- child: Padding(
- padding: widget.showReactions
- ? EdgeInsets.only(
- top: widget.message.reactionCounts
- ?.isNotEmpty ==
- true
- ? 18
- : 0,
- )
- : EdgeInsets.zero,
- child: (widget.message.isDeleted &&
- widget.message.status !=
- MessageSendingStatus.FAILED_DELETE)
- ? Transform(
- alignment: Alignment.center,
+ child: Stack(
+ clipBehavior: Clip.none,
+ children: [
+ Padding(
+ padding: widget.showReactions
+ ? EdgeInsets.only(
+ top: widget.message.reactionCounts
+ ?.isNotEmpty ==
+ true
+ ? 18
+ : 0,
+ )
+ : EdgeInsets.zero,
+ child: (widget.message.isDeleted &&
+ widget.message.status !=
+ MessageSendingStatus
+ .FAILED_DELETE)
+ ? Transform(
+ alignment: Alignment.center,
+ transform: Matrix4.rotationY(
+ widget.reverse ? pi : 0),
+ child: DeletedMessage(
+ messageTheme: widget.messageTheme,
+ ),
+ )
+ : Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ ..._parseAttachments(context),
+ if (widget.message.text
+ .trim()
+ .isNotEmpty)
+ _buildTextBubble(context),
+ ],
+ ),
+ ),
+ if (widget.showReactionPickerIndicator)
+ Positioned(
+ right: 0,
+ top: -6,
+ child: Transform(
transform: Matrix4.rotationY(
widget.reverse ? pi : 0),
- child: DeletedMessage(
- messageTheme: widget.messageTheme,
+ child: CustomPaint(
+ painter: ReactionBubblePainter(
+ widget.messageTheme
+ .reactionsBackgroundColor,
+ widget.messageTheme
+ .reactionsBorderColor,
+ ),
),
)
: Column(
@@ -287,6 +328,8 @@ class _MessageWidgetState extends State {
_buildTextBubble(context),
],
),
+ ),
+ ],
),
),
),
@@ -430,6 +473,10 @@ class _MessageWidgetState extends State {
return StreamChannel(
channel: channel,
child: MessageActionsModal(
+ showUserAvatar:
+ widget.message.user.id == channel.client.state.user.id
+ ? DisplayWidget.gone
+ : DisplayWidget.show,
messageTheme: widget.messageTheme,
messageShape: widget.shape ?? _getDefaultShape(context),
reverse: widget.reverse,
@@ -454,6 +501,10 @@ class _MessageWidgetState extends State {
return StreamChannel(
channel: channel,
child: MessageReactionsModal(
+ showUserAvatar:
+ widget.message.user.id == channel.client.state.user.id
+ ? DisplayWidget.gone
+ : DisplayWidget.show,
onUserAvatarTap: widget.onUserAvatarTap,
messageTheme: widget.messageTheme,
messageShape: widget.shape ?? _getDefaultShape(context),
@@ -625,7 +676,11 @@ class _MessageWidgetState extends State {
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Transform.translate(
offset: Offset(
- 0, widget.messageTheme.avatarTheme.constraints.maxHeight / 2),
+ 0,
+ widget.translateUserAvatar
+ ? widget.messageTheme.avatarTheme.constraints.maxHeight / 2
+ : 0,
+ ),
child: UserAvatar(
user: widget.message.user,
onTap: widget.onUserAvatarTap,
diff --git a/lib/src/reaction_picker.dart b/lib/src/reaction_picker.dart
index 87487cde..e7d302e6 100644
--- a/lib/src/reaction_picker.dart
+++ b/lib/src/reaction_picker.dart
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
-import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import '../stream_chat_flutter.dart';
@@ -22,59 +21,44 @@ class ReactionPicker extends StatelessWidget {
@override
Widget build(BuildContext context) {
final reactionIcons = StreamChatTheme.of(context).reactionIcons;
- return Stack(
- fit: StackFit.passthrough,
- children: [
- Material(
- color: messageTheme.reactionsBackgroundColor,
- clipBehavior: Clip.hardEdge,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(24),
- ),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.center,
- mainAxisSize: MainAxisSize.min,
- children: reactionIcons.map((reactionIcon) {
- final ownReactionIndex = message.ownReactions?.indexWhere(
- (reaction) => reaction.type == reactionIcon.type) ??
- -1;
- return IconButton(
- iconSize: 24,
- icon: Icon(
- reactionIcon.iconData,
- color: ownReactionIndex != -1
- ? StreamChatTheme.of(context).accentColor
- : Theme.of(context).iconTheme.color.withOpacity(.5),
- ),
- onPressed: () {
- if (ownReactionIndex != -1) {
- removeReaction(
- context,
- message.ownReactions[ownReactionIndex],
- );
- } else {
- sendReaction(
- context,
- reactionIcon.type,
- );
- }
- },
- );
- }).toList(),
- ),
- ),
- Positioned(
- right: 14,
- bottom: 0,
- child: CustomPaint(
- painter: ReactionBubblePainter(
- messageTheme.reactionsBackgroundColor,
- messageTheme.reactionsBorderColor,
+ return Material(
+ color: messageTheme.reactionsBackgroundColor,
+ clipBehavior: Clip.hardEdge,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(24),
+ ),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.end,
+ mainAxisSize: MainAxisSize.min,
+ children: reactionIcons.map((reactionIcon) {
+ final ownReactionIndex = message.ownReactions?.indexWhere(
+ (reaction) => reaction.type == reactionIcon.type) ??
+ -1;
+ return IconButton(
+ iconSize: 24,
+ icon: Icon(
+ reactionIcon.iconData,
+ color: ownReactionIndex != -1
+ ? StreamChatTheme.of(context).accentColor
+ : Theme.of(context).iconTheme.color.withOpacity(.5),
),
- ),
- ),
- ],
+ onPressed: () {
+ if (ownReactionIndex != -1) {
+ removeReaction(
+ context,
+ message.ownReactions[ownReactionIndex],
+ );
+ } else {
+ sendReaction(
+ context,
+ reactionIcon.type,
+ );
+ }
+ },
+ );
+ }).toList(),
+ ),
);
}
diff --git a/pubspec.yaml b/pubspec.yaml
index d61e6874..452eb69a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -24,7 +24,7 @@ dependencies:
file_picker: ^2.0.8+1
image_picker: ^0.6.7+2
flutter_keyboard_visibility: ^3.2.1
- stream_chat: ^0.2.10
+ stream_chat: ^0.2.10+1
mime: ^0.9.6+3
visibility_detector: ^0.1.5
http_parser: ^3.1.4