Merge remote-tracking branch 'origin/develop' into v7.0.0

# Conflicts:
#	packages/stream_chat/CHANGELOG.md
#	packages/stream_chat_flutter/CHANGELOG.md
#	packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart
#	packages/stream_chat_flutter_core/CHANGELOG.md
This commit is contained in:
Sahil Kumar
2023-09-07 16:19:20 +05:30
19 changed files with 192 additions and 138 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
name: Publish docusaurus docs name: Publish docusaurus docs
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Node 16 - name: Setup Node 16
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: "Git Checkout" - name: "Git Checkout"
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
+5 -5
View File
@@ -23,7 +23,7 @@ jobs:
stream_chat: stream_chat:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: axel-op/dart-package-analyzer@v3 - uses: axel-op/dart-package-analyzer@v3
id: analysis id: analysis
with: with:
@@ -43,7 +43,7 @@ jobs:
stream_chat_persistence: stream_chat_persistence:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: axel-op/dart-package-analyzer@v3 - uses: axel-op/dart-package-analyzer@v3
id: analysis id: analysis
with: with:
@@ -64,7 +64,7 @@ jobs:
stream_chat_flutter_core: stream_chat_flutter_core:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: axel-op/dart-package-analyzer@v3 - uses: axel-op/dart-package-analyzer@v3
id: analysis id: analysis
with: with:
@@ -84,7 +84,7 @@ jobs:
stream_chat_flutter: stream_chat_flutter:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: axel-op/dart-package-analyzer@v3 - uses: axel-op/dart-package-analyzer@v3
id: analysis id: analysis
with: with:
@@ -104,7 +104,7 @@ jobs:
stream_chat_localizations: stream_chat_localizations:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: axel-op/dart-package-analyzer@v3 - uses: axel-op/dart-package-analyzer@v3
id: analysis id: analysis
with: with:
@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: "Git Checkout" - name: "Git Checkout"
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: "Install Flutter" - name: "Install Flutter"
@@ -57,7 +57,7 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: "Git Checkout" - name: "Git Checkout"
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: "Install Flutter" - name: "Install Flutter"
@@ -82,7 +82,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- name: "Git Checkout" - name: "Git Checkout"
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: "Install Flutter" - name: "Install Flutter"
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
name: Vale doc linter name: Vale doc linter
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: errata-ai/vale-action@reviewdog - uses: errata-ai/vale-action@reviewdog
with: with:
# added, diff_context, file, nofilter # added, diff_context, file, nofilter
+7
View File
@@ -1,3 +1,10 @@
## Upcoming
🐞 Fixed
- [[#1716]](https://github.com/GetStream/stream-chat-flutter/issues/1716) Fixed client not able to
update message with `type: reply`.
## 7.0.0-beta.3 ## 7.0.0-beta.3
- Included the changes from version [6.8.0](#670). - Included the changes from version [6.8.0](#670).
@@ -87,8 +87,16 @@ class Message extends Equatable {
final MessageState state; final MessageState state;
/// The message type. /// The message type.
@JsonKey(includeIfNull: false, toJson: _typeToJson)
final String type; final String type;
// We need to skip passing type if it's not regular or system as the API
// does not expect it.
static String? _typeToJson(String type) {
if (['regular', 'system'].contains(type)) return type;
return null;
}
/// The list of attachments, either provided by the user or generated from a /// The list of attachments, either provided by the user or generated from a
/// command or as a result of URL scraping. /// command or as a result of URL scraping.
@JsonKey(includeIfNull: false) @JsonKey(includeIfNull: false)
@@ -71,17 +71,27 @@ Message _$MessageFromJson(Map<String, dynamic> json) => Message(
), ),
); );
Map<String, dynamic> _$MessageToJson(Message instance) => <String, dynamic>{ Map<String, dynamic> _$MessageToJson(Message instance) {
'id': instance.id, final val = <String, dynamic>{
'text': instance.text, 'id': instance.id,
'type': instance.type, 'text': instance.text,
'attachments': instance.attachments.map((e) => e.toJson()).toList(), };
'mentioned_users': User.toIds(instance.mentionedUsers),
'parent_id': instance.parentId, void writeNotNull(String key, dynamic value) {
'quoted_message_id': instance.quotedMessageId, if (value != null) {
'show_in_channel': instance.showInChannel, val[key] = value;
'silent': instance.silent, }
'pinned': instance.pinned, }
'pin_expires': instance.pinExpires?.toIso8601String(),
'extra_data': instance.extraData, writeNotNull('type', Message._typeToJson(instance.type));
}; val['attachments'] = instance.attachments.map((e) => e.toJson()).toList();
val['mentioned_users'] = User.toIds(instance.mentionedUsers);
val['parent_id'] = instance.parentId;
val['quoted_message_id'] = instance.quotedMessageId;
val['show_in_channel'] = instance.showInChannel;
val['silent'] = instance.silent;
val['pinned'] = instance.pinned;
val['pin_expires'] = instance.pinExpires?.toIso8601String();
val['extra_data'] = instance.extraData;
return val;
}
+10 -1
View File
@@ -1,3 +1,11 @@
## Upcoming
✅ Added
- Added support for overriding the `MessageWidget.onReactionsHover` callback.
> **Note**
> Used only in desktop devices (web and desktop).
## 7.0.0-beta.4 ## 7.0.0-beta.4
- Included the changes from version [6.9.0](#681). - Included the changes from version [6.9.0](#681).
@@ -18,7 +26,8 @@
- Added support for listening error events in AttachmentPickerBottomSheet. - Added support for listening error events in AttachmentPickerBottomSheet.
- Added support for overriding the `MessageWidget.onReactionTap` callback. - Added support for overriding the `MessageWidget.onReactionTap` callback.
- Added support for `StreamMessageInput.contentInsertionConfiguration` to specify the content insertion configuration. - Added support for `StreamMessageInput.contentInsertionConfiguration` to specify the content
insertion configuration.
[#1613](https://github.com/GetStream/stream-chat-flutter/issues/1613) [#1613](https://github.com/GetStream/stream-chat-flutter/issues/1613)
```dart ```dart
@@ -159,7 +159,6 @@
C4CD72858CD59598795BB48E /* Pods-Runner.release.xcconfig */, C4CD72858CD59598795BB48E /* Pods-Runner.release.xcconfig */,
2BCA7399119839DE435DACD6 /* Pods-Runner.profile.xcconfig */, 2BCA7399119839DE435DACD6 /* Pods-Runner.profile.xcconfig */,
); );
name = Pods;
path = Pods; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -203,7 +202,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 0920; LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1300; LastUpgradeCheck = 1430;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
33CC10EC2044A3C60003C045 = { 33CC10EC2044A3C60003C045 = {
@@ -427,7 +426,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 11.0;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
@@ -554,7 +553,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 11.0;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -575,7 +574,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 11.0;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1300" LastUpgradeVersion = "1430"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -55,6 +55,7 @@ class StreamMessageWidget extends StatefulWidget {
this.onMentionTap, this.onMentionTap,
this.onMessageTap, this.onMessageTap,
this.onReactionsTap, this.onReactionsTap,
this.onReactionsHover,
this.showReactionPicker = true, this.showReactionPicker = true,
@internal this.showReactionPickerTail = false, @internal this.showReactionPickerTail = false,
this.showUserAvatar = DisplayWidget.show, this.showUserAvatar = DisplayWidget.show,
@@ -486,8 +487,16 @@ class StreamMessageWidget extends StatefulWidget {
final void Function(Message)? onMessageTap; final void Function(Message)? onMessageTap;
/// {@macro onReactionsTap} /// {@macro onReactionsTap}
///
/// Note: Only used in mobile devices (iOS and Android). Do not confuse this
/// with the tap action on the reactions picker.
final OnReactionsTap? onReactionsTap; final OnReactionsTap? onReactionsTap;
/// {@template onReactionsHover}
///
/// Note: Only used in desktop devices (web and desktop).
final OnReactionsHover? onReactionsHover;
/// {@template customActions} /// {@template customActions}
/// List of custom actions shown on message long tap /// List of custom actions shown on message long tap
/// {@endtemplate} /// {@endtemplate}
@@ -547,6 +556,7 @@ class StreamMessageWidget extends StatefulWidget {
bool? showInChannelIndicator, bool? showInChannelIndicator,
void Function(User)? onUserAvatarTap, void Function(User)? onUserAvatarTap,
void Function(String)? onLinkTap, void Function(String)? onLinkTap,
bool? showReactionBrowser,
bool? showReactionPicker, bool? showReactionPicker,
@internal bool? showReactionPickerTail, @internal bool? showReactionPickerTail,
List<Read>? readList, List<Read>? readList,
@@ -567,6 +577,7 @@ class StreamMessageWidget extends StatefulWidget {
OnQuotedMessageTap? onQuotedMessageTap, OnQuotedMessageTap? onQuotedMessageTap,
void Function(Message)? onMessageTap, void Function(Message)? onMessageTap,
OnReactionsTap? onReactionsTap, OnReactionsTap? onReactionsTap,
OnReactionsHover? onReactionsHover,
List<StreamMessageAction>? customActions, List<StreamMessageAction>? customActions,
void Function(Message message, Attachment attachment)? onAttachmentTap, void Function(Message message, Attachment attachment)? onAttachmentTap,
Widget Function(BuildContext, User)? userAvatarBuilder, Widget Function(BuildContext, User)? userAvatarBuilder,
@@ -629,6 +640,7 @@ class StreamMessageWidget extends StatefulWidget {
onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap, onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap,
onMessageTap: onMessageTap ?? this.onMessageTap, onMessageTap: onMessageTap ?? this.onMessageTap,
onReactionsTap: onReactionsTap ?? this.onReactionsTap, onReactionsTap: onReactionsTap ?? this.onReactionsTap,
onReactionsHover: onReactionsHover ?? this.onReactionsHover,
customActions: customActions ?? this.customActions, customActions: customActions ?? this.customActions,
onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap, onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap,
userAvatarBuilder: userAvatarBuilder ?? this.userAvatarBuilder, userAvatarBuilder: userAvatarBuilder ?? this.userAvatarBuilder,
@@ -839,7 +851,6 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
reverse: widget.reverse, reverse: widget.reverse,
message: widget.message, message: widget.message,
hasNonUrlAttachments: hasNonUrlAttachments, hasNonUrlAttachments: hasNonUrlAttachments,
shouldShowReactions: shouldShowReactions,
hasQuotedMessage: hasQuotedMessage, hasQuotedMessage: hasQuotedMessage,
textPadding: widget.textPadding, textPadding: widget.textPadding,
attachmentBuilders: widget.attachmentBuilders, attachmentBuilders: widget.attachmentBuilders,
@@ -864,6 +875,7 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
? widget.onReactionsTap!(widget.message) ? widget.onReactionsTap!(widget.message)
: _showMessageReactionsModal(context); : _showMessageReactionsModal(context);
}, },
onReactionsHover: widget.onReactionsHover,
showUserAvatar: widget.showUserAvatar, showUserAvatar: widget.showUserAvatar,
streamChat: _streamChat, streamChat: _streamChat,
translateUserAvatar: widget.translateUserAvatar, translateUserAvatar: widget.translateUserAvatar,
@@ -1125,7 +1137,8 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
translateUserAvatar: false, translateUserAvatar: false,
showSendingIndicator: false, showSendingIndicator: false,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
// Show both the tail and indicator if the indicator is shown. // Show both the tail if the picker is shown.
showReactionPicker: widget.showReactionPicker,
showReactionPickerTail: widget.showReactionPicker, showReactionPickerTail: widget.showReactionPicker,
showPinHighlight: false, showPinHighlight: false,
showUserAvatar: widget.message.user!.id == showUserAvatar: widget.message.user!.id ==
@@ -37,8 +37,8 @@ class MessageWidgetContent extends StatelessWidget {
required this.avatarWidth, required this.avatarWidth,
required this.showReactions, required this.showReactions,
required this.onReactionsTap, required this.onReactionsTap,
required this.onReactionsHover,
required this.messageTheme, required this.messageTheme,
required this.shouldShowReactions,
required this.streamChatTheme, required this.streamChatTheme,
required this.isFailedState, required this.isFailedState,
required this.hasQuotedMessage, required this.hasQuotedMessage,
@@ -101,17 +101,15 @@ class MessageWidgetContent extends StatelessWidget {
/// {@macro showReactions} /// {@macro showReactions}
final bool showReactions; final bool showReactions;
/// Callback called when the reactions icon is tapped. /// {@macro onReactionsTap}
///
/// Do not confuse this with the tap action on the reactions picker.
final VoidCallback onReactionsTap; final VoidCallback onReactionsTap;
/// {@macro onReactionsHover}
final OnReactionsHover? onReactionsHover;
/// {@macro messageTheme} /// {@macro messageTheme}
final StreamMessageThemeData messageTheme; final StreamMessageThemeData messageTheme;
/// {@macro shouldShowReactions}
final bool shouldShowReactions;
/// {@macro onUserAvatarTap} /// {@macro onUserAvatarTap}
final void Function(User)? onUserAvatarTap; final void Function(User)? onUserAvatarTap;
@@ -285,7 +283,6 @@ class MessageWidgetContent extends StatelessWidget {
messageTheme: messageTheme, messageTheme: messageTheme,
ownId: streamChat.currentUser!.id, ownId: streamChat.currentUser!.id,
reverse: reverse, reverse: reverse,
shouldShowReactions: shouldShowReactions,
onTap: onReactionsTap, onTap: onReactionsTap,
) )
: null, : null,
@@ -304,25 +301,15 @@ class MessageWidgetContent extends StatelessWidget {
children: [ children: [
Padding( Padding(
padding: showReactions padding: showReactions
? EdgeInsets.only( ? const EdgeInsets.only(top: 18)
top: message.reactionCounts
?.isNotEmpty ==
true
? 18
: 0,
)
: EdgeInsets.zero, : EdgeInsets.zero,
child: (message.isDeleted && !isFailedState) child: (message.isDeleted && !isFailedState)
? Container( ? Container(
// ignore: lines_longer_than_80_chars
margin: EdgeInsets.symmetric( margin: EdgeInsets.symmetric(
horizontal: horizontal: showUserAvatar ==
// ignore: lines_longer_than_80_chars DisplayWidget.gone
showUserAvatar == ? 0
// ignore: lines_longer_than_80_chars : 4.0,
DisplayWidget.gone
? 0
: 4.0,
), ),
child: StreamDeletedMessage( child: StreamDeletedMessage(
borderRadiusGeometry: borderRadiusGeometry:
@@ -401,7 +388,7 @@ class MessageWidgetContent extends StatelessWidget {
SizedBox(width: avatarWidth + 4), SizedBox(width: avatarWidth + 4),
], ],
), ),
if (isDesktopDeviceOrWeb && shouldShowReactions) ...[ if (isDesktopDeviceOrWeb && showReactions) ...[
Padding( Padding(
padding: showUserAvatar != DisplayWidget.gone padding: showUserAvatar != DisplayWidget.gone
? EdgeInsets.only( ? EdgeInsets.only(
@@ -412,7 +399,7 @@ class MessageWidgetContent extends StatelessWidget {
child: DesktopReactionsBuilder( child: DesktopReactionsBuilder(
message: message, message: message,
messageTheme: messageTheme, messageTheme: messageTheme,
shouldShowReactions: shouldShowReactions, onHover: onReactionsHover,
borderSide: borderSide, borderSide: borderSide,
reverse: reverse, reverse: reverse,
), ),
@@ -1,3 +1,5 @@
// ignore_for_file: cascade_invocations
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -5,8 +7,6 @@ import 'package:flutter_portal/flutter_portal.dart';
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_card.dart'; import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_card.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
// ignore_for_file: cascade_invocations
/// {@template desktopReactionsBuilder} /// {@template desktopReactionsBuilder}
/// Builds a list of reactions to a message on desktop & web. /// Builds a list of reactions to a message on desktop & web.
/// ///
@@ -16,16 +16,13 @@ class DesktopReactionsBuilder extends StatefulWidget {
/// {@macro desktopReactionsBuilder} /// {@macro desktopReactionsBuilder}
const DesktopReactionsBuilder({ const DesktopReactionsBuilder({
super.key, super.key,
required this.shouldShowReactions,
required this.message, required this.message,
required this.messageTheme, required this.messageTheme,
this.onHover,
this.borderSide, this.borderSide,
required this.reverse, required this.reverse,
}); });
/// Whether reactions should be shown.
final bool shouldShowReactions;
/// The message to show reactions for. /// The message to show reactions for.
final Message message; final Message message;
@@ -35,6 +32,9 @@ class DesktopReactionsBuilder extends StatefulWidget {
/// reactions matches the design spec for messages. /// reactions matches the design spec for messages.
final StreamMessageThemeData messageTheme; final StreamMessageThemeData messageTheme;
/// Callback to run when the mouse enters or exits the reactions.
final OnReactionsHover? onHover;
/// {@macro borderSide} /// {@macro borderSide}
final BorderSide? borderSide; final BorderSide? borderSide;
@@ -48,12 +48,6 @@ class DesktopReactionsBuilder extends StatefulWidget {
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties.add(
DiagnosticsProperty<bool>(
'shouldShowReactions',
shouldShowReactions,
),
);
properties.add( properties.add(
DiagnosticsProperty<Message>('message', message), DiagnosticsProperty<Message>('message', message),
); );
@@ -81,18 +75,15 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
final streamChatTheme = StreamChatTheme.of(context); final streamChatTheme = StreamChatTheme.of(context);
final reactionsMap = <String, Reaction>{}; final reactionsMap = <String, Reaction>{};
var reactionsList = <Reaction>[]; widget.message.latestReactions?.forEach((element) {
if (widget.shouldShowReactions) { if (!reactionsMap.containsKey(element.type) ||
widget.message.latestReactions?.forEach((element) { element.user!.id == currentUser.id) {
if (!reactionsMap.containsKey(element.type) || reactionsMap[element.type] = element;
element.user!.id == currentUser.id) { }
reactionsMap[element.type] = element; });
}
});
reactionsList = reactionsMap.values.toList() final reactionsList = reactionsMap.values.toList()
..sort((a, b) => a.user!.id == currentUser.id ? 1 : -1); ..sort((a, b) => a.user!.id == currentUser.id ? 1 : -1);
}
return PortalTarget( return PortalTarget(
visible: _showReactionsPopup, visible: _showReactionsPopup,
@@ -100,17 +91,11 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
anchor: Aligned( anchor: Aligned(
target: widget.reverse ? Alignment.topRight : Alignment.topLeft, target: widget.reverse ? Alignment.topRight : Alignment.topLeft,
follower: widget.reverse ? Alignment.bottomRight : Alignment.bottomLeft, follower: widget.reverse ? Alignment.bottomRight : Alignment.bottomLeft,
shiftToWithinBound: const AxisFlag( shiftToWithinBound: const AxisFlag(y: true),
y: true,
),
), ),
portalFollower: MouseRegion( portalFollower: MouseRegion(
onEnter: (event) async { onEnter: (_) => _onReactionsHover(true),
setState(() => _showReactionsPopup = !_showReactionsPopup); onExit: (_) => _onReactionsHover(false),
},
onExit: (event) {
setState(() => _showReactionsPopup = !_showReactionsPopup);
},
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints( constraints: const BoxConstraints(
maxWidth: 336, maxWidth: 336,
@@ -125,12 +110,8 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
), ),
child: MouseRegion( child: MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
onEnter: (event) async { onEnter: (_) => _onReactionsHover(true),
setState(() => _showReactionsPopup = !_showReactionsPopup); onExit: (_) => _onReactionsHover(false),
},
onExit: (event) {
setState(() => _showReactionsPopup = !_showReactionsPopup);
},
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
vertical: 2, vertical: 2,
@@ -161,6 +142,14 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
), ),
); );
} }
void _onReactionsHover(bool isHovering) {
if (widget.onHover != null) {
return widget.onHover!(isHovering);
}
setState(() => _showReactionsPopup = isHovering);
}
} }
class _BottomReaction extends StatelessWidget { class _BottomReaction extends StatelessWidget {
@@ -13,7 +13,6 @@ class ReactionIndicator extends StatelessWidget {
super.key, super.key,
required this.ownId, required this.ownId,
required this.message, required this.message,
required this.shouldShowReactions,
required this.onTap, required this.onTap,
required this.reverse, required this.reverse,
required this.messageTheme, required this.messageTheme,
@@ -25,9 +24,6 @@ class ReactionIndicator extends StatelessWidget {
/// {@macro message} /// {@macro message}
final Message message; final Message message;
/// {@macro shouldShowReactions}
final bool shouldShowReactions;
/// The callback to perform when the widget is tapped or clicked. /// The callback to perform when the widget is tapped or clicked.
final VoidCallback onTap; final VoidCallback onTap;
@@ -50,34 +46,27 @@ class ReactionIndicator extends StatelessWidget {
..sort((a, b) => a.user!.id == ownId ? 1 : -1); ..sort((a, b) => a.user!.id == ownId ? 1 : -1);
return Transform( return Transform(
transform: Matrix4.translationValues( transform: Matrix4.translationValues(reverse ? 12 : -12, 0, 0),
reverse ? 12 : -12,
0,
0,
),
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints( constraints: const BoxConstraints(
maxWidth: 22 * 6.0, maxWidth: 22 * 6.0,
), ),
child: AnimatedSwitcher( child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: shouldShowReactions child: GestureDetector(
? GestureDetector( onTap: onTap,
onTap: onTap, child: StreamReactionBubble(
child: StreamReactionBubble( key: ValueKey('${message.id}.reactions'),
key: ValueKey('${message.id}.reactions'), reverse: reverse,
reverse: reverse, flipTail: reverse,
flipTail: reverse, backgroundColor:
backgroundColor: messageTheme.reactionsBackgroundColor ?? messageTheme.reactionsBackgroundColor ?? Colors.transparent,
Colors.transparent, borderColor:
borderColor: messageTheme.reactionsBorderColor ?? Colors.transparent,
messageTheme.reactionsBorderColor ?? Colors.transparent, maskColor: messageTheme.reactionsMaskColor ?? Colors.transparent,
maskColor: reactions: reactionsList,
messageTheme.reactionsMaskColor ?? Colors.transparent, ),
reactions: reactionsList, ),
),
)
: const SizedBox(),
), ),
), ),
); );
@@ -217,6 +217,12 @@ typedef OnMessageTap = void Function(Message);
/// {@endtemplate} /// {@endtemplate}
typedef OnReactionsTap = void Function(Message); typedef OnReactionsTap = void Function(Message);
/// {@template onReactionsHover}
/// The action to perform when a message's reactions are hovered.
/// {@endtemplate}
// ignore: avoid_positional_boolean_parameters
typedef OnReactionsHover = void Function(bool isHovering);
/// {@template messageSearchItemTapCallback} /// {@template messageSearchItemTapCallback}
/// The action to perform when tapping or clicking on a user in a /// The action to perform when tapping or clicking on a user in a
/// [MessageSearchListView]. /// [MessageSearchListView].
+11 -3
View File
@@ -1,3 +1,8 @@
## Upcoming
- Added support for `StreamChannel.loadingBuilder` and `StreamChannel.errorBuilder` to customize
loading and error states.
## 7.0.0-beta.3 ## 7.0.0-beta.3
- Updated minimum supported `SDK` version to Flutter 3.13/Dart 3.1 - Updated minimum supported `SDK` version to Flutter 3.13/Dart 3.1
@@ -50,9 +55,11 @@
- Updated `dart` sdk environment range to support `3.0.0`. - Updated `dart` sdk environment range to support `3.0.0`.
- Updated `stream_chat` dependency to [`6.1.0`](https://pub.dev/packages/stream_chat/changelog). - Updated `stream_chat` dependency to [`6.1.0`](https://pub.dev/packages/stream_chat/changelog).
- [[#1356]](https://github.com/GetStream/stream-chat-flutter/issues/1356) Channel doesn't auto display again after being - [[#1356]](https://github.com/GetStream/stream-chat-flutter/issues/1356) Channel doesn't auto
display again after being
hidden. hidden.
- [[#1540]](https://github.com/GetStream/stream-chat-flutter/issues/1540) Use `CircularProgressIndicator.adaptive` - [[#1540]](https://github.com/GetStream/stream-chat-flutter/issues/1540)
Use `CircularProgressIndicator.adaptive`
instead of material indicator. instead of material indicator.
## 6.0.0 ## 6.0.0
@@ -71,7 +78,8 @@
## 5.1.0 ## 5.1.0
- Deprecated the `sort` parameter in the `StreamChannelListController` in favor of `channelStateSort`. - Deprecated the `sort` parameter in the `StreamChannelListController` in favor
of `channelStateSort`.
## 5.0.0 ## 5.0.0
@@ -15,6 +15,15 @@ enum QueryDirection {
bottom, bottom,
} }
/// Signature used by [StreamChannel.errorBuilder] to create a replacement
/// widget for an error that occurs while asynchronously building the channel.
// TODO: Remove once ErrorBuilder supports passing stacktrace.
typedef ErrorWidgetBuilder = Widget Function(
BuildContext context,
Object error,
StackTrace? stackTrace,
);
/// Widget used to provide information about the channel to the widget tree /// Widget used to provide information about the channel to the widget tree
/// ///
/// Use [StreamChannel.of] to get the current [StreamChannelState] instance. /// Use [StreamChannel.of] to get the current [StreamChannelState] instance.
@@ -27,6 +36,8 @@ class StreamChannel extends StatefulWidget {
required this.channel, required this.channel,
this.showLoading = true, this.showLoading = true,
this.initialMessageId, this.initialMessageId,
this.errorBuilder = _defaultErrorBuilder,
this.loadingBuilder = _defaultLoadingBuilder,
}); });
/// The child of the widget /// The child of the widget
@@ -41,6 +52,31 @@ class StreamChannel extends StatefulWidget {
/// If passed the channel will load from this particular message. /// If passed the channel will load from this particular message.
final String? initialMessageId; final String? initialMessageId;
/// Widget builder used in case the channel is initialising.
final WidgetBuilder loadingBuilder;
/// Widget builder used in case an error occurs while building the channel.
final ErrorWidgetBuilder errorBuilder;
static Widget _defaultLoadingBuilder(BuildContext context) {
return const Center(child: CircularProgressIndicator.adaptive());
}
static Widget _defaultErrorBuilder(
BuildContext context,
Object error,
StackTrace? stackTrace,
) {
if (error is DioException) {
if (error.type == DioExceptionType.badResponse) {
return Center(child: Text(error.message ?? 'Bad response'));
}
return const Center(child: Text('Check your connection and retry'));
}
return Center(child: Text(error.toString()));
}
/// Use this method to get the current [StreamChannelState] instance /// Use this method to get the current [StreamChannelState] instance
static StreamChannelState of(BuildContext context) { static StreamChannelState of(BuildContext context) {
StreamChannelState? streamChannelState; StreamChannelState? streamChannelState;
@@ -430,22 +466,14 @@ class StreamChannelState extends State<StreamChannel> {
], ],
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
final error = snapshot.error; final error = snapshot.error!;
if (error is DioException) { final stackTrace = snapshot.stackTrace;
if (error.type == DioExceptionType.badResponse) { return widget.errorBuilder(context, error, stackTrace);
return Center(child: Text(error.message ?? 'Bad response'));
}
return const Center(child: Text('Check your connection and retry'));
}
return Center(child: Text(error.toString()));
} }
final dataLoaded = snapshot.data?.every((it) => it) == true; final dataLoaded = snapshot.data?.every((it) => it) == true;
if (widget.showLoading && !dataLoaded) { if (widget.showLoading && !dataLoaded) {
return const Center( return widget.loadingBuilder(context);
child: CircularProgressIndicator.adaptive(),
);
} }
return widget.child; return widget.child;
}, },
@@ -4,6 +4,7 @@ import 'package:stream_chat/stream_chat.dart';
/// A signature for a callback which exposes an error and returns a function. /// A signature for a callback which exposes an error and returns a function.
/// This Callback can be used in cases where an API failure occurs and the /// This Callback can be used in cases where an API failure occurs and the
/// widget is unable to render data. /// widget is unable to render data.
// TODO: Add stacktrace as a parameter in v7.0.0
typedef ErrorBuilder = Widget Function(BuildContext context, Object error); typedef ErrorBuilder = Widget Function(BuildContext context, Object error);
/// A Signature for a handler function which will expose a [event]. /// A Signature for a handler function which will expose a [event].