Merge branch 'feature/new-ui' into qa-fix-dj

This commit is contained in:
Salvatore Giordano
2020-10-29 13:11:16 +01:00
18 changed files with 429 additions and 389 deletions
+2 -2
View File
@@ -31,15 +31,15 @@ android {
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
} }
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
checkReleaseBuilds false
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example" applicationId "com.example.example"
minSdkVersion 16 minSdkVersion 21
targetSdkVersion 28 targetSdkVersion 28
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
+1 -1
View File
@@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.0' classpath 'com.android.tools.build:gradle:3.6.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.2' classpath 'com.google.gms:google-services:4.3.2'
} }
+6
View File
@@ -80,6 +80,8 @@ PODS:
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_apns (0.0.1): - flutter_apns (0.0.1):
- Flutter - Flutter
- flutter_app_badger (0.0.1):
- Flutter
- flutter_keyboard_visibility (0.0.1): - flutter_keyboard_visibility (0.0.1):
- Flutter - Flutter
- flutter_local_notifications (0.0.1): - flutter_local_notifications (0.0.1):
@@ -160,6 +162,7 @@ DEPENDENCIES:
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_apns (from `.symlinks/plugins/flutter_apns/ios`) - flutter_apns (from `.symlinks/plugins/flutter_apns/ios`)
- flutter_app_badger (from `.symlinks/plugins/flutter_app_badger/ios`)
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- image_picker (from `.symlinks/plugins/image_picker/ios`) - image_picker (from `.symlinks/plugins/image_picker/ios`)
@@ -207,6 +210,8 @@ EXTERNAL SOURCES:
:path: Flutter :path: Flutter
flutter_apns: flutter_apns:
:path: ".symlinks/plugins/flutter_apns/ios" :path: ".symlinks/plugins/flutter_apns/ios"
flutter_app_badger:
:path: ".symlinks/plugins/flutter_app_badger/ios"
flutter_keyboard_visibility: flutter_keyboard_visibility:
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios" :path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
flutter_local_notifications: flutter_local_notifications:
@@ -244,6 +249,7 @@ SPEC CHECKSUMS:
FirebaseMessaging: 29543feb343b09546ab3aa04d008ee8595b43c44 FirebaseMessaging: 29543feb343b09546ab3aa04d008ee8595b43c44
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
flutter_apns: f516b118e423fe7c0a38771180549c4d6cb67c2f flutter_apns: f516b118e423fe7c0a38771180549c4d6cb67c2f
flutter_app_badger: 65de4d6f0c34a891df49e6cfb8a1c0496426fa68
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
@@ -322,6 +322,7 @@
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework", "${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework", "${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
"${BUILT_PRODUCTS_DIR}/flutter_apns/flutter_apns.framework", "${BUILT_PRODUCTS_DIR}/flutter_apns/flutter_apns.framework",
"${BUILT_PRODUCTS_DIR}/flutter_app_badger/flutter_app_badger.framework",
"${BUILT_PRODUCTS_DIR}/flutter_keyboard_visibility/flutter_keyboard_visibility.framework", "${BUILT_PRODUCTS_DIR}/flutter_keyboard_visibility/flutter_keyboard_visibility.framework",
"${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework", "${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework",
"${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework", "${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework",
@@ -350,6 +351,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_apns.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_apns.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_app_badger.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_keyboard_visibility.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_keyboard_visibility.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework",
+4
View File
@@ -54,5 +54,9 @@
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
</dict> </dict>
</plist> </plist>
+1 -1
View File
@@ -76,7 +76,7 @@ void main() async {
await client.setUser( await client.setUser(
User(id: 'super-band-9', extraData: { User(id: 'super-band-9', extraData: {
'name': 'John Doe', 'name': 'Jonathan Doe',
}), }),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
); );
+1 -1
View File
@@ -1,6 +1,6 @@
name: example name: example
description: A new Flutter project. description: A new Flutter project.
version: 1.0.32+34 version: 1.0.36+38
environment: environment:
sdk: ">=2.2.2 <3.0.0" sdk: ">=2.2.2 <3.0.0"
+85 -71
View File
@@ -43,88 +43,102 @@ class MessageActionsModal extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( final ownId = StreamChat.of(context).user.id;
children: [ return GestureDetector(
Positioned.fill( behavior: HitTestBehavior.translucent,
child: GestureDetector( onTap: () {
behavior: HitTestBehavior.translucent, Navigator.pop(context);
onTap: () { },
Navigator.pop(context); child: Stack(
}, children: [
Positioned.fill(
child: BackdropFilter( child: BackdropFilter(
filter: ImageFilter.blur( filter: ImageFilter.blur(
sigmaX: 10, sigmaX: 10.8731,
sigmaY: 10, sigmaY: 10.8731,
), ),
child: Container( child: Container(
color: Colors.transparent, color: Colors.black.withOpacity(0.2),
), ),
), ),
), ),
), Center(
Column( child: SingleChildScrollView(
mainAxisAlignment: MainAxisAlignment.center, child: Padding(
crossAxisAlignment: CrossAxisAlignment.stretch, padding: const EdgeInsets.symmetric(vertical: 8.0),
children: <Widget>[
if (showReactions &&
(message.status == MessageSendingStatus.SENT ||
message.status == null))
Center(
child: ReactionPicker(
message: message,
messageTheme: messageTheme,
),
),
AbsorbPointer(
child: MessageWidget(
key: Key('MessageWidget'),
reverse: reverse,
message: message.text.length > 200
? message.copyWith(
text: '${message.text.substring(0, 200)}...')
: message,
messageTheme: messageTheme,
showReactions: false,
showUsername: false,
showReplyIndicator: false,
showTimestamp: false,
showSendingIndicator: DisplayWidget.gone,
shape: messageShape,
),
),
SizedBox(
height: 8,
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 48.0,
),
child: Material(
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: ListTile.divideTiles( children: <Widget>[
context: context, if (showReactions &&
tiles: [ (message.status == MessageSendingStatus.SENT ||
if (showReply && message.status == null))
(message.status == MessageSendingStatus.SENT || Center(
message.status == null) && child: ReactionPicker(
message.parentId == null) message: message,
_buildReplyButton(context), messageTheme: messageTheme,
if (showEditMessage) _buildEditMessage(context), ),
if (showDeleteMessage) _buildDeleteButton(context), ),
if (showCopyMessage) _buildCopyButton(context), IgnorePointer(
], child: MessageWidget(
).toList(), key: Key('MessageWidget'),
reverse: reverse,
message: message.copyWith(
text: message.text.length > 200
? '${message.text.substring(0, 200)}...'
: message.text,
),
messageTheme: messageTheme,
showReactions: false,
showUsername: false,
showReplyIndicator: false,
showUserAvatar: message.user.id == ownId
? DisplayWidget.gone
: DisplayWidget.show,
showTimestamp: false,
showSendingIndicator: DisplayWidget.gone,
shape: messageShape,
),
),
SizedBox(
height: 8,
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 48.0,
),
child: Material(
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: ListTile.divideTiles(
context: context,
tiles: [
if (showReply &&
(message.status ==
MessageSendingStatus.SENT ||
message.status == null) &&
message.parentId == null)
_buildReplyButton(context),
if (showEditMessage) _buildEditMessage(context),
if (showDeleteMessage)
_buildDeleteButton(context),
if (showCopyMessage) _buildCopyButton(context),
],
).toList(),
),
),
)
],
), ),
), ),
) ),
], ),
), ],
], ),
); );
} }
+120 -111
View File
@@ -34,66 +34,75 @@ class MessageReactionsModal extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( final ownId = StreamChat.of(context).user.id;
children: [ return GestureDetector(
Positioned.fill( behavior: HitTestBehavior.translucent,
child: GestureDetector( onTap: () {
behavior: HitTestBehavior.translucent, Navigator.pop(context);
onTap: () { },
Navigator.pop(context); child: Stack(
}, children: [
Positioned.fill(
child: BackdropFilter( child: BackdropFilter(
filter: ImageFilter.blur( filter: ImageFilter.blur(
sigmaX: 10, sigmaX: 10.8731,
sigmaY: 10, sigmaY: 10.8731,
), ),
child: Container( child: Container(
color: Colors.transparent, color: Colors.black.withOpacity(0.2),
), ),
), ),
), ),
), Center(
Column( child: SingleChildScrollView(
mainAxisAlignment: MainAxisAlignment.center, child: Padding(
crossAxisAlignment: CrossAxisAlignment.stretch, padding: const EdgeInsets.symmetric(vertical: 8.0),
children: <Widget>[ child: Column(
if (showReactions && mainAxisAlignment: MainAxisAlignment.center,
(message.status == MessageSendingStatus.SENT || crossAxisAlignment: CrossAxisAlignment.stretch,
message.status == null)) children: <Widget>[
Center( if (showReactions &&
child: ReactionPicker( (message.status == MessageSendingStatus.SENT ||
message: message, message.status == null))
messageTheme: messageTheme, Center(
child: ReactionPicker(
message: message,
messageTheme: messageTheme,
),
),
IgnorePointer(
child: MessageWidget(
key: Key('MessageWidget'),
reverse: reverse,
message: message.copyWith(
text: message.text.length > 200
? '${message.text.substring(0, 200)}...'
: message.text,
),
messageTheme: messageTheme,
showReactions: false,
showUsername: false,
showUserAvatar: message.user.id == ownId
? DisplayWidget.gone
: DisplayWidget.show,
showReplyIndicator: false,
showTimestamp: false,
showSendingIndicator: DisplayWidget.gone,
shape: messageShape,
),
),
SizedBox(
height: 16,
),
if (message.latestReactions?.isNotEmpty == true)
_buildReactionCard(context),
],
), ),
), ),
AbsorbPointer(
child: MessageWidget(
key: Key('MessageWidget'),
reverse: reverse,
message: message.text.length > 200
? message.copyWith(
text: '${message.text.substring(0, 200)}...')
: message,
messageTheme: messageTheme,
showReactions: false,
showUsername: false,
showReplyIndicator: false,
showTimestamp: false,
showSendingIndicator: DisplayWidget.gone,
shape: messageShape,
),
), ),
SizedBox( ),
height: 16, ],
), ),
if (message.latestReactions?.isNotEmpty == true)
Container(
constraints: BoxConstraints.loose(Size.fromHeight(400)),
child: _buildReactionCard(context),
),
],
),
],
); );
} }
@@ -119,30 +128,25 @@ class MessageReactionsModal extends StatelessWidget {
), ),
), ),
Flexible( Flexible(
child: Padding( child: SingleChildScrollView(
padding: const EdgeInsets.only( child: Padding(
left: 16.0, padding: const EdgeInsets.only(
right: 16, left: 18,
bottom: 16, right: 18,
), bottom: 26,
child: GridView.builder( ),
shrinkWrap: true, child: Wrap(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( spacing: 16,
crossAxisCount: 4, runSpacing: 22,
crossAxisSpacing: 16, alignment: WrapAlignment.start,
childAspectRatio: 0.75, children: message.latestReactions
mainAxisSpacing: 22, .map((e) => _buildReaction(
e,
currentUser,
context,
))
.toList(),
), ),
itemCount: message.latestReactions.length,
itemBuilder: (context, i) {
final reaction = message.latestReactions[i];
return _buildReaction(
reaction,
currentUser,
context,
);
},
), ),
), ),
), ),
@@ -152,51 +156,56 @@ class MessageReactionsModal extends StatelessWidget {
); );
} }
Column _buildReaction( Widget _buildReaction(
Reaction reaction, Reaction reaction,
User currentUser, User currentUser,
BuildContext context, BuildContext context,
) { ) {
final isCurrentUser = reaction.user.id == currentUser.id; final isCurrentUser = reaction.user.id == currentUser.id;
return Column( return ConstrainedBox(
mainAxisSize: MainAxisSize.min, constraints: BoxConstraints.loose(Size(
mainAxisAlignment: MainAxisAlignment.start, 64,
crossAxisAlignment: CrossAxisAlignment.center, 98,
children: [ )),
Stack( child: Column(
children: [ mainAxisSize: MainAxisSize.min,
UserAvatar( mainAxisAlignment: MainAxisAlignment.start,
onTap: onUserAvatarTap, crossAxisAlignment: CrossAxisAlignment.center,
user: reaction.user, children: [
constraints: BoxConstraints.tightFor( Stack(
height: 64, children: [
width: 64, UserAvatar(
onTap: onUserAvatarTap,
user: reaction.user,
constraints: BoxConstraints.tightFor(
height: 64,
width: 64,
),
borderRadius: BorderRadius.circular(32),
), ),
borderRadius: BorderRadius.circular(32), Positioned(
), child: Align(
Positioned( alignment: Alignment.centerLeft,
child: ReactionBubble( child: ReactionBubble(
reactions: [reaction], reactions: [reaction],
borderColor: isCurrentUser borderColor: messageTheme.reactionsBorderColor,
? messageTheme.ownReactionsBorderColor backgroundColor: messageTheme.reactionsBackgroundColor,
: messageTheme.otherReactionsBorderColor, highlightOwnReactions: false,
backgroundColor: isCurrentUser ),
? messageTheme.ownReactionsBackgroundColor ),
: messageTheme.otherReactionsBackgroundColor, bottom: 4,
flipTail: !isCurrentUser, left: isCurrentUser ? 0 : null,
right: isCurrentUser ? 0 : null,
), ),
bottom: 0, ],
left: isCurrentUser ? 0 : null, ),
right: isCurrentUser ? 0 : null, Text(
), reaction.user.name,
], style: Theme.of(context).textTheme.subtitle2,
), textAlign: TextAlign.center,
Text( ),
reaction.user.name, ],
style: Theme.of(context).textTheme.subtitle2, ),
textAlign: TextAlign.center,
),
],
); );
} }
} }
+1 -1
View File
@@ -24,7 +24,7 @@ class MessageText extends StatelessWidget {
final text = _replaceMentions(message.text); final text = _replaceMentions(message.text);
return MarkdownBody( return MarkdownBody(
data: text, data: text,
onTapLink: (link) { onTapLink: (text, link, title) {
if (link.startsWith('@')) { if (link.startsWith('@')) {
final mentionedUser = message.mentionedUsers.firstWhere( final mentionedUser = message.mentionedUsers.firstWhere(
(u) => '@${u.name.replaceAll(' ', '')}' == link, (u) => '@${u.name.replaceAll(' ', '')}' == link,
+43 -81
View File
@@ -239,20 +239,24 @@ class _MessageWidgetState extends State<MessageWidget> {
8, 8,
), ),
Flexible( Flexible(
child: Padding( child: PortalEntry(
padding: widget.showReactions portal: Container(
? EdgeInsets.only( transform: Matrix4.translationValues(-16, 2, 0),
top: widget.message.reactionCounts child: _buildReactionIndicator(context),
?.isNotEmpty == constraints: BoxConstraints(maxWidth: 22 * 6.0),
true ),
? 12 portalAnchor: Alignment(-1.0, -1.0),
: 0, childAnchor: Alignment(1, -1.0),
) child: Padding(
: EdgeInsets.zero, padding: widget.showReactions
child: PortalEntry( ? EdgeInsets.only(
portalAnchor: Alignment(-0.81, 0), top: widget.message.reactionCounts
childAnchor: Alignment.topRight, ?.isNotEmpty ==
portal: _buildReactionIndicator(context), true
? 18
: 0,
)
: EdgeInsets.zero,
child: (widget.message.isDeleted && child: (widget.message.isDeleted &&
widget.message.status != widget.message.status !=
MessageSendingStatus.FAILED_DELETE) MessageSendingStatus.FAILED_DELETE)
@@ -378,75 +382,33 @@ class _MessageWidgetState extends State<MessageWidget> {
); );
} }
Widget _buildReactionIndicator(BuildContext context) { Widget _buildReactionIndicator(
final otherReactions = widget.message.latestReactions BuildContext context,
?.where( ) {
(element) => element.user.id != StreamChat.of(context).user.id) final ownId = StreamChat.of(context).user.id;
?.toList() ?? final reactionsMap = <String, Reaction>{};
[]; widget.message.latestReactions?.forEach((element) {
if (!reactionsMap.containsKey(element.type) || element.user.id == ownId) {
reactionsMap[element.type] = element;
}
});
final reactionsList = reactionsMap.values.toList()
..sort((a, b) => a.user.id == ownId ? 1 : -1);
var rowChildren = [
if (widget.message.ownReactions?.isNotEmpty == true) ...[
Transform.translate(
offset: Offset(
widget.reverse ? -6 : 0,
0,
),
child: ReactionBubble(
key: ValueKey('${widget.message.id}.own'),
reverse: widget.reverse,
backgroundColor: widget.messageTheme.ownReactionsBackgroundColor,
borderColor: widget.messageTheme.ownReactionsBorderColor,
reactions: widget.message.ownReactions,
),
),
if (otherReactions.isEmpty)
Container(
width: 18,
),
],
if (otherReactions?.isNotEmpty == true) ...[
if (widget.message.ownReactions?.isEmpty == true)
Container(
width: 18,
),
Transform.translate(
offset: Offset(
widget.reverse ? 0 : -6,
0,
),
child: ReactionBubble(
key: ValueKey('${widget.message.id}.other'),
reactions: otherReactions,
reverse: widget.reverse,
flipTail: true,
backgroundColor: widget.messageTheme.otherReactionsBackgroundColor,
borderColor: widget.messageTheme.otherReactionsBorderColor,
),
),
]
];
if (widget.reverse) {
rowChildren = rowChildren.reversed.toList();
}
return AnimatedSwitcher( return AnimatedSwitcher(
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
child: (widget.showReactions && child: (widget.showReactions &&
(widget.message.reactionCounts?.isNotEmpty == true || (widget.message.reactionCounts?.isNotEmpty == true) &&
otherReactions.isNotEmpty) &&
!widget.message.isDeleted) !widget.message.isDeleted)
? Container( ? GestureDetector(
child: GestureDetector( onTap: () => _showMessageReactionsModalBottomSheet(context),
onTap: () => _showMessageReactionsModalBottomSheet(context), child: ReactionBubble(
child: FractionallySizedBox( key: ValueKey('${widget.message.id}.reactions'),
widthFactor: 0.5, reverse: widget.reverse,
child: Row( flipTail: widget.reverse,
mainAxisAlignment: MainAxisAlignment.start, backgroundColor: widget.messageTheme.reactionsBackgroundColor,
mainAxisSize: MainAxisSize.min, borderColor: widget.messageTheme.reactionsBorderColor,
children: rowChildren, reactions: reactionsList,
),
),
), ),
) )
: SizedBox(), : SizedBox(),
@@ -498,8 +460,8 @@ class _MessageWidgetState extends State<MessageWidget> {
}); });
} }
ContinuousRectangleBorder _getDefaultShape(BuildContext context) { ShapeBorder _getDefaultShape(BuildContext context) {
return ContinuousRectangleBorder( return RoundedRectangleBorder(
side: widget.attachmentBorderSide ?? side: widget.attachmentBorderSide ??
widget.borderSide ?? widget.borderSide ??
BorderSide( BorderSide(
@@ -718,7 +680,7 @@ class _MessageWidgetState extends State<MessageWidget> {
onLongPress: () => onLongPress(context), onLongPress: () => onLongPress(context),
child: Material( child: Material(
shape: widget.shape ?? shape: widget.shape ??
ContinuousRectangleBorder( RoundedRectangleBorder(
side: widget.borderSide ?? side: widget.borderSide ??
BorderSide( BorderSide(
color: Theme.of(context).brightness == Brightness.dark color: Theme.of(context).brightness == Brightness.dark
+89 -55
View File
@@ -1,6 +1,8 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:stream_chat_flutter/src/reaction_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class ReactionBubble extends StatelessWidget { class ReactionBubble extends StatelessWidget {
@@ -11,6 +13,7 @@ class ReactionBubble extends StatelessWidget {
@required this.backgroundColor, @required this.backgroundColor,
this.reverse = false, this.reverse = false,
this.flipTail = false, this.flipTail = false,
this.highlightOwnReactions = true,
}) : super(key: key); }) : super(key: key);
final List<Reaction> reactions; final List<Reaction> reactions;
@@ -18,67 +21,100 @@ class ReactionBubble extends StatelessWidget {
final Color backgroundColor; final Color backgroundColor;
final bool reverse; final bool reverse;
final bool flipTail; final bool flipTail;
final bool highlightOwnReactions;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final reactionIcons = StreamChatTheme.of(context).reactionIcons; final reactionIcons = StreamChatTheme.of(context).reactionIcons;
final offset = reactions.length > 1 ? 16.0 : 2.0;
return Transform( return Transform(
transform: Matrix4.rotationY(reverse ? pi : 0), transform: Matrix4.rotationY(reverse ? pi : 0),
alignment: Alignment.center, alignment: Alignment.center,
child: Column( child: Stack(
crossAxisAlignment: alignment: Alignment.center,
flipTail ? CrossAxisAlignment.start : CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( Transform.translate(
padding: const EdgeInsets.all(4), offset: Offset(reverse ? offset : -offset, 0),
decoration: BoxDecoration( child: Container(
border: Border.all( padding: const EdgeInsets.symmetric(vertical: 4),
color: borderColor, decoration: BoxDecoration(
border: Border.all(
color: borderColor,
),
color: backgroundColor,
borderRadius: BorderRadius.all(Radius.circular(14)),
), ),
color: backgroundColor, child: LayoutBuilder(
borderRadius: BorderRadius.all(Radius.circular(14)), builder: (context, constraints) {
), return Flex(
child: Wrap( direction: Axis.horizontal,
children: [ mainAxisSize: MainAxisSize.min,
...reactions.map((reaction) { children: [
final reactionIcon = reactionIcons.firstWhere( if (constraints.maxWidth < double.infinity)
(r) => r.type == reaction.type, ...reactions
orElse: () => null, .take((constraints.maxWidth) ~/ 22)
.map((reaction) {
return _buildReaction(
reactionIcons,
reaction,
context,
);
}).toList(),
if (constraints.maxWidth == double.infinity)
...reactions.map((reaction) {
return _buildReaction(
reactionIcons,
reaction,
context,
);
}).toList(),
],
); );
if (reactionIcon == null) { },
return Text( ),
'?',
style: TextStyle(
color: StreamChatTheme.of(context).accentColor,
),
);
}
return Icon(
reactionIcon.iconData,
size: 16,
color: StreamChatTheme.of(context).accentColor,
);
}).toList(),
],
), ),
), ),
_buildReactionsTail(context), Positioned(
bottom: 0,
left: reverse ? null : 11,
right: !reverse ? null : 11,
child: _buildReactionsTail(context),
),
], ],
), ),
); );
} }
Widget _buildReaction(
List<ReactionIcon> reactionIcons,
Reaction reaction,
BuildContext context,
) {
final reactionIcon = reactionIcons.firstWhere(
(r) => r.type == reaction.type,
orElse: () => null,
);
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4.0,
),
child: Icon(
reactionIcon?.iconData ?? Icons.help_outline_rounded,
size: 16,
color: (!highlightOwnReactions ||
reaction.user.id == StreamChat.of(context).user.id)
? StreamChatTheme.of(context).accentColor
: Colors.black.withOpacity(.5),
),
);
}
Widget _buildReactionsTail(BuildContext context) { Widget _buildReactionsTail(BuildContext context) {
final tail = Transform.translate( final tail = CustomPaint(
offset: Offset(reactions.length > 1 ? -9 : -9, 0), painter: ReactionBubblePainter(
child: CustomPaint( backgroundColor,
painter: ReactionBubblePainter( borderColor,
backgroundColor,
borderColor,
reactions.length,
),
), ),
); );
@@ -97,12 +133,10 @@ class ReactionBubble extends StatelessWidget {
class ReactionBubblePainter extends CustomPainter { class ReactionBubblePainter extends CustomPainter {
final Color color; final Color color;
final Color borderColor; final Color borderColor;
final int reactionsCount;
ReactionBubblePainter( ReactionBubblePainter(
this.color, this.color,
this.borderColor, this.borderColor,
this.reactionsCount,
); );
@override @override
@@ -125,7 +159,7 @@ class ReactionBubblePainter extends CustomPainter {
final path = Path(); final path = Path();
path.addOval( path.addOval(
Rect.fromCircle( Rect.fromCircle(
center: Offset(6, 2), center: Offset(4, 3),
radius: 2, radius: 2,
), ),
); );
@@ -139,7 +173,7 @@ class ReactionBubblePainter extends CustomPainter {
final path = Path(); final path = Path();
path.addOval(Rect.fromCircle( path.addOval(Rect.fromCircle(
center: Offset(6, 2), center: Offset(4, 3),
radius: 2, radius: 2,
)); ));
canvas.drawPath(path, paint); canvas.drawPath(path, paint);
@@ -151,13 +185,13 @@ class ReactionBubblePainter extends CustomPainter {
..strokeWidth = 1 ..strokeWidth = 1
..style = PaintingStyle.stroke; ..style = PaintingStyle.stroke;
final dy = reactionsCount > 1 ? -2.0 : -3.0; final dy = -2.2;
final startAngle = reactionsCount > 1 ? 1.08 : 1.16; final startAngle = 1.1;
final sweepAngle = reactionsCount > 1 ? 0.95 : 1.1; final sweepAngle = 1.2;
final path = Path(); final path = Path();
path.addArc( path.addArc(
Rect.fromCircle( Rect.fromCircle(
center: Offset(0, dy), center: Offset(1, dy),
radius: 4, radius: 4,
), ),
-pi * startAngle, -pi * startAngle,
@@ -171,13 +205,13 @@ class ReactionBubblePainter extends CustomPainter {
..color = color ..color = color
..strokeWidth = 1; ..strokeWidth = 1;
final dy = reactionsCount > 1 ? -2.0 : -3.0; final dy = -2.2;
final startAngle = reactionsCount > 1 ? 1 : 1.16; final startAngle = 1;
final sweepAngle = reactionsCount > 1 ? 1.2 : 1; final sweepAngle = 1.3;
final path = Path(); final path = Path();
path.addArc( path.addArc(
Rect.fromCircle( Rect.fromCircle(
center: Offset(0, dy), center: Offset(1, dy),
radius: 4, radius: 4,
), ),
-pi * startAngle, -pi * startAngle,
+4 -5
View File
@@ -26,7 +26,7 @@ class ReactionPicker extends StatelessWidget {
fit: StackFit.passthrough, fit: StackFit.passthrough,
children: [ children: [
Material( Material(
color: messageTheme.ownReactionsBackgroundColor, color: messageTheme.reactionsBackgroundColor,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
@@ -45,7 +45,7 @@ class ReactionPicker extends StatelessWidget {
reactionIcon.iconData, reactionIcon.iconData,
color: ownReactionIndex != -1 color: ownReactionIndex != -1
? StreamChatTheme.of(context).accentColor ? StreamChatTheme.of(context).accentColor
: Theme.of(context).iconTheme.color, : Theme.of(context).iconTheme.color.withOpacity(.5),
), ),
onPressed: () { onPressed: () {
if (ownReactionIndex != -1) { if (ownReactionIndex != -1) {
@@ -69,9 +69,8 @@ class ReactionPicker extends StatelessWidget {
bottom: 0, bottom: 0,
child: CustomPaint( child: CustomPaint(
painter: ReactionBubblePainter( painter: ReactionBubblePainter(
messageTheme.ownReactionsBackgroundColor, messageTheme.reactionsBackgroundColor,
messageTheme.ownReactionsBorderColor, messageTheme.reactionsBorderColor,
2,
), ),
), ),
), ),
+15 -16
View File
@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_app_badger/flutter_app_badger.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
@@ -129,14 +130,9 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
constraints: themeData?.ownMessageTheme?.avatarTheme?.constraints, constraints: themeData?.ownMessageTheme?.avatarTheme?.constraints,
borderRadius: themeData?.ownMessageTheme?.avatarTheme?.borderRadius, borderRadius: themeData?.ownMessageTheme?.avatarTheme?.borderRadius,
), ),
otherReactionsBorderColor: reactionsBorderColor: themeData?.ownMessageTheme?.reactionsBorderColor,
themeData?.ownMessageTheme?.otherReactionsBorderColor, reactionsBackgroundColor:
otherReactionsBackgroundColor: themeData?.ownMessageTheme?.reactionsBackgroundColor,
themeData?.ownMessageTheme?.otherReactionsBackgroundColor,
ownReactionsBackgroundColor:
themeData?.ownMessageTheme?.ownReactionsBackgroundColor,
ownReactionsBorderColor:
themeData?.ownMessageTheme?.ownReactionsBorderColor,
), ),
otherMessageTheme: defaultTheme.otherMessageTheme.copyWith( otherMessageTheme: defaultTheme.otherMessageTheme.copyWith(
replies: themeData?.otherMessageTheme?.replies, replies: themeData?.otherMessageTheme?.replies,
@@ -150,14 +146,10 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
constraints: themeData?.otherMessageTheme?.avatarTheme?.constraints, constraints: themeData?.otherMessageTheme?.avatarTheme?.constraints,
borderRadius: themeData?.otherMessageTheme?.avatarTheme?.borderRadius, borderRadius: themeData?.otherMessageTheme?.avatarTheme?.borderRadius,
), ),
otherReactionsBorderColor: reactionsBorderColor:
themeData?.otherMessageTheme?.otherReactionsBorderColor, themeData?.otherMessageTheme?.reactionsBorderColor,
otherReactionsBackgroundColor: reactionsBackgroundColor:
themeData?.otherMessageTheme?.otherReactionsBackgroundColor, themeData?.otherMessageTheme?.reactionsBackgroundColor,
ownReactionsBackgroundColor:
themeData?.otherMessageTheme?.ownReactionsBackgroundColor,
ownReactionsBorderColor:
themeData?.otherMessageTheme?.ownReactionsBorderColor,
), ),
accentColor: themeData?.accentColor, accentColor: themeData?.accentColor,
secondaryColor: themeData?.secondaryColor, secondaryColor: themeData?.secondaryColor,
@@ -186,6 +178,13 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
client.state?.totalUnreadCountStream?.listen((count) {
if (count > 0) {
FlutterAppBadger.updateBadgeCount(count);
} else {
FlutterAppBadger.removeBadge();
}
});
} }
StreamSubscription _newMessageSubscription; StreamSubscription _newMessageSubscription;
+17 -35
View File
@@ -220,8 +220,8 @@ class StreamChatThemeData {
return StreamChatThemeData( return StreamChatThemeData(
accentColor: accentColor, accentColor: accentColor,
primaryColor: isDark ? Colors.black : Colors.white, primaryColor: isDark ? Colors.black : Colors.white,
primaryIconTheme: primaryIconTheme: IconThemeData(
IconThemeData(color: isDark ? Colors.white : Colors.black), color: isDark ? Colors.white : Colors.black.withOpacity(.5)),
defaultChannelImage: (context, channel) => SizedBox(), defaultChannelImage: (context, channel) => SizedBox(),
backgroundColor: isDark ? Colors.black : Colors.white, backgroundColor: isDark ? Colors.black : Colors.white,
defaultUserImage: (context, user) => Center( defaultUserImage: (context, user) => Center(
@@ -287,7 +287,7 @@ class StreamChatThemeData {
), ),
ownMessageTheme: MessageTheme( ownMessageTheme: MessageTheme(
messageText: TextStyle( messageText: TextStyle(
fontSize: 15, fontSize: 14.5,
color: isDark ? Colors.white : Colors.black, color: isDark ? Colors.white : Colors.black,
), ),
createdAt: TextStyle( createdAt: TextStyle(
@@ -302,12 +302,8 @@ class StreamChatThemeData {
fontSize: 12, fontSize: 12,
), ),
messageBackgroundColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA), messageBackgroundColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA),
ownReactionsBackgroundColor: reactionsBackgroundColor: isDark ? Colors.black : Colors.white,
isDark ? Color(0xff191919) : Color(0xffEAEAEA), reactionsBorderColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA),
ownReactionsBorderColor: isDark ? Colors.black : Colors.white,
otherReactionsBackgroundColor: isDark ? Colors.black : Colors.white,
otherReactionsBorderColor:
isDark ? Color(0xff191919) : Color(0xffEAEAEA),
avatarTheme: AvatarTheme( avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
constraints: BoxConstraints.tightFor( constraints: BoxConstraints.tightFor(
@@ -320,14 +316,11 @@ class StreamChatThemeData {
), ),
), ),
otherMessageTheme: MessageTheme( otherMessageTheme: MessageTheme(
ownReactionsBackgroundColor: reactionsBackgroundColor:
isDark ? Color(0xff191919) : Color(0xffEAEAEA),
ownReactionsBorderColor: isDark ? Colors.black : Colors.white,
otherReactionsBackgroundColor: isDark ? Colors.black : Colors.white,
otherReactionsBorderColor:
isDark ? Color(0xff191919) : Color(0xffEAEAEA), isDark ? Color(0xff191919) : Color(0xffEAEAEA),
reactionsBorderColor: isDark ? Colors.black : Colors.white,
messageText: TextStyle( messageText: TextStyle(
fontSize: 15, fontSize: 14.5,
color: isDark ? Colors.white : Colors.black, color: isDark ? Colors.white : Colors.black,
), ),
createdAt: TextStyle( createdAt: TextStyle(
@@ -457,10 +450,8 @@ class MessageTheme {
final TextStyle createdAt; final TextStyle createdAt;
final TextStyle replies; final TextStyle replies;
final Color messageBackgroundColor; final Color messageBackgroundColor;
final Color ownReactionsBackgroundColor; final Color reactionsBackgroundColor;
final Color ownReactionsBorderColor; final Color reactionsBorderColor;
final Color otherReactionsBackgroundColor;
final Color otherReactionsBorderColor;
final AvatarTheme avatarTheme; final AvatarTheme avatarTheme;
const MessageTheme({ const MessageTheme({
@@ -469,10 +460,8 @@ class MessageTheme {
this.messageAuthor, this.messageAuthor,
this.messageLinks, this.messageLinks,
this.messageBackgroundColor, this.messageBackgroundColor,
this.ownReactionsBackgroundColor, this.reactionsBackgroundColor,
this.ownReactionsBorderColor, this.reactionsBorderColor,
this.otherReactionsBackgroundColor,
this.otherReactionsBorderColor,
this.avatarTheme, this.avatarTheme,
this.createdAt, this.createdAt,
}); });
@@ -485,10 +474,8 @@ class MessageTheme {
TextStyle replies, TextStyle replies,
Color messageBackgroundColor, Color messageBackgroundColor,
AvatarTheme avatarTheme, AvatarTheme avatarTheme,
Color ownReactionsBackgroundColor, Color reactionsBackgroundColor,
Color ownReactionsBorderColor, Color reactionsBorderColor,
Color otherReactionsBackgroundColor,
Color otherReactionsBorderColor,
}) => }) =>
MessageTheme( MessageTheme(
messageText: messageText ?? this.messageText, messageText: messageText ?? this.messageText,
@@ -499,14 +486,9 @@ class MessageTheme {
messageBackgroundColor ?? this.messageBackgroundColor, messageBackgroundColor ?? this.messageBackgroundColor,
avatarTheme: avatarTheme ?? this.avatarTheme, avatarTheme: avatarTheme ?? this.avatarTheme,
replies: replies ?? this.replies, replies: replies ?? this.replies,
ownReactionsBackgroundColor: reactionsBackgroundColor:
ownReactionsBackgroundColor ?? this.ownReactionsBackgroundColor, reactionsBackgroundColor ?? this.reactionsBackgroundColor,
ownReactionsBorderColor: reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor,
ownReactionsBorderColor ?? this.ownReactionsBorderColor,
otherReactionsBackgroundColor:
otherReactionsBackgroundColor ?? this.otherReactionsBackgroundColor,
otherReactionsBorderColor:
otherReactionsBorderColor ?? this.otherReactionsBorderColor,
); );
} }
+4 -3
View File
@@ -11,16 +11,17 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_app_badger: ^1.1.2
photo_view: ^0.10.1 photo_view: ^0.10.1
rxdart: ^0.24.1 rxdart: ^0.24.1
jiffy: ^3.0.1 jiffy: ^3.0.1
flutter_portal: ^0.3.0 flutter_portal: ^0.3.0
cached_network_image: ^2.2.0+1 cached_network_image: ^2.2.0+1
flutter_markdown: ^0.4.2 flutter_markdown: ^0.5.0
url_launcher: ^5.4.11 url_launcher: ^5.4.11
video_player: ^0.10.11+1 video_player: ^0.10.12+5
chewie: ^0.9.10 chewie: ^0.9.10
file_picker: ^2.0.0 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
+10 -2
View File
@@ -10,13 +10,21 @@ void main() {
testWidgets( testWidgets(
'it should show one thumbs from the picker', 'it should show one thumbs from the picker',
(WidgetTester tester) async { (WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final themeData = ThemeData(); final themeData = ThemeData();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: themeData, theme: themeData,
home: StreamChatTheme( home: StreamChat(
data: streamTheme, client: client,
streamChatThemeData: streamTheme,
child: MessageReactionsModal( child: MessageReactionsModal(
message: Message( message: Message(
id: 'test', id: 'test',
+24 -4
View File
@@ -1,8 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() { void main() {
testWidgets( testWidgets(
'it should show no reactions', 'it should show no reactions',
@@ -29,17 +32,25 @@ void main() {
testWidgets( testWidgets(
'it should show a thumb up', 'it should show a thumb up',
(WidgetTester tester) async { (WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final themeData = ThemeData(); final themeData = ThemeData();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: themeData, theme: themeData,
home: StreamChatTheme( home: StreamChat(
data: StreamChatThemeData.getDefaultTheme(themeData), client: client,
streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData),
child: Container( child: Container(
child: ReactionBubble( child: ReactionBubble(
reactions: [ reactions: [
Reaction( Reaction(
type: 'thumbs_up', type: 'thumbs_up',
user: User(id: 'test'),
), ),
], ],
borderColor: Colors.black, borderColor: Colors.black,
@@ -56,20 +67,29 @@ void main() {
testWidgets( testWidgets(
'it should show two reactions', 'it should show two reactions',
(WidgetTester tester) async { (WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final themeData = ThemeData(); final themeData = ThemeData();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: themeData, theme: themeData,
home: StreamChatTheme( home: StreamChat(
data: StreamChatThemeData.getDefaultTheme(themeData), client: client,
streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData),
child: Container( child: Container(
child: ReactionBubble( child: ReactionBubble(
reactions: [ reactions: [
Reaction( Reaction(
type: 'thumbs_up', type: 'thumbs_up',
user: User(id: 'test'),
), ),
Reaction( Reaction(
type: 'love', type: 'love',
user: User(id: 'test'),
), ),
], ],
borderColor: Colors.black, borderColor: Colors.black,