Merge branch 'feature/new-ui' into feature/attachments

This commit is contained in:
Salvatore Giordano
2020-11-05 10:45:19 +01:00
committed by GitHub
9 changed files with 162 additions and 114 deletions
+4
View File
@@ -14,6 +14,10 @@ import Flutter
sharedDefaults?.removeObject(forKey: "messageQueue") sharedDefaults?.removeObject(forKey: "messageQueue")
} }
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self
}
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
+4
View File
@@ -58,5 +58,9 @@
<array> <array>
<string>remote-notification</string> <string>remote-notification</string>
</array> </array>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict> </dict>
</plist> </plist>
-30
View File
@@ -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);
});
}
+1
View File
@@ -78,6 +78,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
return AppBar( return AppBar(
brightness: Theme.of(context).brightness,
elevation: 1, elevation: 1,
leading: showBackButton leading: showBackButton
? StreamBackButton(onPressed: onBackPressed) ? StreamBackButton(onPressed: onBackPressed)
+21 -6
View File
@@ -25,6 +25,7 @@ class MessageActionsModal extends StatelessWidget {
final bool showReply; final bool showReply;
final bool reverse; final bool reverse;
final ShapeBorder messageShape; final ShapeBorder messageShape;
final DisplayWidget showUserAvatar;
const MessageActionsModal({ const MessageActionsModal({
Key key, Key key,
@@ -36,6 +37,7 @@ class MessageActionsModal extends StatelessWidget {
this.onThreadTap, this.onThreadTap,
this.showCopyMessage = true, this.showCopyMessage = true,
this.showReply = true, this.showReply = true,
this.showUserAvatar = DisplayWidget.show,
this.editMessageInputBuilder, this.editMessageInputBuilder,
this.messageShape, this.messageShape,
this.reverse = false, this.reverse = false,
@@ -43,7 +45,15 @@ class MessageActionsModal extends StatelessWidget {
@override @override
Widget build(BuildContext context) { 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( return GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () { onTap: () {
@@ -58,7 +68,7 @@ class MessageActionsModal extends StatelessWidget {
sigmaY: 10.8731, sigmaY: 10.8731,
), ),
child: Container( child: Container(
color: Colors.black.withOpacity(0.2), color: Colors.black.withOpacity(0.1),
), ),
), ),
), ),
@@ -73,7 +83,12 @@ class MessageActionsModal extends StatelessWidget {
if (showReactions && if (showReactions &&
(message.status == MessageSendingStatus.SENT || (message.status == MessageSendingStatus.SENT ||
message.status == null)) 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( child: ReactionPicker(
message: message, message: message,
messageTheme: messageTheme, messageTheme: messageTheme,
@@ -92,10 +107,10 @@ class MessageActionsModal extends StatelessWidget {
showReactions: false, showReactions: false,
showUsername: false, showUsername: false,
showReplyIndicator: false, showReplyIndicator: false,
showUserAvatar: message.user.id == ownId showUserAvatar: showUserAvatar,
? DisplayWidget.gone
: DisplayWidget.show,
showTimestamp: false, showTimestamp: false,
translateUserAvatar: false,
showReactionPickerIndicator: true,
showSendingIndicator: DisplayWidget.gone, showSendingIndicator: DisplayWidget.gone,
shape: messageShape, shape: messageShape,
), ),
+21 -6
View File
@@ -17,6 +17,7 @@ class MessageReactionsModal extends StatelessWidget {
final MessageTheme messageTheme; final MessageTheme messageTheme;
final bool reverse; final bool reverse;
final bool showReactions; final bool showReactions;
final DisplayWidget showUserAvatar;
final ShapeBorder messageShape; final ShapeBorder messageShape;
final void Function(User) onUserAvatarTap; final void Function(User) onUserAvatarTap;
@@ -29,12 +30,21 @@ class MessageReactionsModal extends StatelessWidget {
this.editMessageInputBuilder, this.editMessageInputBuilder,
this.messageShape, this.messageShape,
this.reverse = false, this.reverse = false,
this.showUserAvatar = DisplayWidget.show,
this.onUserAvatarTap, this.onUserAvatarTap,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { 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( return GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () { onTap: () {
@@ -49,7 +59,7 @@ class MessageReactionsModal extends StatelessWidget {
sigmaY: 10.8731, sigmaY: 10.8731,
), ),
child: Container( child: Container(
color: Colors.black.withOpacity(0.2), color: Colors.black.withOpacity(0.1),
), ),
), ),
), ),
@@ -64,7 +74,12 @@ class MessageReactionsModal extends StatelessWidget {
if (showReactions && if (showReactions &&
(message.status == MessageSendingStatus.SENT || (message.status == MessageSendingStatus.SENT ||
message.status == null)) 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( child: ReactionPicker(
message: message, message: message,
messageTheme: messageTheme, messageTheme: messageTheme,
@@ -82,13 +97,13 @@ class MessageReactionsModal extends StatelessWidget {
messageTheme: messageTheme, messageTheme: messageTheme,
showReactions: false, showReactions: false,
showUsername: false, showUsername: false,
showUserAvatar: message.user.id == ownId showUserAvatar: showUserAvatar,
? DisplayWidget.gone
: DisplayWidget.show,
showReplyIndicator: false, showReplyIndicator: false,
showTimestamp: false, showTimestamp: false,
translateUserAvatar: false,
showSendingIndicator: DisplayWidget.gone, showSendingIndicator: DisplayWidget.gone,
shape: messageShape, shape: messageShape,
showReactionPickerIndicator: true,
), ),
), ),
SizedBox( SizedBox(
+73 -18
View File
@@ -106,6 +106,9 @@ class MessageWidget extends StatefulWidget {
/// The function called when tapping on a link /// The function called when tapping on a link
final void Function(String) onLinkTap; final void Function(String) onLinkTap;
/// Used in [MessageReactionsModal] and [MessageActionsModal]
final bool showReactionPickerIndicator;
final List<Read> readList; final List<Read> readList;
/// If true show the users username next to the timestamp of the message /// 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 bool showEditMessage;
final Map<String, AttachmentBuilder> attachmentBuilders; final Map<String, AttachmentBuilder> attachmentBuilders;
/// Center user avatar with bottom of the message
final bool translateUserAvatar;
MessageWidget({ MessageWidget({
Key key, Key key,
@required this.message, @required this.message,
@required this.messageTheme, @required this.messageTheme,
this.reverse = false, this.reverse = false,
this.translateUserAvatar = true,
this.shape, this.shape,
this.attachmentShape, this.attachmentShape,
this.borderSide, this.borderSide,
@@ -127,6 +134,7 @@ class MessageWidget extends StatefulWidget {
this.borderRadiusGeometry, this.borderRadiusGeometry,
this.attachmentBorderRadiusGeometry, this.attachmentBorderRadiusGeometry,
this.onMentionTap, this.onMentionTap,
this.showReactionPickerIndicator = false,
this.showUserAvatar = DisplayWidget.show, this.showUserAvatar = DisplayWidget.show,
this.showSendingIndicator = DisplayWidget.show, this.showSendingIndicator = DisplayWidget.show,
this.showReplyIndicator = true, this.showReplyIndicator = true,
@@ -253,25 +261,58 @@ class _MessageWidgetState extends State<MessageWidget> {
), ),
portalAnchor: Alignment(-1.0, -1.0), portalAnchor: Alignment(-1.0, -1.0),
childAnchor: Alignment(1, -1.0), childAnchor: Alignment(1, -1.0),
child: Padding( child: Stack(
padding: widget.showReactions clipBehavior: Clip.none,
? EdgeInsets.only( children: [
top: widget.message.reactionCounts Padding(
?.isNotEmpty == padding: widget.showReactions
true ? EdgeInsets.only(
? 18 top: widget.message.reactionCounts
: 0, ?.isNotEmpty ==
) true
: EdgeInsets.zero, ? 18
child: (widget.message.isDeleted && : 0,
widget.message.status != )
MessageSendingStatus.FAILED_DELETE) : EdgeInsets.zero,
? Transform( child: (widget.message.isDeleted &&
alignment: Alignment.center, 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: <Widget>[
..._parseAttachments(context),
if (widget.message.text
.trim()
.isNotEmpty)
_buildTextBubble(context),
],
),
),
if (widget.showReactionPickerIndicator)
Positioned(
right: 0,
top: -6,
child: Transform(
transform: Matrix4.rotationY( transform: Matrix4.rotationY(
widget.reverse ? pi : 0), widget.reverse ? pi : 0),
child: DeletedMessage( child: CustomPaint(
messageTheme: widget.messageTheme, painter: ReactionBubblePainter(
widget.messageTheme
.reactionsBackgroundColor,
widget.messageTheme
.reactionsBorderColor,
),
), ),
) )
: Column( : Column(
@@ -287,6 +328,8 @@ class _MessageWidgetState extends State<MessageWidget> {
_buildTextBubble(context), _buildTextBubble(context),
], ],
), ),
),
],
), ),
), ),
), ),
@@ -430,6 +473,10 @@ class _MessageWidgetState extends State<MessageWidget> {
return StreamChannel( return StreamChannel(
channel: channel, channel: channel,
child: MessageActionsModal( child: MessageActionsModal(
showUserAvatar:
widget.message.user.id == channel.client.state.user.id
? DisplayWidget.gone
: DisplayWidget.show,
messageTheme: widget.messageTheme, messageTheme: widget.messageTheme,
messageShape: widget.shape ?? _getDefaultShape(context), messageShape: widget.shape ?? _getDefaultShape(context),
reverse: widget.reverse, reverse: widget.reverse,
@@ -454,6 +501,10 @@ class _MessageWidgetState extends State<MessageWidget> {
return StreamChannel( return StreamChannel(
channel: channel, channel: channel,
child: MessageReactionsModal( child: MessageReactionsModal(
showUserAvatar:
widget.message.user.id == channel.client.state.user.id
? DisplayWidget.gone
: DisplayWidget.show,
onUserAvatarTap: widget.onUserAvatarTap, onUserAvatarTap: widget.onUserAvatarTap,
messageTheme: widget.messageTheme, messageTheme: widget.messageTheme,
messageShape: widget.shape ?? _getDefaultShape(context), messageShape: widget.shape ?? _getDefaultShape(context),
@@ -625,7 +676,11 @@ class _MessageWidgetState extends State<MessageWidget> {
padding: const EdgeInsets.symmetric(horizontal: 4.0), padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Transform.translate( child: Transform.translate(
offset: Offset( offset: Offset(
0, widget.messageTheme.avatarTheme.constraints.maxHeight / 2), 0,
widget.translateUserAvatar
? widget.messageTheme.avatarTheme.constraints.maxHeight / 2
: 0,
),
child: UserAvatar( child: UserAvatar(
user: widget.message.user, user: widget.message.user,
onTap: widget.onUserAvatarTap, onTap: widget.onUserAvatarTap,
+37 -53
View File
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import '../stream_chat_flutter.dart'; import '../stream_chat_flutter.dart';
@@ -22,59 +21,44 @@ class ReactionPicker extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final reactionIcons = StreamChatTheme.of(context).reactionIcons; final reactionIcons = StreamChatTheme.of(context).reactionIcons;
return Stack( return Material(
fit: StackFit.passthrough, color: messageTheme.reactionsBackgroundColor,
children: [ clipBehavior: Clip.hardEdge,
Material( shape: RoundedRectangleBorder(
color: messageTheme.reactionsBackgroundColor, borderRadius: BorderRadius.circular(24),
clipBehavior: Clip.hardEdge, ),
shape: RoundedRectangleBorder( child: Row(
borderRadius: BorderRadius.circular(24), crossAxisAlignment: CrossAxisAlignment.start,
), mainAxisAlignment: MainAxisAlignment.end,
child: Row( mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, children: reactionIcons.map((reactionIcon) {
mainAxisAlignment: MainAxisAlignment.center, final ownReactionIndex = message.ownReactions?.indexWhere(
mainAxisSize: MainAxisSize.min, (reaction) => reaction.type == reactionIcon.type) ??
children: reactionIcons.map((reactionIcon) { -1;
final ownReactionIndex = message.ownReactions?.indexWhere( return IconButton(
(reaction) => reaction.type == reactionIcon.type) ?? iconSize: 24,
-1; icon: Icon(
return IconButton( reactionIcon.iconData,
iconSize: 24, color: ownReactionIndex != -1
icon: Icon( ? StreamChatTheme.of(context).accentColor
reactionIcon.iconData, : Theme.of(context).iconTheme.color.withOpacity(.5),
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,
), ),
), onPressed: () {
), if (ownReactionIndex != -1) {
], removeReaction(
context,
message.ownReactions[ownReactionIndex],
);
} else {
sendReaction(
context,
reactionIcon.type,
);
}
},
);
}).toList(),
),
); );
} }
+1 -1
View File
@@ -24,7 +24,7 @@ dependencies:
file_picker: ^2.0.8+1 file_picker: ^2.0.8+1
image_picker: ^0.6.7+2 image_picker: ^0.6.7+2
flutter_keyboard_visibility: ^3.2.1 flutter_keyboard_visibility: ^3.2.1
stream_chat: ^0.2.10 stream_chat: ^0.2.10+1
mime: ^0.9.6+3 mime: ^0.9.6+3
visibility_detector: ^0.1.5 visibility_detector: ^0.1.5
http_parser: ^3.1.4 http_parser: ^3.1.4