diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index dda667cf..474bfd75 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -31,15 +31,15 @@ android {
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
-
lintOptions {
disable 'InvalidPackage'
+ checkReleaseBuilds false
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
- minSdkVersion 16
+ minSdkVersion 21
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
diff --git a/example/android/build.gradle b/example/android/build.gradle
index e1cfea50..8c57e18a 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -6,7 +6,7 @@ buildscript {
}
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 'com.google.gms:google-services:4.3.2'
}
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index d22f1e0d..1e268608 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -80,6 +80,8 @@ PODS:
- Flutter (1.0.0)
- flutter_apns (0.0.1):
- Flutter
+ - flutter_app_badger (0.0.1):
+ - Flutter
- flutter_keyboard_visibility (0.0.1):
- Flutter
- flutter_local_notifications (0.0.1):
@@ -160,6 +162,7 @@ DEPENDENCIES:
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- Flutter (from `Flutter`)
- 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_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- image_picker (from `.symlinks/plugins/image_picker/ios`)
@@ -207,6 +210,8 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_apns:
:path: ".symlinks/plugins/flutter_apns/ios"
+ flutter_app_badger:
+ :path: ".symlinks/plugins/flutter_app_badger/ios"
flutter_keyboard_visibility:
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
flutter_local_notifications:
@@ -244,6 +249,7 @@ SPEC CHECKSUMS:
FirebaseMessaging: 29543feb343b09546ab3aa04d008ee8595b43c44
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
flutter_apns: f516b118e423fe7c0a38771180549c4d6cb67c2f
+ flutter_app_badger: 65de4d6f0c34a891df49e6cfb8a1c0496426fa68
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index db80aaed..6d377561 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -322,6 +322,7 @@
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.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_local_notifications/flutter_local_notifications.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}/file_picker.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_local_notifications.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework",
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index ea713691..6c2c8e27 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -54,5 +54,9 @@
UIViewControllerBasedStatusBarAppearance
+ UIBackgroundModes
+
+ remote-notification
+
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 89ed9381..667b1e0f 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -76,7 +76,7 @@ void main() async {
await client.setUser(
User(id: 'super-band-9', extraData: {
- 'name': 'John Doe',
+ 'name': 'Jonathan Doe',
}),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
);
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index cb0eca8e..c73804eb 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -1,6 +1,6 @@
name: example
description: A new Flutter project.
-version: 1.0.32+34
+version: 1.0.36+38
environment:
sdk: ">=2.2.2 <3.0.0"
diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart
index ce680d34..93e1c0db 100644
--- a/lib/src/message_actions_modal.dart
+++ b/lib/src/message_actions_modal.dart
@@ -43,88 +43,102 @@ class MessageActionsModal extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Stack(
- children: [
- Positioned.fill(
- child: GestureDetector(
- behavior: HitTestBehavior.translucent,
- onTap: () {
- Navigator.pop(context);
- },
+ final ownId = StreamChat.of(context).user.id;
+ return GestureDetector(
+ behavior: HitTestBehavior.translucent,
+ onTap: () {
+ Navigator.pop(context);
+ },
+ child: Stack(
+ children: [
+ Positioned.fill(
child: BackdropFilter(
filter: ImageFilter.blur(
- sigmaX: 10,
- sigmaY: 10,
+ sigmaX: 10.8731,
+ sigmaY: 10.8731,
),
child: Container(
- color: Colors.transparent,
+ color: Colors.black.withOpacity(0.2),
),
),
),
- ),
- Column(
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- 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),
- ),
+ Center(
+ child: SingleChildScrollView(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
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(),
+ children: [
+ if (showReactions &&
+ (message.status == MessageSendingStatus.SENT ||
+ message.status == null))
+ 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,
+ 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(),
+ ),
+ ),
+ )
+ ],
),
),
- )
- ],
- ),
- ],
+ ),
+ ),
+ ],
+ ),
);
}
diff --git a/lib/src/message_reactions_modal.dart b/lib/src/message_reactions_modal.dart
index b30f92b3..375da94e 100644
--- a/lib/src/message_reactions_modal.dart
+++ b/lib/src/message_reactions_modal.dart
@@ -34,66 +34,75 @@ class MessageReactionsModal extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Stack(
- children: [
- Positioned.fill(
- child: GestureDetector(
- behavior: HitTestBehavior.translucent,
- onTap: () {
- Navigator.pop(context);
- },
+ final ownId = StreamChat.of(context).user.id;
+ return GestureDetector(
+ behavior: HitTestBehavior.translucent,
+ onTap: () {
+ Navigator.pop(context);
+ },
+ child: Stack(
+ children: [
+ Positioned.fill(
child: BackdropFilter(
filter: ImageFilter.blur(
- sigmaX: 10,
- sigmaY: 10,
+ sigmaX: 10.8731,
+ sigmaY: 10.8731,
),
child: Container(
- color: Colors.transparent,
+ color: Colors.black.withOpacity(0.2),
),
),
),
- ),
- Column(
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- if (showReactions &&
- (message.status == MessageSendingStatus.SENT ||
- message.status == null))
- Center(
- child: ReactionPicker(
- message: message,
- messageTheme: messageTheme,
+ Center(
+ child: SingleChildScrollView(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ if (showReactions &&
+ (message.status == MessageSendingStatus.SENT ||
+ message.status == null))
+ 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(
- child: Padding(
- padding: const EdgeInsets.only(
- left: 16.0,
- right: 16,
- bottom: 16,
- ),
- child: GridView.builder(
- shrinkWrap: true,
- gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 4,
- crossAxisSpacing: 16,
- childAspectRatio: 0.75,
- mainAxisSpacing: 22,
+ child: SingleChildScrollView(
+ child: Padding(
+ padding: const EdgeInsets.only(
+ left: 18,
+ right: 18,
+ bottom: 26,
+ ),
+ child: Wrap(
+ spacing: 16,
+ runSpacing: 22,
+ alignment: WrapAlignment.start,
+ children: message.latestReactions
+ .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,
User currentUser,
BuildContext context,
) {
final isCurrentUser = reaction.user.id == currentUser.id;
- return Column(
- mainAxisSize: MainAxisSize.min,
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Stack(
- children: [
- UserAvatar(
- onTap: onUserAvatarTap,
- user: reaction.user,
- constraints: BoxConstraints.tightFor(
- height: 64,
- width: 64,
+ return ConstrainedBox(
+ constraints: BoxConstraints.loose(Size(
+ 64,
+ 98,
+ )),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Stack(
+ children: [
+ UserAvatar(
+ onTap: onUserAvatarTap,
+ user: reaction.user,
+ constraints: BoxConstraints.tightFor(
+ height: 64,
+ width: 64,
+ ),
+ borderRadius: BorderRadius.circular(32),
),
- borderRadius: BorderRadius.circular(32),
- ),
- Positioned(
- child: ReactionBubble(
- reactions: [reaction],
- borderColor: isCurrentUser
- ? messageTheme.ownReactionsBorderColor
- : messageTheme.otherReactionsBorderColor,
- backgroundColor: isCurrentUser
- ? messageTheme.ownReactionsBackgroundColor
- : messageTheme.otherReactionsBackgroundColor,
- flipTail: !isCurrentUser,
+ Positioned(
+ child: Align(
+ alignment: Alignment.centerLeft,
+ child: ReactionBubble(
+ reactions: [reaction],
+ borderColor: messageTheme.reactionsBorderColor,
+ backgroundColor: messageTheme.reactionsBackgroundColor,
+ highlightOwnReactions: false,
+ ),
+ ),
+ bottom: 4,
+ 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,
+ ),
+ ],
+ ),
);
}
}
diff --git a/lib/src/message_text.dart b/lib/src/message_text.dart
index 2ee6a6f8..ed636a57 100644
--- a/lib/src/message_text.dart
+++ b/lib/src/message_text.dart
@@ -24,7 +24,7 @@ class MessageText extends StatelessWidget {
final text = _replaceMentions(message.text);
return MarkdownBody(
data: text,
- onTapLink: (link) {
+ onTapLink: (text, link, title) {
if (link.startsWith('@')) {
final mentionedUser = message.mentionedUsers.firstWhere(
(u) => '@${u.name.replaceAll(' ', '')}' == link,
diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart
index 967fb72b..0e703fa0 100644
--- a/lib/src/message_widget.dart
+++ b/lib/src/message_widget.dart
@@ -239,20 +239,24 @@ class _MessageWidgetState extends State {
8,
),
Flexible(
- child: Padding(
- padding: widget.showReactions
- ? EdgeInsets.only(
- top: widget.message.reactionCounts
- ?.isNotEmpty ==
- true
- ? 12
- : 0,
- )
- : EdgeInsets.zero,
- child: PortalEntry(
- portalAnchor: Alignment(-0.81, 0),
- childAnchor: Alignment.topRight,
- portal: _buildReactionIndicator(context),
+ child: PortalEntry(
+ portal: Container(
+ transform: Matrix4.translationValues(-16, 2, 0),
+ child: _buildReactionIndicator(context),
+ constraints: BoxConstraints(maxWidth: 22 * 6.0),
+ ),
+ 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)
@@ -378,75 +382,33 @@ class _MessageWidgetState extends State {
);
}
- Widget _buildReactionIndicator(BuildContext context) {
- final otherReactions = widget.message.latestReactions
- ?.where(
- (element) => element.user.id != StreamChat.of(context).user.id)
- ?.toList() ??
- [];
+ Widget _buildReactionIndicator(
+ BuildContext context,
+ ) {
+ final ownId = StreamChat.of(context).user.id;
+ final reactionsMap = {};
+ 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(
duration: Duration(milliseconds: 300),
child: (widget.showReactions &&
- (widget.message.reactionCounts?.isNotEmpty == true ||
- otherReactions.isNotEmpty) &&
+ (widget.message.reactionCounts?.isNotEmpty == true) &&
!widget.message.isDeleted)
- ? Container(
- child: GestureDetector(
- onTap: () => _showMessageReactionsModalBottomSheet(context),
- child: FractionallySizedBox(
- widthFactor: 0.5,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- children: rowChildren,
- ),
- ),
+ ? GestureDetector(
+ onTap: () => _showMessageReactionsModalBottomSheet(context),
+ child: ReactionBubble(
+ key: ValueKey('${widget.message.id}.reactions'),
+ reverse: widget.reverse,
+ flipTail: widget.reverse,
+ backgroundColor: widget.messageTheme.reactionsBackgroundColor,
+ borderColor: widget.messageTheme.reactionsBorderColor,
+ reactions: reactionsList,
),
)
: SizedBox(),
@@ -498,8 +460,8 @@ class _MessageWidgetState extends State {
});
}
- ContinuousRectangleBorder _getDefaultShape(BuildContext context) {
- return ContinuousRectangleBorder(
+ ShapeBorder _getDefaultShape(BuildContext context) {
+ return RoundedRectangleBorder(
side: widget.attachmentBorderSide ??
widget.borderSide ??
BorderSide(
@@ -718,7 +680,7 @@ class _MessageWidgetState extends State {
onLongPress: () => onLongPress(context),
child: Material(
shape: widget.shape ??
- ContinuousRectangleBorder(
+ RoundedRectangleBorder(
side: widget.borderSide ??
BorderSide(
color: Theme.of(context).brightness == Brightness.dark
diff --git a/lib/src/reaction_bubble.dart b/lib/src/reaction_bubble.dart
index 0d37d87f..83593afc 100644
--- a/lib/src/reaction_bubble.dart
+++ b/lib/src/reaction_bubble.dart
@@ -1,6 +1,8 @@
import 'dart:math';
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';
class ReactionBubble extends StatelessWidget {
@@ -11,6 +13,7 @@ class ReactionBubble extends StatelessWidget {
@required this.backgroundColor,
this.reverse = false,
this.flipTail = false,
+ this.highlightOwnReactions = true,
}) : super(key: key);
final List reactions;
@@ -18,67 +21,100 @@ class ReactionBubble extends StatelessWidget {
final Color backgroundColor;
final bool reverse;
final bool flipTail;
+ final bool highlightOwnReactions;
@override
Widget build(BuildContext context) {
final reactionIcons = StreamChatTheme.of(context).reactionIcons;
+ final offset = reactions.length > 1 ? 16.0 : 2.0;
return Transform(
transform: Matrix4.rotationY(reverse ? pi : 0),
alignment: Alignment.center,
- child: Column(
- crossAxisAlignment:
- flipTail ? CrossAxisAlignment.start : CrossAxisAlignment.end,
- mainAxisSize: MainAxisSize.min,
+ child: Stack(
+ alignment: Alignment.center,
children: [
- Container(
- padding: const EdgeInsets.all(4),
- decoration: BoxDecoration(
- border: Border.all(
- color: borderColor,
+ Transform.translate(
+ offset: Offset(reverse ? offset : -offset, 0),
+ child: Container(
+ padding: const EdgeInsets.symmetric(vertical: 4),
+ decoration: BoxDecoration(
+ border: Border.all(
+ color: borderColor,
+ ),
+ color: backgroundColor,
+ borderRadius: BorderRadius.all(Radius.circular(14)),
),
- color: backgroundColor,
- borderRadius: BorderRadius.all(Radius.circular(14)),
- ),
- child: Wrap(
- children: [
- ...reactions.map((reaction) {
- final reactionIcon = reactionIcons.firstWhere(
- (r) => r.type == reaction.type,
- orElse: () => null,
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ return Flex(
+ direction: Axis.horizontal,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (constraints.maxWidth < double.infinity)
+ ...reactions
+ .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 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) {
- final tail = Transform.translate(
- offset: Offset(reactions.length > 1 ? -9 : -9, 0),
- child: CustomPaint(
- painter: ReactionBubblePainter(
- backgroundColor,
- borderColor,
- reactions.length,
- ),
+ final tail = CustomPaint(
+ painter: ReactionBubblePainter(
+ backgroundColor,
+ borderColor,
),
);
@@ -97,12 +133,10 @@ class ReactionBubble extends StatelessWidget {
class ReactionBubblePainter extends CustomPainter {
final Color color;
final Color borderColor;
- final int reactionsCount;
ReactionBubblePainter(
this.color,
this.borderColor,
- this.reactionsCount,
);
@override
@@ -125,7 +159,7 @@ class ReactionBubblePainter extends CustomPainter {
final path = Path();
path.addOval(
Rect.fromCircle(
- center: Offset(6, 2),
+ center: Offset(4, 3),
radius: 2,
),
);
@@ -139,7 +173,7 @@ class ReactionBubblePainter extends CustomPainter {
final path = Path();
path.addOval(Rect.fromCircle(
- center: Offset(6, 2),
+ center: Offset(4, 3),
radius: 2,
));
canvas.drawPath(path, paint);
@@ -151,13 +185,13 @@ class ReactionBubblePainter extends CustomPainter {
..strokeWidth = 1
..style = PaintingStyle.stroke;
- final dy = reactionsCount > 1 ? -2.0 : -3.0;
- final startAngle = reactionsCount > 1 ? 1.08 : 1.16;
- final sweepAngle = reactionsCount > 1 ? 0.95 : 1.1;
+ final dy = -2.2;
+ final startAngle = 1.1;
+ final sweepAngle = 1.2;
final path = Path();
path.addArc(
Rect.fromCircle(
- center: Offset(0, dy),
+ center: Offset(1, dy),
radius: 4,
),
-pi * startAngle,
@@ -171,13 +205,13 @@ class ReactionBubblePainter extends CustomPainter {
..color = color
..strokeWidth = 1;
- final dy = reactionsCount > 1 ? -2.0 : -3.0;
- final startAngle = reactionsCount > 1 ? 1 : 1.16;
- final sweepAngle = reactionsCount > 1 ? 1.2 : 1;
+ final dy = -2.2;
+ final startAngle = 1;
+ final sweepAngle = 1.3;
final path = Path();
path.addArc(
Rect.fromCircle(
- center: Offset(0, dy),
+ center: Offset(1, dy),
radius: 4,
),
-pi * startAngle,
diff --git a/lib/src/reaction_picker.dart b/lib/src/reaction_picker.dart
index 5855b59f..87487cde 100644
--- a/lib/src/reaction_picker.dart
+++ b/lib/src/reaction_picker.dart
@@ -26,7 +26,7 @@ class ReactionPicker extends StatelessWidget {
fit: StackFit.passthrough,
children: [
Material(
- color: messageTheme.ownReactionsBackgroundColor,
+ color: messageTheme.reactionsBackgroundColor,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
@@ -45,7 +45,7 @@ class ReactionPicker extends StatelessWidget {
reactionIcon.iconData,
color: ownReactionIndex != -1
? StreamChatTheme.of(context).accentColor
- : Theme.of(context).iconTheme.color,
+ : Theme.of(context).iconTheme.color.withOpacity(.5),
),
onPressed: () {
if (ownReactionIndex != -1) {
@@ -69,9 +69,8 @@ class ReactionPicker extends StatelessWidget {
bottom: 0,
child: CustomPaint(
painter: ReactionBubblePainter(
- messageTheme.ownReactionsBackgroundColor,
- messageTheme.ownReactionsBorderColor,
- 2,
+ messageTheme.reactionsBackgroundColor,
+ messageTheme.reactionsBorderColor,
),
),
),
diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart
index b22c257c..e32c7fa1 100644
--- a/lib/src/stream_chat.dart
+++ b/lib/src/stream_chat.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/foundation.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_flutter/src/stream_chat_theme.dart';
@@ -129,14 +130,9 @@ class StreamChatState extends State with WidgetsBindingObserver {
constraints: themeData?.ownMessageTheme?.avatarTheme?.constraints,
borderRadius: themeData?.ownMessageTheme?.avatarTheme?.borderRadius,
),
- otherReactionsBorderColor:
- themeData?.ownMessageTheme?.otherReactionsBorderColor,
- otherReactionsBackgroundColor:
- themeData?.ownMessageTheme?.otherReactionsBackgroundColor,
- ownReactionsBackgroundColor:
- themeData?.ownMessageTheme?.ownReactionsBackgroundColor,
- ownReactionsBorderColor:
- themeData?.ownMessageTheme?.ownReactionsBorderColor,
+ reactionsBorderColor: themeData?.ownMessageTheme?.reactionsBorderColor,
+ reactionsBackgroundColor:
+ themeData?.ownMessageTheme?.reactionsBackgroundColor,
),
otherMessageTheme: defaultTheme.otherMessageTheme.copyWith(
replies: themeData?.otherMessageTheme?.replies,
@@ -150,14 +146,10 @@ class StreamChatState extends State with WidgetsBindingObserver {
constraints: themeData?.otherMessageTheme?.avatarTheme?.constraints,
borderRadius: themeData?.otherMessageTheme?.avatarTheme?.borderRadius,
),
- otherReactionsBorderColor:
- themeData?.otherMessageTheme?.otherReactionsBorderColor,
- otherReactionsBackgroundColor:
- themeData?.otherMessageTheme?.otherReactionsBackgroundColor,
- ownReactionsBackgroundColor:
- themeData?.otherMessageTheme?.ownReactionsBackgroundColor,
- ownReactionsBorderColor:
- themeData?.otherMessageTheme?.ownReactionsBorderColor,
+ reactionsBorderColor:
+ themeData?.otherMessageTheme?.reactionsBorderColor,
+ reactionsBackgroundColor:
+ themeData?.otherMessageTheme?.reactionsBackgroundColor,
),
accentColor: themeData?.accentColor,
secondaryColor: themeData?.secondaryColor,
@@ -186,6 +178,13 @@ class StreamChatState extends State with WidgetsBindingObserver {
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
+ client.state?.totalUnreadCountStream?.listen((count) {
+ if (count > 0) {
+ FlutterAppBadger.updateBadgeCount(count);
+ } else {
+ FlutterAppBadger.removeBadge();
+ }
+ });
}
StreamSubscription _newMessageSubscription;
diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart
index 359ab102..cc959a35 100644
--- a/lib/src/stream_chat_theme.dart
+++ b/lib/src/stream_chat_theme.dart
@@ -220,8 +220,8 @@ class StreamChatThemeData {
return StreamChatThemeData(
accentColor: accentColor,
primaryColor: isDark ? Colors.black : Colors.white,
- primaryIconTheme:
- IconThemeData(color: isDark ? Colors.white : Colors.black),
+ primaryIconTheme: IconThemeData(
+ color: isDark ? Colors.white : Colors.black.withOpacity(.5)),
defaultChannelImage: (context, channel) => SizedBox(),
backgroundColor: isDark ? Colors.black : Colors.white,
defaultUserImage: (context, user) => Center(
@@ -287,7 +287,7 @@ class StreamChatThemeData {
),
ownMessageTheme: MessageTheme(
messageText: TextStyle(
- fontSize: 15,
+ fontSize: 14.5,
color: isDark ? Colors.white : Colors.black,
),
createdAt: TextStyle(
@@ -302,12 +302,8 @@ class StreamChatThemeData {
fontSize: 12,
),
messageBackgroundColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA),
- ownReactionsBackgroundColor:
- isDark ? Color(0xff191919) : Color(0xffEAEAEA),
- ownReactionsBorderColor: isDark ? Colors.black : Colors.white,
- otherReactionsBackgroundColor: isDark ? Colors.black : Colors.white,
- otherReactionsBorderColor:
- isDark ? Color(0xff191919) : Color(0xffEAEAEA),
+ reactionsBackgroundColor: isDark ? Colors.black : Colors.white,
+ reactionsBorderColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA),
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20),
constraints: BoxConstraints.tightFor(
@@ -320,14 +316,11 @@ class StreamChatThemeData {
),
),
otherMessageTheme: MessageTheme(
- ownReactionsBackgroundColor:
- isDark ? Color(0xff191919) : Color(0xffEAEAEA),
- ownReactionsBorderColor: isDark ? Colors.black : Colors.white,
- otherReactionsBackgroundColor: isDark ? Colors.black : Colors.white,
- otherReactionsBorderColor:
+ reactionsBackgroundColor:
isDark ? Color(0xff191919) : Color(0xffEAEAEA),
+ reactionsBorderColor: isDark ? Colors.black : Colors.white,
messageText: TextStyle(
- fontSize: 15,
+ fontSize: 14.5,
color: isDark ? Colors.white : Colors.black,
),
createdAt: TextStyle(
@@ -457,10 +450,8 @@ class MessageTheme {
final TextStyle createdAt;
final TextStyle replies;
final Color messageBackgroundColor;
- final Color ownReactionsBackgroundColor;
- final Color ownReactionsBorderColor;
- final Color otherReactionsBackgroundColor;
- final Color otherReactionsBorderColor;
+ final Color reactionsBackgroundColor;
+ final Color reactionsBorderColor;
final AvatarTheme avatarTheme;
const MessageTheme({
@@ -469,10 +460,8 @@ class MessageTheme {
this.messageAuthor,
this.messageLinks,
this.messageBackgroundColor,
- this.ownReactionsBackgroundColor,
- this.ownReactionsBorderColor,
- this.otherReactionsBackgroundColor,
- this.otherReactionsBorderColor,
+ this.reactionsBackgroundColor,
+ this.reactionsBorderColor,
this.avatarTheme,
this.createdAt,
});
@@ -485,10 +474,8 @@ class MessageTheme {
TextStyle replies,
Color messageBackgroundColor,
AvatarTheme avatarTheme,
- Color ownReactionsBackgroundColor,
- Color ownReactionsBorderColor,
- Color otherReactionsBackgroundColor,
- Color otherReactionsBorderColor,
+ Color reactionsBackgroundColor,
+ Color reactionsBorderColor,
}) =>
MessageTheme(
messageText: messageText ?? this.messageText,
@@ -499,14 +486,9 @@ class MessageTheme {
messageBackgroundColor ?? this.messageBackgroundColor,
avatarTheme: avatarTheme ?? this.avatarTheme,
replies: replies ?? this.replies,
- ownReactionsBackgroundColor:
- ownReactionsBackgroundColor ?? this.ownReactionsBackgroundColor,
- ownReactionsBorderColor:
- ownReactionsBorderColor ?? this.ownReactionsBorderColor,
- otherReactionsBackgroundColor:
- otherReactionsBackgroundColor ?? this.otherReactionsBackgroundColor,
- otherReactionsBorderColor:
- otherReactionsBorderColor ?? this.otherReactionsBorderColor,
+ reactionsBackgroundColor:
+ reactionsBackgroundColor ?? this.reactionsBackgroundColor,
+ reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor,
);
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 00b71790..d61e6874 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -11,16 +11,17 @@ environment:
dependencies:
flutter:
sdk: flutter
+ flutter_app_badger: ^1.1.2
photo_view: ^0.10.1
rxdart: ^0.24.1
jiffy: ^3.0.1
flutter_portal: ^0.3.0
cached_network_image: ^2.2.0+1
- flutter_markdown: ^0.4.2
+ flutter_markdown: ^0.5.0
url_launcher: ^5.4.11
- video_player: ^0.10.11+1
+ video_player: ^0.10.12+5
chewie: ^0.9.10
- file_picker: ^2.0.0
+ file_picker: ^2.0.8+1
image_picker: ^0.6.7+2
flutter_keyboard_visibility: ^3.2.1
stream_chat: ^0.2.10
diff --git a/test/src/message_reaction_modal_test.dart b/test/src/message_reaction_modal_test.dart
index e3972780..e270793e 100644
--- a/test/src/message_reaction_modal_test.dart
+++ b/test/src/message_reaction_modal_test.dart
@@ -10,13 +10,21 @@ void main() {
testWidgets(
'it should show one thumbs from the picker',
(WidgetTester tester) async {
+ final client = MockClient();
+ final clientState = MockClientState();
final themeData = ThemeData();
+
+ when(client.state).thenReturn(clientState);
+ when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
+
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
+
await tester.pumpWidget(
MaterialApp(
theme: themeData,
- home: StreamChatTheme(
- data: streamTheme,
+ home: StreamChat(
+ client: client,
+ streamChatThemeData: streamTheme,
child: MessageReactionsModal(
message: Message(
id: 'test',
diff --git a/test/src/reaction_bubble_test.dart b/test/src/reaction_bubble_test.dart
index dc0381de..c524061c 100644
--- a/test/src/reaction_bubble_test.dart
+++ b/test/src/reaction_bubble_test.dart
@@ -1,8 +1,11 @@
import 'package:flutter/material.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/stream_chat_flutter.dart';
+import 'mocks.dart';
+
void main() {
testWidgets(
'it should show no reactions',
@@ -29,17 +32,25 @@ void main() {
testWidgets(
'it should show a thumb up',
(WidgetTester tester) async {
+ final client = MockClient();
+ final clientState = MockClientState();
final themeData = ThemeData();
+
+ when(client.state).thenReturn(clientState);
+ when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
+
await tester.pumpWidget(
MaterialApp(
theme: themeData,
- home: StreamChatTheme(
- data: StreamChatThemeData.getDefaultTheme(themeData),
+ home: StreamChat(
+ client: client,
+ streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData),
child: Container(
child: ReactionBubble(
reactions: [
Reaction(
type: 'thumbs_up',
+ user: User(id: 'test'),
),
],
borderColor: Colors.black,
@@ -56,20 +67,29 @@ void main() {
testWidgets(
'it should show two reactions',
(WidgetTester tester) async {
+ final client = MockClient();
+ final clientState = MockClientState();
final themeData = ThemeData();
+
+ when(client.state).thenReturn(clientState);
+ when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
+
await tester.pumpWidget(
MaterialApp(
theme: themeData,
- home: StreamChatTheme(
- data: StreamChatThemeData.getDefaultTheme(themeData),
+ home: StreamChat(
+ client: client,
+ streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData),
child: Container(
child: ReactionBubble(
reactions: [
Reaction(
type: 'thumbs_up',
+ user: User(id: 'test'),
),
Reaction(
type: 'love',
+ user: User(id: 'test'),
),
],
borderColor: Colors.black,