diff --git a/.github/workflows/docusaurus.yml b/.github/workflows/docusaurus.yml index 5a234e8f4..6beaeb4f7 100644 --- a/.github/workflows/docusaurus.yml +++ b/.github/workflows/docusaurus.yml @@ -12,7 +12,7 @@ jobs: name: Publish docusaurus docs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Node 16 uses: actions/setup-node@v3 with: diff --git a/.github/workflows/legacy_version_analyze.yml b/.github/workflows/legacy_version_analyze.yml index 4b462c524..66e44437b 100644 --- a/.github/workflows/legacy_version_analyze.yml +++ b/.github/workflows/legacy_version_analyze.yml @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Git Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/pana.yml b/.github/workflows/pana.yml index 73074871f..6690dc785 100644 --- a/.github/workflows/pana.yml +++ b/.github/workflows/pana.yml @@ -23,7 +23,7 @@ jobs: stream_chat: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: axel-op/dart-package-analyzer@v3 id: analysis with: @@ -43,7 +43,7 @@ jobs: stream_chat_persistence: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: axel-op/dart-package-analyzer@v3 id: analysis with: @@ -64,7 +64,7 @@ jobs: stream_chat_flutter_core: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: axel-op/dart-package-analyzer@v3 id: analysis with: @@ -84,7 +84,7 @@ jobs: stream_chat_flutter: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: axel-op/dart-package-analyzer@v3 id: analysis with: @@ -104,7 +104,7 @@ jobs: stream_chat_localizations: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: axel-op/dart-package-analyzer@v3 id: analysis with: diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 5f7c04889..5665818f4 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Git Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: "Install Flutter" @@ -57,14 +57,14 @@ jobs: timeout-minutes: 15 steps: - name: "Git Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: "Install Flutter" uses: subosito/flutter-action@v2 with: cache: true - flutter-version: ${{ env.flutter_version }} + channel: ${{ env.flutter_channel }} - name: "Install Tools" run: | flutter pub global activate melos @@ -82,14 +82,14 @@ jobs: timeout-minutes: 30 steps: - name: "Git Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: "Install Flutter" uses: subosito/flutter-action@v2 with: cache: true - flutter-version: ${{ env.flutter_version }} + channel: ${{ env.flutter_channel }} - name: "Install Tools" run: | flutter pub global activate melos diff --git a/.github/workflows/vale-doc-lint.yml b/.github/workflows/vale-doc-lint.yml index d0253fb43..5246302c7 100644 --- a/.github/workflows/vale-doc-lint.yml +++ b/.github/workflows/vale-doc-lint.yml @@ -7,7 +7,7 @@ jobs: name: Vale doc linter runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: errata-ai/vale-action@reviewdog with: # added, diff_context, file, nofilter diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 762f4ad36..c1537e5c9 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,12 @@ +## 6.9.0 + +🐞 Fixed + +- [[#1716]](https://github.com/GetStream/stream-chat-flutter/issues/1716) Fixed client not able to + update message with `type: reply`. +- [[#1724]](https://github.com/GetStream/stream-chat-flutter/issues/1724) Fixed sendFile error + on `AttachmentFile` with `bytes` and no `name`. + ## 6.8.0 🐞 Fixed diff --git a/packages/stream_chat/lib/src/core/models/attachment_file.dart b/packages/stream_chat/lib/src/core/models/attachment_file.dart index ee6690ca9..73a428ea7 100644 --- a/packages/stream_chat/lib/src/core/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/core/models/attachment_file.dart @@ -74,13 +74,13 @@ class AttachmentFile { if (CurrentPlatform.isWeb) { multiPartFile = MultipartFile.fromBytes( bytes!, - filename: name, + filename: name ?? 'file', contentType: mimeType, ); } else { multiPartFile = await MultipartFile.fromFile( path!, - filename: name, + filename: name ?? 'file', contentType: mimeType, ); } diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index 37d9f8407..931107225 100644 --- a/packages/stream_chat/lib/src/core/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -168,8 +168,16 @@ class Message extends Equatable { late final MessageState state; /// The message type. + @JsonKey(includeIfNull: false, toJson: _typeToJson) 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 /// command or as a result of URL scraping. @JsonKey(includeIfNull: false) diff --git a/packages/stream_chat/lib/src/core/models/message.g.dart b/packages/stream_chat/lib/src/core/models/message.g.dart index 2eed96607..883408d0e 100644 --- a/packages/stream_chat/lib/src/core/models/message.g.dart +++ b/packages/stream_chat/lib/src/core/models/message.g.dart @@ -71,17 +71,27 @@ Message _$MessageFromJson(Map json) => Message( ), ); -Map _$MessageToJson(Message instance) => { - 'id': instance.id, - 'text': instance.text, - 'type': instance.type, - 'attachments': instance.attachments.map((e) => e.toJson()).toList(), - 'mentioned_users': User.toIds(instance.mentionedUsers), - 'parent_id': instance.parentId, - 'quoted_message_id': instance.quotedMessageId, - 'show_in_channel': instance.showInChannel, - 'silent': instance.silent, - 'pinned': instance.pinned, - 'pin_expires': instance.pinExpires?.toIso8601String(), - 'extra_data': instance.extraData, - }; +Map _$MessageToJson(Message instance) { + final val = { + 'id': instance.id, + 'text': instance.text, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + 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; +} diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index 085db925c..18b77e8d6 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header // ignore: constant_identifier_names -const PACKAGE_VERSION = '6.8.0'; +const PACKAGE_VERSION = '6.9.0'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index b03685f4c..458cbd141 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat homepage: https://getstream.io/ description: The official Dart client for Stream Chat, a service for building chat applications. -version: 6.8.0 +version: 6.9.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 5d3e46b09..65ed66cff 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,16 @@ +## 6.10.0 + +🐞 Fixed + +- [[#1721]](https://github.com/GetStream/stream-chat-flutter/issues/1721) + Fixed `StreamMessageInput.allowedAttachmentPickerTypes` not working on mobile devices. + +✅ Added + +- Added support for overriding the `MessageWidget.onReactionsHover` callback. + > **Note** + > Used only in desktop devices (web and desktop). + ## 6.9.0 🐞 Fixed @@ -11,7 +24,8 @@ - Added support for listening error events in AttachmentPickerBottomSheet. - 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) ```dart diff --git a/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/project.pbxproj b/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/project.pbxproj index 747ee4d99..fdd919322 100644 --- a/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/project.pbxproj @@ -159,7 +159,6 @@ C4CD72858CD59598795BB48E /* Pods-Runner.release.xcconfig */, 2BCA7399119839DE435DACD6 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -203,7 +202,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -427,7 +426,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 11.0; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; @@ -554,7 +553,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 11.0; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -575,7 +574,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 11.0; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; diff --git a/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 7fd7126b0..e495d20dd 100644 --- a/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ option.supportedTypes.every(allowedTypes.contains)), + }.where((option) => option.supportedTypes.every(allowedTypes.contains)), }, ); } diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index f99ee144d..c038cf023 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -56,6 +56,7 @@ class StreamMessageWidget extends StatefulWidget { this.onMentionTap, this.onMessageTap, this.onReactionsTap, + this.onReactionsHover, bool? showReactionPicker, @Deprecated('Use `showReactionPicker` instead') bool showReactionPickerIndicator = true, @@ -474,7 +475,8 @@ class StreamMessageWidget extends StatefulWidget { /// {@template showReactionPickerIndicator} /// Used in [StreamMessageReactionsModal] and [MessageActionsModal] - /// {@endtemplate} @Deprecated('Use `showReactionPicker` instead') + /// {@endtemplate} + @Deprecated('Use `showReactionPicker` instead') bool get showReactionPickerIndicator => showReactionPicker; /// {@template showReactionPickerTail} @@ -565,8 +567,16 @@ class StreamMessageWidget extends StatefulWidget { final void Function(Message)? onMessageTap; /// {@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; + /// {@template onReactionsHover} + /// + /// Note: Only used in desktop devices (web and desktop). + final OnReactionsHover? onReactionsHover; + /// {@template customActions} /// List of custom actions shown on message long tap /// {@endtemplate} @@ -640,6 +650,7 @@ class StreamMessageWidget extends StatefulWidget { bool? showInChannelIndicator, void Function(User)? onUserAvatarTap, void Function(String)? onLinkTap, + bool? showReactionBrowser, bool? showReactionPicker, @Deprecated('Use `showReactionPicker` instead') bool? showReactionPickerIndicator, @@ -662,6 +673,7 @@ class StreamMessageWidget extends StatefulWidget { OnQuotedMessageTap? onQuotedMessageTap, void Function(Message)? onMessageTap, OnReactionsTap? onReactionsTap, + OnReactionsHover? onReactionsHover, List? customActions, void Function(Message message, Attachment attachment)? onAttachmentTap, Widget Function(BuildContext, User)? userAvatarBuilder, @@ -752,6 +764,7 @@ class StreamMessageWidget extends StatefulWidget { onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap, onMessageTap: onMessageTap ?? this.onMessageTap, onReactionsTap: onReactionsTap ?? this.onReactionsTap, + onReactionsHover: onReactionsHover ?? this.onReactionsHover, customActions: customActions ?? this.customActions, onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap, userAvatarBuilder: userAvatarBuilder ?? this.userAvatarBuilder, @@ -978,7 +991,6 @@ class _StreamMessageWidgetState extends State reverse: widget.reverse, message: widget.message, hasNonUrlAttachments: hasNonUrlAttachments, - shouldShowReactions: shouldShowReactions, hasQuotedMessage: hasQuotedMessage, textPadding: widget.textPadding, attachmentBuilders: widget.attachmentBuilders, @@ -997,6 +1009,7 @@ class _StreamMessageWidgetState extends State ? widget.onReactionsTap!(widget.message) : _showMessageReactionsModal(context); }, + onReactionsHover: widget.onReactionsHover, showUserAvatar: widget.showUserAvatar, streamChat: _streamChat, translateUserAvatar: widget.translateUserAvatar, @@ -1258,8 +1271,9 @@ class _StreamMessageWidgetState extends State translateUserAvatar: false, showSendingIndicator: false, padding: EdgeInsets.zero, - // Show both the tail and indicator if the indicator is shown. - showReactionPickerTail: widget.showReactionPickerIndicator, + // Show both the tail if the picker is shown. + showReactionPicker: widget.showReactionPicker, + showReactionPickerTail: widget.showReactionPicker, showPinHighlight: false, showUserAvatar: widget.message.user!.id == channel.client.state.currentUser!.id @@ -1281,7 +1295,6 @@ class _StreamMessageWidgetState extends State showResendMessage: shouldShowResendAction, showCopyMessage: shouldShowCopyAction, showEditMessage: shouldShowEditAction, - showReactionPicker: widget.showReactionPickerIndicator, showReplyMessage: shouldShowReplyAction, showThreadReplyMessage: shouldShowThreadReplyAction, showFlagButton: widget.showFlagButton, diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart index 2910ee62a..b01c65588 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart @@ -36,8 +36,8 @@ class MessageWidgetContent extends StatelessWidget { required this.avatarWidth, required this.showReactions, required this.onReactionsTap, + required this.onReactionsHover, required this.messageTheme, - required this.shouldShowReactions, required this.streamChatTheme, required this.isFailedState, required this.hasQuotedMessage, @@ -114,17 +114,15 @@ class MessageWidgetContent extends StatelessWidget { /// {@macro showReactions} final bool showReactions; - /// Callback called when the reactions icon is tapped. - /// - /// Do not confuse this with the tap action on the reactions picker. + /// {@macro onReactionsTap} final VoidCallback onReactionsTap; + /// {@macro onReactionsHover} + final OnReactionsHover? onReactionsHover; + /// {@macro messageTheme} final StreamMessageThemeData messageTheme; - /// {@macro shouldShowReactions} - final bool shouldShowReactions; - /// {@macro onUserAvatarTap} final void Function(User)? onUserAvatarTap; @@ -295,7 +293,6 @@ class MessageWidgetContent extends StatelessWidget { messageTheme: messageTheme, ownId: streamChat.currentUser!.id, reverse: reverse, - shouldShowReactions: shouldShowReactions, onTap: onReactionsTap, ) : null, @@ -314,25 +311,15 @@ class MessageWidgetContent extends StatelessWidget { children: [ Padding( padding: showReactions - ? EdgeInsets.only( - top: message.reactionCounts - ?.isNotEmpty == - true - ? 18 - : 0, - ) + ? const EdgeInsets.only(top: 18) : EdgeInsets.zero, child: (message.isDeleted && !isFailedState) ? Container( - // ignore: lines_longer_than_80_chars margin: EdgeInsets.symmetric( - horizontal: - // ignore: lines_longer_than_80_chars - showUserAvatar == - // ignore: lines_longer_than_80_chars - DisplayWidget.gone - ? 0 - : 4.0, + horizontal: showUserAvatar == + DisplayWidget.gone + ? 0 + : 4.0, ), child: StreamDeletedMessage( borderRadiusGeometry: @@ -405,7 +392,7 @@ class MessageWidgetContent extends StatelessWidget { SizedBox(width: avatarWidth + 4), ], ), - if (isDesktopDeviceOrWeb && shouldShowReactions) ...[ + if (isDesktopDeviceOrWeb && showReactions) ...[ Padding( padding: showUserAvatar != DisplayWidget.gone ? EdgeInsets.only( @@ -416,7 +403,7 @@ class MessageWidgetContent extends StatelessWidget { child: DesktopReactionsBuilder( message: message, messageTheme: messageTheme, - shouldShowReactions: shouldShowReactions, + onHover: onReactionsHover, borderSide: borderSide, reverse: reverse, ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget/reactions/desktop_reactions_builder.dart b/packages/stream_chat_flutter/lib/src/message_widget/reactions/desktop_reactions_builder.dart index cbc55bdbb..753341083 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/reactions/desktop_reactions_builder.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/reactions/desktop_reactions_builder.dart @@ -1,3 +1,5 @@ +// ignore_for_file: cascade_invocations + import 'package:collection/collection.dart'; import 'package:flutter/foundation.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/stream_chat_flutter.dart'; -// ignore_for_file: cascade_invocations - /// {@template desktopReactionsBuilder} /// Builds a list of reactions to a message on desktop & web. /// @@ -16,16 +16,13 @@ class DesktopReactionsBuilder extends StatefulWidget { /// {@macro desktopReactionsBuilder} const DesktopReactionsBuilder({ super.key, - required this.shouldShowReactions, required this.message, required this.messageTheme, + this.onHover, this.borderSide, required this.reverse, }); - /// Whether reactions should be shown. - final bool shouldShowReactions; - /// The message to show reactions for. final Message message; @@ -35,6 +32,9 @@ class DesktopReactionsBuilder extends StatefulWidget { /// reactions matches the design spec for messages. final StreamMessageThemeData messageTheme; + /// Callback to run when the mouse enters or exits the reactions. + final OnReactionsHover? onHover; + /// {@macro borderSide} final BorderSide? borderSide; @@ -48,12 +48,6 @@ class DesktopReactionsBuilder extends StatefulWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add( - DiagnosticsProperty( - 'shouldShowReactions', - shouldShowReactions, - ), - ); properties.add( DiagnosticsProperty('message', message), ); @@ -81,18 +75,15 @@ class _DesktopReactionsBuilderState extends State { final streamChatTheme = StreamChatTheme.of(context); final reactionsMap = {}; - var reactionsList = []; - if (widget.shouldShowReactions) { - widget.message.latestReactions?.forEach((element) { - if (!reactionsMap.containsKey(element.type) || - element.user!.id == currentUser.id) { - reactionsMap[element.type] = element; - } - }); + widget.message.latestReactions?.forEach((element) { + if (!reactionsMap.containsKey(element.type) || + element.user!.id == currentUser.id) { + reactionsMap[element.type] = element; + } + }); - reactionsList = reactionsMap.values.toList() - ..sort((a, b) => a.user!.id == currentUser.id ? 1 : -1); - } + final reactionsList = reactionsMap.values.toList() + ..sort((a, b) => a.user!.id == currentUser.id ? 1 : -1); return PortalTarget( visible: _showReactionsPopup, @@ -100,17 +91,11 @@ class _DesktopReactionsBuilderState extends State { anchor: Aligned( target: widget.reverse ? Alignment.topRight : Alignment.topLeft, follower: widget.reverse ? Alignment.bottomRight : Alignment.bottomLeft, - shiftToWithinBound: const AxisFlag( - y: true, - ), + shiftToWithinBound: const AxisFlag(y: true), ), portalFollower: MouseRegion( - onEnter: (event) async { - setState(() => _showReactionsPopup = !_showReactionsPopup); - }, - onExit: (event) { - setState(() => _showReactionsPopup = !_showReactionsPopup); - }, + onEnter: (_) => _onReactionsHover(true), + onExit: (_) => _onReactionsHover(false), child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 336, @@ -125,12 +110,8 @@ class _DesktopReactionsBuilderState extends State { ), child: MouseRegion( cursor: SystemMouseCursors.click, - onEnter: (event) async { - setState(() => _showReactionsPopup = !_showReactionsPopup); - }, - onExit: (event) { - setState(() => _showReactionsPopup = !_showReactionsPopup); - }, + onEnter: (_) => _onReactionsHover(true), + onExit: (_) => _onReactionsHover(false), child: Padding( padding: EdgeInsets.symmetric( vertical: 2, @@ -161,6 +142,14 @@ class _DesktopReactionsBuilderState extends State { ), ); } + + void _onReactionsHover(bool isHovering) { + if (widget.onHover != null) { + return widget.onHover!(isHovering); + } + + setState(() => _showReactionsPopup = isHovering); + } } class _BottomReaction extends StatelessWidget { diff --git a/packages/stream_chat_flutter/lib/src/message_widget/reactions/reaction_indicator.dart b/packages/stream_chat_flutter/lib/src/message_widget/reactions/reaction_indicator.dart index 87bae5680..ef6e06eec 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/reactions/reaction_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/reactions/reaction_indicator.dart @@ -13,7 +13,6 @@ class ReactionIndicator extends StatelessWidget { super.key, required this.ownId, required this.message, - required this.shouldShowReactions, required this.onTap, required this.reverse, required this.messageTheme, @@ -25,9 +24,6 @@ class ReactionIndicator extends StatelessWidget { /// {@macro message} final Message message; - /// {@macro shouldShowReactions} - final bool shouldShowReactions; - /// The callback to perform when the widget is tapped or clicked. final VoidCallback onTap; @@ -50,34 +46,27 @@ class ReactionIndicator extends StatelessWidget { ..sort((a, b) => a.user!.id == ownId ? 1 : -1); return Transform( - transform: Matrix4.translationValues( - reverse ? 12 : -12, - 0, - 0, - ), + transform: Matrix4.translationValues(reverse ? 12 : -12, 0, 0), child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 22 * 6.0, ), child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), - child: shouldShowReactions - ? GestureDetector( - onTap: onTap, - child: StreamReactionBubble( - key: ValueKey('${message.id}.reactions'), - reverse: reverse, - flipTail: reverse, - backgroundColor: messageTheme.reactionsBackgroundColor ?? - Colors.transparent, - borderColor: - messageTheme.reactionsBorderColor ?? Colors.transparent, - maskColor: - messageTheme.reactionsMaskColor ?? Colors.transparent, - reactions: reactionsList, - ), - ) - : const SizedBox(), + child: GestureDetector( + onTap: onTap, + child: StreamReactionBubble( + key: ValueKey('${message.id}.reactions'), + reverse: reverse, + flipTail: reverse, + backgroundColor: + messageTheme.reactionsBackgroundColor ?? Colors.transparent, + borderColor: + messageTheme.reactionsBorderColor ?? Colors.transparent, + maskColor: messageTheme.reactionsMaskColor ?? Colors.transparent, + reactions: reactionsList, + ), + ), ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart index f278de0c0..9ecb72ed4 100644 --- a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart +++ b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart @@ -235,6 +235,12 @@ typedef OnMessageTap = void Function(Message); /// {@endtemplate} 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} /// The action to perform when tapping or clicking on a user in a // ignore: deprecated_member_use_from_same_package diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index b2978b52b..cadff0c2b 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 6.9.0 +version: 6.10.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -38,7 +38,7 @@ dependencies: rxdart: ^0.27.7 share_plus: ^7.1.0 shimmer: ^3.0.0 - stream_chat_flutter_core: ^6.8.0 + stream_chat_flutter_core: ^6.9.0 synchronized: ^3.1.0 thumblr: ^0.0.4 url_launcher: ^6.1.12 diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index aae7bb18b..93d46c975 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,8 @@ +## 6.9.0 + +- Added support for `StreamChannel.loadingBuilder` and `StreamChannel.errorBuilder` to customize + loading and error states. + ## 6.8.0 - Updated minimum supported `SDK` version to Flutter 3.10/Dart 3.0 @@ -35,9 +40,11 @@ - 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). -- [[#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. -- [[#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. ## 6.0.0 @@ -56,7 +63,8 @@ ## 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 diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart index 5bb8dee7f..095bbec93 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -15,6 +15,15 @@ enum QueryDirection { 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 /// /// Use [StreamChannel.of] to get the current [StreamChannelState] instance. @@ -27,6 +36,8 @@ class StreamChannel extends StatefulWidget { required this.channel, this.showLoading = true, this.initialMessageId, + this.errorBuilder = _defaultErrorBuilder, + this.loadingBuilder = _defaultLoadingBuilder, }); /// The child of the widget @@ -41,6 +52,31 @@ class StreamChannel extends StatefulWidget { /// If passed the channel will load from this particular message. 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 static StreamChannelState of(BuildContext context) { StreamChannelState? streamChannelState; @@ -430,22 +466,14 @@ class StreamChannelState extends State { ], builder: (context, snapshot) { if (snapshot.hasError) { - final error = snapshot.error; - 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())); + final error = snapshot.error!; + final stackTrace = snapshot.stackTrace; + return widget.errorBuilder(context, error, stackTrace); } final dataLoaded = snapshot.data?.every((it) => it) == true; if (widget.showLoading && !dataLoaded) { - return const Center( - child: CircularProgressIndicator.adaptive(), - ); + return widget.loadingBuilder(context); } return widget.child; }, diff --git a/packages/stream_chat_flutter_core/lib/src/typedef.dart b/packages/stream_chat_flutter_core/lib/src/typedef.dart index ace8d7e6a..4b69c79c5 100644 --- a/packages/stream_chat_flutter_core/lib/src/typedef.dart +++ b/packages/stream_chat_flutter_core/lib/src/typedef.dart @@ -4,6 +4,7 @@ import 'package:stream_chat/stream_chat.dart'; /// 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 /// widget is unable to render data. +// TODO: Add stacktrace as a parameter in v7.0.0 typedef ErrorBuilder = Widget Function(BuildContext context, Object error); /// A Signature for a handler function which will expose a [event]. diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 655421b53..643df918f 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 6.8.0 +version: 6.9.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -17,7 +17,7 @@ dependencies: freezed_annotation: ^2.4.1 meta: ^1.9.1 rxdart: ^0.27.7 - stream_chat: ^6.8.0 + stream_chat: ^6.9.0 dev_dependencies: build_runner: ^2.4.6 diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md index 98a08c7c2..cd79033e2 100644 --- a/packages/stream_chat_localizations/CHANGELOG.md +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.10.0 + +* Updated `stream_chat_flutter` dependency to [`6.10.0`](https://pub.dev/packages/stream_chat_flutter/changelog). + ## 5.9.0 * Updated minimum supported `SDK` version to Flutter 3.10/Dart 3.0 diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index cc391977a..d6f85f81c 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -1,6 +1,6 @@ name: stream_chat_localizations description: The Official localizations for Stream Chat Flutter, a service for building chat applications -version: 5.9.0 +version: 5.10.0 homepage: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -14,7 +14,7 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - stream_chat_flutter: ^6.9.0 + stream_chat_flutter: ^6.10.0 dev_dependencies: flutter_test: diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index 394a54a1e..3fe320927 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.9.0 + +- Updated `stream_chat` dependency to [`6.9.0`](https://pub.dev/packages/stream_chat/changelog). + ## 6.8.0 - Updated minimum supported `SDK` version to Flutter 3.10/Dart 3.0 diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 0656cea67..fc4e85ebc 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_persistence homepage: https://github.com/GetStream/stream-chat-flutter description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. -version: 6.8.0 +version: 6.9.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -18,7 +18,7 @@ dependencies: path: ^1.8.3 path_provider: ^2.1.0 sqlite3_flutter_libs: ^0.5.15 - stream_chat: ^6.8.0 + stream_chat: ^6.9.0 dev_dependencies: build_runner: ^2.4.6