diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index a5ffc54df..54f9025e3 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -1,7 +1,7 @@ name: Dart Code Metrics env: - flutter_version: "3.7.0" + flutter_version: "3.10.0" folders: "lib, test" on: diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index b63933bb7..03c7c49c3 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -2,7 +2,7 @@ name: stream_flutter_workflow env: ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' - flutter_version: "3.7.0" + flutter_version: "3.10.0" on: pull_request: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..4094801ee --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# Reporting a Vulnerability +At Stream we are committed to the security of our Software. We appreciate your efforts in disclosing vulnerabilities responsibly and we will make every effort to acknowledge your contributions. + +Report security vulnerabilities at the following email address: +``` +[security@getstream.io](mailto:security@getstream.io) +``` +Alternatively it is also possible to open a new issue in the affected repository, tagging it with the `security` tag. + +A team member will acknowledge the vulnerability and will follow-up with more detailed information. A representative of the security team will be in touch if more information is needed. + +# Information to include in a report +While we appreciate any information that you are willing to provide, please make sure to include the following: +* Which repository is affected +* Which branch, if relevant +* Be as descriptive as possible, the team will replicate the vulnerability before working on a fix. diff --git a/melos.yaml b/melos.yaml index 23289dffd..6b93f812f 100644 --- a/melos.yaml +++ b/melos.yaml @@ -26,9 +26,9 @@ scripts: - Note: you can also rely on your IDEs Dart Analysis / Issues window. format: - run: flutter format --set-exit-if-changed . + run: dart format --set-exit-if-changed . description: | - Run `flutter format --set-exit-if-changed .` in all packages. + Run `dart format --set-exit-if-changed .` in all packages. metrics: run: | diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 3abd5f22f..b001d99e7 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,14 @@ +## 6.2.0 + +🐞 Fixed + +- [[#1422]](https://github.com/GetStream/stream-chat-flutter/issues/1422) Fixed `User.createdAt` property using + currentTime when the ws connection is not established. + +✅ Added + +- Added support for `ChatPersistenceClient.isConnected` for checking if the client is connected to the database. + ## 6.1.0 🐞 Fixed diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index a280d18c5..dd0b77048 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -35,8 +35,8 @@ class User extends Equatable { this.role, String? name, String? image, - DateTime? createdAt, - DateTime? updatedAt, + this.createdAt, + this.updatedAt, this.lastActive, Map extraData = const {}, this.online = false, @@ -44,8 +44,7 @@ class User extends Equatable { this.banExpires, this.teams = const [], this.language, - }) : createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(), + }) : // For backwards compatibility, set 'name', 'image' in [extraData]. extraData = { ...extraData, @@ -104,11 +103,11 @@ class User extends Equatable { /// Date of user creation. @JsonKey(includeToJson: false) - final DateTime createdAt; + final DateTime? createdAt; /// Date of last user update. @JsonKey(includeToJson: false) - final DateTime updatedAt; + final DateTime? updatedAt; /// Date of last user connection. @JsonKey(includeToJson: false) diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 1826c4b55..4710190e8 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -14,6 +14,9 @@ import 'package:stream_chat/src/core/util/extension.dart'; /// A simple client used for persisting chat data locally. abstract class ChatPersistenceClient { + /// Whether the connection is established. + bool get isConnected; + /// Creates a new connection to the client Future connect(String userId); diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index a2567eda3..8fdd5ebcf 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.1.0'; +const PACKAGE_VERSION = '6.2.0'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 1f2ae6a50..62bf7b52a 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.1.0 +version: 6.2.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/test/src/core/models/user_test.dart b/packages/stream_chat/test/src/core/models/user_test.dart index c8e04ede4..fe44dc871 100644 --- a/packages/stream_chat/test/src/core/models/user_test.dart +++ b/packages/stream_chat/test/src/core/models/user_test.dart @@ -198,8 +198,8 @@ void main() { expect(user.banned, false); expect(user.teams, []); expect(user.lastActive, null); - expect(user.createdAt, isNotNull); - expect(user.updatedAt, isNotNull); + expect(user.createdAt, null); + expect(user.updatedAt, null); }); test('default values, parse json', () { @@ -214,8 +214,8 @@ void main() { expect(user.banned, false); expect(user.teams, []); expect(user.lastActive, null); - expect(user.createdAt, isNotNull); - expect(user.updatedAt, isNotNull); + expect(user.createdAt, null); + expect(user.updatedAt, null); }); }); } diff --git a/packages/stream_chat/test/src/db/chat_persistence_client_test.dart b/packages/stream_chat/test/src/db/chat_persistence_client_test.dart index 33e9b52b2..62a7cf25e 100644 --- a/packages/stream_chat/test/src/db/chat_persistence_client_test.dart +++ b/packages/stream_chat/test/src/db/chat_persistence_client_test.dart @@ -12,6 +12,9 @@ import 'package:stream_chat/src/db/chat_persistence_client.dart'; import 'package:test/test.dart'; class TestPersistenceClient extends ChatPersistenceClient { + @override + bool get isConnected => throw UnimplementedError(); + @override Future connect(String userId) => throw UnimplementedError(); diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index f32ef2d2b..0ea80c8f1 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,43 @@ +## 6.2.0 + +🐞 Fixed + +- [[#1546]](https://github.com/GetStream/stream-chat-flutter/issues/1546) + Fixed `StreamMessageInputTheme.linkHighlightColor` returning null for default theme. +- [[#1548]](https://github.com/GetStream/stream-chat-flutter/issues/1548) Fixed `StreamMessageInput` urlRegex only + matching the lowercase `http(s)|ftp`. +- [[#1542]](https://github.com/GetStream/stream-chat-flutter/issues/1542) Handle error thrown in `StreamMessageInput` + when unable to fetch a link preview. +- [[#1540]](https://github.com/GetStream/stream-chat-flutter/issues/1540) Use `CircularProgressIndicator.adaptive` + instead of material indicator. +- [[#1490]](https://github.com/GetStream/stream-chat-flutter/issues/1490) Fixed `editMessageInputBuilder` property not + used in `MessageActionsModal.editMessage` option. +- [[#1544]](https://github.com/GetStream/stream-chat-flutter/issues/1544) Fixed error thrown when unable to fetch + image/data in Message link preview. +- [[#1482]](https://github.com/GetStream/stream-chat-flutter/issues/1482) Fixed `StreaChannelListTile` not showing + unread indicator when `currentUser` is not present in the initial member list. +- [[#1487]](https://github.com/GetStream/stream-chat-flutter/issues/1487) Use localized title + for `WebOrDesktopAttachmentPickerOption` in `StreamMessageInput`. +- [[#1250]](https://github.com/GetStream/stream-chat-flutter/issues/1250) Fixed bottomRow widgetSpans getting resized + twice when `textScaling` is enabled. +- [[#1498]](https://github.com/GetStream/stream-chat-flutter/issues/1498) Fixed `MessageInput` autocomplete not working + on non-mobile platforms. +- [[#1576]](https://github.com/GetStream/stream-chat-flutter/issues/1576) Temporary fix for `StreamMessageListView` + getting broken when loaded at a particular message and a new message is added. + +✅ Added + +- Added support for `StreamMessageThemeData.urlAttachmentTextMaxLine` to specify the `.maxLines` for the url attachment + text. [#1543](https://github.com/GetStream/stream-chat-flutter/issues/1543) + +🔄 Changed + +- Updated `shimmer` dependency to `^3.0.0`. +- Updated `image_gallery_saver` dependency to `^2.0.1`. +- Deprecated `ChannelPreview` in favor of `StreamChannelListTile`. +- Updated `stream_chat_flutter_core` dependency + to [`6.2.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog). + ## 6.1.0 🐞 Fixed diff --git a/packages/stream_chat_flutter/README.md b/packages/stream_chat_flutter/README.md index 380b06d88..379a15eb9 100644 --- a/packages/stream_chat_flutter/README.md +++ b/packages/stream_chat_flutter/README.md @@ -196,7 +196,7 @@ import 'package:stream_chat_persistence/stream_chat_persistence.dart'; final chatPersistentClient = StreamChatPersistenceClient( logLevel: Level.INFO, - connectionMode: ConnectionMode.background, + connectionMode: ConnectionMode.regular, ); final client = StreamChatClient( diff --git a/packages/stream_chat_flutter/example/lib/split_view.dart b/packages/stream_chat_flutter/example/lib/split_view.dart index bbfe63016..4a83a4501 100644 --- a/packages/stream_chat_flutter/example/lib/split_view.dart +++ b/packages/stream_chat_flutter/example/lib/split_view.dart @@ -128,12 +128,12 @@ class ChannelPage extends StatelessWidget { @override Widget build(BuildContext context) => Navigator( onGenerateRoute: (settings) => MaterialPageRoute( - builder: (context) => Scaffold( - appBar: const StreamChannelHeader( + builder: (context) => const Scaffold( + appBar: StreamChannelHeader( showBackButton: false, ), body: Column( - children: const [ + children: [ Expanded( child: StreamMessageListView(), ), diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_1.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_1.dart index d847fa3a7..ac6908e3e 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_1.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_1.dart @@ -91,10 +91,10 @@ class ChannelPage extends StatelessWidget { @override // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { - return Scaffold( - appBar: const StreamChannelHeader(), + return const Scaffold( + appBar: StreamChannelHeader(), body: Column( - children: const [ + children: [ Expanded( child: StreamMessageListView(), ), diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart index 0c509b514..d6b972fc0 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart @@ -126,10 +126,10 @@ class ChannelPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: const StreamChannelHeader(), + return const Scaffold( + appBar: StreamChannelHeader(), body: Column( - children: const [ + children: [ Expanded( child: StreamMessageListView(), ), diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart index 39dca5fa5..fa42566ac 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart @@ -168,10 +168,10 @@ class ChannelPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: const StreamChannelHeader(), + return const Scaffold( + appBar: StreamChannelHeader(), body: Column( - children: const [ + children: [ Expanded( child: StreamMessageListView(), ), diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index defb72573..7e051f96c 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -28,7 +28,7 @@ dependencies: cupertino_icons: ^1.0.4 flutter: sdk: flutter - responsive_builder: ^0.6.4 + responsive_builder: ^0.7.0 stream_chat_flutter: path: ../ stream_chat_localizations: diff --git a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart index 299c42573..efebaf692 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -200,7 +200,7 @@ class _FileTypeImage extends StatelessWidget { child: SizedBox( width: 20, height: 20, - child: CircularProgressIndicator(), + child: CircularProgressIndicator.adaptive(), ), ), ), @@ -210,7 +210,7 @@ class _FileTypeImage extends StatelessWidget { child: SizedBox( width: 20, height: 20, - child: CircularProgressIndicator(), + child: CircularProgressIndicator.adaptive(), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart index be756acaf..85997b644 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -59,6 +59,7 @@ class StreamGiphyAttachment extends StreamAttachmentWidget { color: StreamChatTheme.of(context).colorTheme.barsBg, elevation: 2, clipBehavior: Clip.hardEdge, + margin: EdgeInsets.zero, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topRight: Radius.circular(16), @@ -115,7 +116,7 @@ class StreamGiphyAttachment extends StreamAttachmentWidget { width: constraints?.maxHeight, height: constraints?.maxWidth, child: const Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator.adaptive(), ), ), imageUrl: imageUrl, @@ -241,10 +242,7 @@ class StreamGiphyAttachment extends StreamAttachmentWidget { const SizedBox(height: 4), const Align( alignment: Alignment.centerRight, - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: StreamVisibleFootnote(), - ), + child: StreamVisibleFootnote(), ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart index a8ed00c66..a628ee60c 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart @@ -1,5 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamUrlAttachment} @@ -64,14 +65,33 @@ class StreamUrlAttachment extends StatelessWidget { ), child: Stack( children: [ - CachedNetworkImage( - width: double.infinity, - imageUrl: urlAttachment.imageUrl!, - fit: BoxFit.cover, + AspectRatio( + // Default aspect ratio for Open Graph images. + // https://www.kapwing.com/resources/what-is-an-og-image-make-and-format-og-images-for-your-blog-or-webpage + aspectRatio: 1.91 / 1, + child: CachedNetworkImage( + imageUrl: urlAttachment.imageUrl!, + fit: BoxFit.cover, + placeholder: (context, __) { + final image = Image.asset( + 'images/placeholder.png', + fit: BoxFit.cover, + package: 'stream_chat_flutter', + ); + final colorTheme = + StreamChatTheme.of(context).colorTheme; + return Shimmer.fromColors( + baseColor: colorTheme.disabled, + highlightColor: colorTheme.inputBg, + child: image, + ); + }, + errorWidget: (_, __, ___) => const AttachmentError(), + ), ), Positioned( left: 0, - bottom: -1, + bottom: 0, child: DecoratedBox( decoration: BoxDecoration( borderRadius: const BorderRadius.only( @@ -83,7 +103,8 @@ class StreamUrlAttachment extends StatelessWidget { padding: const EdgeInsets.only( top: 8, left: 8, - right: 8, + right: 12, + bottom: 4, ), child: Text( hostDisplayName, @@ -99,20 +120,40 @@ class StreamUrlAttachment extends StatelessWidget { padding: textPadding, child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [ + children: [ if (urlAttachment.title != null) - Text( - urlAttachment.title!.trim(), - maxLines: messageTheme.urlAttachmentTitleMaxLine ?? 1, - overflow: TextOverflow.ellipsis, - style: messageTheme.urlAttachmentTitleStyle, - ), + Builder(builder: (context) { + final maxLines = messageTheme.urlAttachmentTitleMaxLine; + + TextOverflow? overflow; + if (maxLines != null && maxLines > 0) { + overflow = TextOverflow.ellipsis; + } + + return Text( + urlAttachment.title!.trim(), + maxLines: maxLines, + overflow: overflow, + style: messageTheme.urlAttachmentTitleStyle, + ); + }), if (urlAttachment.text != null) - Text( - urlAttachment.text!, - style: messageTheme.urlAttachmentTextStyle, - ), - ], + Builder(builder: (context) { + final maxLines = messageTheme.urlAttachmentTextMaxLine; + + TextOverflow? overflow; + if (maxLines != null && maxLines > 0) { + overflow = TextOverflow.ellipsis; + } + + return Text( + urlAttachment.text!, + maxLines: maxLines, + overflow: overflow, + style: messageTheme.urlAttachmentTextStyle, + ); + }), + ].insertBetween(const SizedBox(height: 4)), ), ), ], diff --git a/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart b/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart index 47a62a324..2125c71be 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart @@ -345,9 +345,11 @@ class AttachmentActionsModal extends StatelessWidget { child: Stack( fit: StackFit.expand, children: [ - CircularProgressIndicator( + CircularProgressIndicator.adaptive( strokeWidth: 8, - color: theme.colorTheme.accentPrimary, + valueColor: AlwaysStoppedAnimation( + theme.colorTheme.accentPrimary, + ), ), Center( child: Text( diff --git a/packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart b/packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart index ea9338d01..23ec54926 100644 --- a/packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart +++ b/packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart @@ -507,10 +507,12 @@ class _StreamAutocompleteState extends State { final anchor = widget.optionsAlignment._toAnchor(); final shouldShowOptions = _shouldShowOptions; final optionViewBuilder = shouldShowOptions - ? _currentTrigger!.optionsViewBuilder( - context, - _currentQuery!, - _messageEditingController, + ? TextFieldTapRegion( + child: _currentTrigger!.optionsViewBuilder( + context, + _currentQuery!, + _messageEditingController, + ), ) : null; diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel/channel_info.dart index 8a50f12aa..5c32d2789 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_info.dart @@ -143,7 +143,7 @@ class _ConnectingTitleState extends StatelessWidget { height: 16, width: 16, child: Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator.adaptive(), ), ), const SizedBox(width: 10), diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart index 20c214b10..0e7973cd3 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart @@ -244,7 +244,7 @@ class _ConnectingTitleState extends StatelessWidget { height: 16, width: 16, child: Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator.adaptive(), ), ), const SizedBox(width: 10), diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel/channel_preview.dart index 2ce6983c8..ae6247ea2 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_preview.dart @@ -24,6 +24,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// The UI is rendered based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget's appearance. /// {@endtemplate} +@Deprecated('Use StreamChannelListTile instead.') class ChannelPreview extends StatelessWidget { /// {@macro channelPreview} const ChannelPreview({ diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart index 273ba1a25..a6feaa5e9 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart @@ -337,7 +337,7 @@ class _FullScreenMediaState extends State { final controller = videoPackages[attachment.id]!; if (!controller.initialized) { return const Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator.adaptive(), ); } return InkWell( diff --git a/packages/stream_chat_flutter/lib/src/indicators/upload_progress_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/upload_progress_indicator.dart index 2260cf9e0..4c14aa5cb 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/upload_progress_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/upload_progress_indicator.dart @@ -51,7 +51,7 @@ class StreamUploadProgressIndicator extends StatelessWidget { SizedBox( height: 16, width: 16, - child: CircularProgressIndicator( + child: CircularProgressIndicator.adaptive( strokeWidth: 3, valueColor: AlwaysStoppedAnimation(progressIndicatorColor), ), diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart index 2ccb0bc2b..b6c43c768 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart @@ -400,8 +400,9 @@ class _MessageActionsModalState extends State { ), ), builder: (context) => EditMessageSheet( - message: widget.message, channel: channel, + message: widget.message, + editMessageInputBuilder: widget.editMessageInputBuilder, ), ); } diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart index 60e88103e..c3de7e133 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart @@ -792,19 +792,19 @@ Widget webOrDesktopAttachmentPickerBuilder({ key: 'image-picker', type: AttachmentPickerType.images, icon: StreamSvgIcon.pictures(size: 36).toIconThemeSvgIcon(), - title: 'Upload a photo', + title: context.translations.uploadAPhotoLabel, ), WebOrDesktopAttachmentPickerOption( key: 'video-picker', type: AttachmentPickerType.videos, icon: StreamSvgIcon.record(size: 36).toIconThemeSvgIcon(), - title: 'Upload a video', + title: context.translations.uploadAVideoLabel, ), WebOrDesktopAttachmentPickerOption( key: 'file-picker', type: AttachmentPickerType.files, icon: StreamSvgIcon.files(size: 36).toIconThemeSvgIcon(), - title: 'Upload a file', + title: context.translations.uploadAFileLabel, ), }, onOptionTap: (context, controller, option) async { diff --git a/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart b/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart index 33145852c..554e8d125 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart @@ -303,7 +303,7 @@ class _ParseAttachments extends StatelessWidget { width: size.width, height: size.height, child: const Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator.adaptive(), ), ); }, @@ -394,7 +394,7 @@ class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> { width: 32, child: _controller.value.isInitialized ? VideoPlayer(_controller) - : const CircularProgressIndicator(), + : const CircularProgressIndicator.adaptive(), ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 724535766..c3a7f7eff 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -1042,6 +1042,7 @@ class StreamMessageInputState extends State CancelableOperation? _enrichUrlOperation; final _urlRegex = RegExp( r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+', + caseSensitive: false, ); void _checkContainsUrl(String value, BuildContext context) async { @@ -1103,8 +1104,12 @@ class StreamMessageInputState extends State var response = _ogAttachmentCache[url]; if (response == null) { final client = StreamChat.of(context).client; - response = await client.enrichUrl(url); - _ogAttachmentCache[url] = response; + try { + response = await client.enrichUrl(url); + _ogAttachmentCache[url] = response; + } catch (e, stk) { + return Future.error(e, stk); + } } return response; } @@ -1367,10 +1372,10 @@ class StreamMessageInputState extends State } final streamChannel = StreamChannel.of(context); + final channel = streamChannel.channel; var message = _effectiveController.value; - if (!streamChannel.channel.ownCapabilities - .contains(PermissionType.sendLinks) && + if (!channel.ownCapabilities.contains(PermissionType.sendLinks) && _urlRegex.allMatches(message.text ?? '').any((element) => element.group(0)?.split('.').last.isValidTLD() == true)) { showInfoBottomSheet( @@ -1396,8 +1401,8 @@ class StreamMessageInputState extends State final skipEnrichUrl = _effectiveController.ogAttachment == null; var shouldKeepFocus = widget.shouldKeepFocusAfterMessage; - shouldKeepFocus ??= !_commandEnabled; + widget.onQuotedMessageCleared?.call(); _effectiveController.reset(); @@ -1406,12 +1411,35 @@ class StreamMessageInputState extends State message = await widget.preMessageSending!(message); } - final channel = streamChannel.channel; + message = message.replaceMentionsWithId(); + + // If the channel is not up to date, we should reload it before sending + // the message. if (!channel.state!.isUpToDate) { await streamChannel.reloadChannel(); + + // We need to wait for the frame to be rendered with the updated channel + // state before sending the message. + await WidgetsBinding.instance.endOfFrame; } - message = message.replaceMentionsWithId(); + await _sendOrUpdateMessage( + message: message, + skipEnrichUrl: skipEnrichUrl, + ); + + if (shouldKeepFocus) { + FocusScope.of(context).requestFocus(_effectiveFocusNode); + } else { + FocusScope.of(context).unfocus(); + } + } + + Future _sendOrUpdateMessage({ + required Message message, + bool skipEnrichUrl = false, + }) async { + final channel = StreamChannel.of(context).channel; try { Future sendingFuture; @@ -1427,12 +1455,6 @@ class StreamMessageInputState extends State ); } - if (shouldKeepFocus) { - FocusScope.of(context).requestFocus(_effectiveFocusNode); - } else { - FocusScope.of(context).unfocus(); - } - final resp = await sendingFuture; if (resp.message?.type == 'error') { _effectiveController.message = message; diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/loading_indicator.dart b/packages/stream_chat_flutter/lib/src/message_list_view/loading_indicator.dart index 7e0e8b3ed..23229c337 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/loading_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/loading_indicator.dart @@ -52,7 +52,7 @@ class LoadingIndicator extends StatelessWidget { const Center( child: Padding( padding: EdgeInsets.all(8), - child: CircularProgressIndicator(), + child: CircularProgressIndicator.adaptive(), ), ); }, diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart index 63fe25a19..4c29eb691 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart @@ -432,7 +432,7 @@ class _StreamMessageListViewState extends State { messageFilter: widget.messageFilter, loadingBuilder: widget.loadingBuilder ?? (context) => const Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator.adaptive(), ), emptyBuilder: widget.emptyBuilder ?? (context) => Center( @@ -566,21 +566,33 @@ class _StreamMessageListViewState extends State { reverse: widget.reverse, shrinkWrap: widget.shrinkWrap, itemCount: itemCount, - findChildIndexCallback: (Key key) { - final indexedKey = key as IndexedKey; - final valueKey = indexedKey.key as ValueKey?; - if (valueKey != null) { - final index = messagesIndex[valueKey.value]; - if (index != null) { - // The calculation is as follows: - // * Add 2 to the index retrieved to account for the footer and the bottom loader. - // * Multiply the result by 2 to account for the separators between each pair of items. - // * Subtract 1 to adjust for the 0-based indexing of the list view. - return ((index + 2) * 2) - 1; - } - } - return null; - }, + + // Commented out as it is not working as expected. + // The list view gets broken in the following case: + // * The list view is loaded at a particular message (eg: Last Read, or a quoted message) + // and a new message is added to the list view. + // + // Issues faced: + // * https://github.com/GetStream/stream-chat-flutter/issues/1576 + // * https://github.com/GetStream/stream-chat-flutter/issues/1414 + // + // Related issues: https://github.com/flutter/flutter/issues/107123 + // + // findChildIndexCallback: (Key key) { + // final indexedKey = key as IndexedKey; + // final valueKey = indexedKey.key as ValueKey?; + // if (valueKey != null) { + // final index = messagesIndex[valueKey.value]; + // if (index != null) { + // // The calculation is as follows: + // // * Add 2 to the index retrieved to account for the footer and the bottom loader. + // // * Multiply the result by 2 to account for the separators between each pair of items. + // // * Subtract 1 to adjust for the 0-based indexing of the list view. + // return ((index + 2) * 2) - 1; + // } + // } + // return null; + // }, // Item Count -> 8 (1 parent, 2 header+footer, 2 top+bottom, 3 messages) // eg: |Type| rev(|Index(item)|) rev(|Index(separator)|) |Index(item)| |Index(separator)| @@ -899,7 +911,10 @@ class _StreamMessageListViewState extends State { final hasUrlAttachment = message.attachments.any((it) => it.ogScrapeUrl != null); - final borderSide = isOnlyEmoji || hasUrlAttachment ? BorderSide.none : null; + final isEphemeral = message.isEphemeral; + + final borderSide = + isOnlyEmoji || hasUrlAttachment || isEphemeral ? BorderSide.none : null; final defaultMessageWidget = StreamMessageWidget( showReplyMessage: false, @@ -980,24 +995,7 @@ class _StreamMessageListViewState extends State { FloatingActionButton( backgroundColor: _streamTheme.colorTheme.barsBg, onPressed: () async { - if (unreadCount > 0) { - streamChannel!.channel.markRead(); - } - if (!_upToDate) { - _bottomPaginationActive = false; - initialAlignment = 0; - initialIndex = 0; - await streamChannel!.reloadChannel(); - - WidgetsBinding.instance.addPostFrameCallback((_) { - _scrollController!.jumpTo(index: 0); - }); - } else { - _showScrollToBottom.value = false; - _scrollController!.jumpTo( - index: 0, - ); - } + return scrollToBottomDefaultTapAction(unreadCount); }, child: widget.reverse ? StreamSvgIcon.down( @@ -1101,10 +1099,13 @@ class _StreamMessageListViewState extends State { final showThreadReplyIndicator = !_isThreadConversation && hasReplies; final isOnlyEmoji = message.text?.isOnlyEmoji ?? false; + final isEphemeral = message.isEphemeral; + final hasUrlAttachment = message.attachments.any((it) => it.ogScrapeUrl != null); - final borderSide = isOnlyEmoji || hasUrlAttachment ? BorderSide.none : null; + final borderSide = + isOnlyEmoji || hasUrlAttachment || isEphemeral ? BorderSide.none : null; final currentUser = StreamChat.of(context).currentUser; final members = StreamChannel.of(context).channel.state?.members ?? []; diff --git a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart b/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart index 8d650f5c6..5ca2462a5 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart @@ -153,8 +153,6 @@ class BottomRow extends StatelessWidget { } } - final children = []; - final threadParticipants = message.threadParticipants?.take(2); final showThreadParticipants = threadParticipants?.isNotEmpty == true; final replyCount = message.replyCount; @@ -183,75 +181,69 @@ class BottomRow extends StatelessWidget { const usernameKey = Key('username'); - children.addAll([ + final children = [ if (showUsername) - WidgetSpan( - child: usernameBuilder?.call(context, message) ?? - Username( - key: usernameKey, - message: message, - messageTheme: messageTheme, - ), - ), + usernameBuilder?.call(context, message) ?? + Username( + key: usernameKey, + message: message, + messageTheme: messageTheme, + ), if (showTimeStamp) - WidgetSpan( - child: Text( - Jiffy(message.createdAt.toLocal()).jm, - style: messageTheme.createdAtStyle, - ), + Text( + Jiffy(message.createdAt.toLocal()).jm, + style: messageTheme.createdAtStyle, ), if (showSendingIndicator) - WidgetSpan( - child: sendingIndicatorBuilder?.call(context, message) ?? - SendingIndicatorBuilder( - messageTheme: messageTheme, - message: message, - hasNonUrlAttachments: hasNonUrlAttachments, - streamChat: streamChat, - streamChatTheme: streamChatTheme, - ), - ), - ]); + sendingIndicatorBuilder?.call(context, message) ?? + SendingIndicatorBuilder( + messageTheme: messageTheme, + message: message, + hasNonUrlAttachments: hasNonUrlAttachments, + streamChat: streamChat, + streamChatTheme: streamChatTheme, + ), + ]; final showThreadTail = !(hasUrlAttachments || isGiphy || isOnlyEmoji) && (showThreadReplyIndicator || showInChannel); - final threadIndicatorWidgets = [ + final threadIndicatorWidgets = [ if (showThreadTail) - WidgetSpan( - child: Padding( - padding: EdgeInsets.only( - bottom: context.textScaleFactor * - ((messageTheme.repliesStyle?.fontSize ?? 1) / 2), - ), - child: CustomPaint( - size: const Size(16, 32) * context.textScaleFactor, - painter: ThreadReplyPainter( - context: context, - color: messageTheme.messageBorderColor, - reverse: reverse, + // Added builder to use the nearest context to get the right + // textScaleFactor value. + Builder( + builder: (context) { + return Padding( + padding: EdgeInsets.only( + bottom: context.textScaleFactor * + ((messageTheme.repliesStyle?.fontSize ?? 1) / 2), ), - ), - ), + child: CustomPaint( + size: const Size(16, 32) * context.textScaleFactor, + painter: ThreadReplyPainter( + context: context, + color: messageTheme.messageBorderColor, + reverse: reverse, + ), + ), + ); + }, ), if (showInChannel || showThreadReplyIndicator) ...[ if (showThreadParticipants) - WidgetSpan( - child: SizedBox.fromSize( - size: Size((threadParticipants!.length * 8.0) + 8, 16), - child: ThreadParticipants( - threadParticipants: threadParticipants, - streamChatTheme: streamChatTheme, - ), + SizedBox.fromSize( + size: Size((threadParticipants!.length * 8.0) + 8, 16), + child: ThreadParticipants( + threadParticipants: threadParticipants, + streamChatTheme: streamChatTheme, ), ), - WidgetSpan( - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: _onThreadTap, - child: Text(msg, style: messageTheme.repliesStyle), - ), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: _onThreadTap, + child: Text(msg, style: messageTheme.repliesStyle), ), ), ], @@ -266,8 +258,21 @@ class BottomRow extends StatelessWidget { return Text.rich( TextSpan( children: [ - ...children, - ].insertBetween(const WidgetSpan(child: SizedBox(width: 8))), + ...children.insertBetween(const SizedBox(width: 8)).map((child) { + final mediaQueryData = MediaQuery.of(context); + return WidgetSpan( + child: MediaQuery( + // Hardcoding the textScaleFactor to 1 to avoid the multiple + // resizing of the text. This is needed because the + // textScaleFactor is already applied to the textSpan. + // + // issue: https://github.com/GetStream/stream-chat-flutter/issues/1250 + data: mediaQueryData.copyWith(textScaleFactor: 1), + child: child, + ), + ); + }), + ], ), maxLines: 1, textAlign: reverse ? TextAlign.right : TextAlign.left, 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 0eb30cfbf..b002d2b5c 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 @@ -217,17 +217,9 @@ class StreamMessageWidget extends StatefulWidget { ); }, 'giphy': (context, message, attachments) { - final border = RoundedRectangleBorder( - side: attachmentBorderSide ?? - BorderSide( - color: StreamChatTheme.of(context).colorTheme.borders, - ), - borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, - ); - - return WrapAttachmentWidget( - attachmentWidget: Column( - children: attachments.map((attachment) { + final attachmentWidget = Column( + children: [ + ...attachments.map((attachment) { final mediaQueryData = MediaQuery.of(context); return StreamGiphyAttachment( attachment: attachment, @@ -240,14 +232,25 @@ class StreamMessageWidget extends StatefulWidget { onShowMessage: onShowMessage, onReplyMessage: onReplyTap, onAttachmentTap: onAttachmentTap != null - ? () { - onAttachmentTap(message, attachment); - } + ? () => onAttachmentTap(message, attachment) : null, ); - }).toList(), - ), + }), + ], + ); + + // If the message is ephemeral, we don't want to show the border. + if (message.isEphemeral) return attachmentWidget; + + final color = StreamChatTheme.of(context).colorTheme.borders; + final border = RoundedRectangleBorder( + side: attachmentBorderSide ?? BorderSide(color: color), + borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, + ); + + return WrapAttachmentWidget( attachmentShape: border, + attachmentWidget: attachmentWidget, ); }, 'file': (context, message, attachments) { diff --git a/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart b/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart index c1f6c144d..60ac8fe9c 100644 --- a/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart +++ b/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; /// {@endtemplate} typedef ReactionIconBuilder = Widget Function( BuildContext context, + // ignore: avoid_positional_boolean_parameters bool isHighlighted, double iconSize, ); diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart index ef665e324..6b2a9b4fe 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart @@ -196,8 +196,7 @@ class StreamChannelListTile extends StatelessWidget { initialData: channelState.members, comparator: const ListEquality().equals, builder: (context, members) { - if (members.isEmpty || - !members.any((it) => it.user!.id == currentUser.id)) { + if (members.isEmpty) { return const Offstage(); } return unreadIndicatorBuilder?.call(context) ?? diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart index d2829ffd3..bf39b98d3 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart @@ -380,41 +380,3 @@ class StreamChannelListSeparator extends StatelessWidget { ); } } - -/// A widget that is used to display an error screen -/// when [StreamChannelListController] fails to load initial channels. -class StreamChannelListErrorWidget extends StatelessWidget { - /// Creates a new instance of [StreamChannelListErrorWidget] widget. - const StreamChannelListErrorWidget({ - super.key, - this.onPressed, - }); - - /// The callback to invoke when the user taps on the retry button. - final VoidCallback? onPressed; - - @override - Widget build(BuildContext context) => Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text.rich( - TextSpan( - children: [ - const WidgetSpan( - child: Padding( - padding: EdgeInsets.only(right: 2), - child: Icon(Icons.error_outline), - ), - ), - TextSpan(text: context.translations.loadingChannelsError), - ], - ), - style: Theme.of(context).textTheme.titleLarge, - ), - TextButton( - onPressed: onPressed, - child: Text(context.translations.retryLabel), - ), - ], - ); -} diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart index 1be657411..b93686d0b 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart @@ -202,9 +202,9 @@ class MediaThumbnailProvider extends ImageProvider { } @override - ImageStreamCompleter loadBuffer( + ImageStreamCompleter loadImage( MediaThumbnailProvider key, - DecoderBufferCallback decode, + ImageDecoderCallback decode, ) { return MultiFrameImageStreamCompleter( codec: _loadAsync(key, decode), @@ -221,7 +221,7 @@ class MediaThumbnailProvider extends ImageProvider { Future _loadAsync( MediaThumbnailProvider key, - DecoderBufferCallback decode, + ImageDecoderCallback decode, ) async { assert(key == this, '$key is not $this'); final bytes = await media.thumbnailDataWithSize( diff --git a/packages/stream_chat_flutter/lib/src/theme/color_theme.dart b/packages/stream_chat_flutter/lib/src/theme/color_theme.dart index 0babd24c4..1d98b5aaa 100644 --- a/packages/stream_chat_flutter/lib/src/theme/color_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/color_theme.dart @@ -66,7 +66,7 @@ class StreamColorTheme { this.appBg = const Color(0xff070A0D), this.barsBg = const Color(0xff101418), this.linkBg = const Color(0xff00193D), - this.accentPrimary = const Color(0xff005FFF), + this.accentPrimary = const Color(0xff337eff), this.accentError = const Color(0xffFF3742), this.accentInfo = const Color(0xff20E070), this.borderTop = const Effect( diff --git a/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart b/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart index ae60fbc61..6e43fef83 100644 --- a/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart @@ -153,6 +153,7 @@ class StreamMessageInputThemeData with Diagnosticable { sendButtonColor: sendButtonColor ?? this.sendButtonColor, actionButtonIdleColor: actionButtonIdleColor ?? this.actionButtonIdleColor, + linkHighlightColor: linkHighlightColor ?? this.linkHighlightColor, expandButtonColor: expandButtonColor ?? this.expandButtonColor, inputTextStyle: inputTextStyle ?? this.inputTextStyle, sendButtonIdleColor: sendButtonIdleColor ?? this.sendButtonIdleColor, diff --git a/packages/stream_chat_flutter/lib/src/theme/message_theme.dart b/packages/stream_chat_flutter/lib/src/theme/message_theme.dart index 6ae2cf884..6c35c5e34 100644 --- a/packages/stream_chat_flutter/lib/src/theme/message_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/message_theme.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/theme/avatar_theme.dart'; @@ -27,6 +29,7 @@ class StreamMessageThemeData with Diagnosticable { this.urlAttachmentTitleStyle, this.urlAttachmentTextStyle, this.urlAttachmentTitleMaxLine, + this.urlAttachmentTextMaxLine, }) : urlAttachmentBackgroundColor = urlAttachmentBackgroundColor ?? linkBackgroundColor; @@ -82,6 +85,9 @@ class StreamMessageThemeData with Diagnosticable { /// Max number of lines in Url link title. final int? urlAttachmentTitleMaxLine; + /// Max number of lines in Url link text. + final int? urlAttachmentTextMaxLine; + /// Copy with a theme StreamMessageThemeData copyWith({ TextStyle? messageTextStyle, @@ -102,6 +108,7 @@ class StreamMessageThemeData with Diagnosticable { TextStyle? urlAttachmentTitleStyle, TextStyle? urlAttachmentTextStyle, int? urlAttachmentTitleMaxLine, + int? urlAttachmentTextMaxLine, }) { return StreamMessageThemeData( messageTextStyle: messageTextStyle ?? this.messageTextStyle, @@ -128,6 +135,8 @@ class StreamMessageThemeData with Diagnosticable { urlAttachmentTextStyle ?? this.urlAttachmentTextStyle, urlAttachmentTitleMaxLine: urlAttachmentTitleMaxLine ?? this.urlAttachmentTitleMaxLine, + urlAttachmentTextMaxLine: + urlAttachmentTextMaxLine ?? this.urlAttachmentTextMaxLine, ); } @@ -178,6 +187,16 @@ class StreamMessageThemeData with Diagnosticable { b.urlAttachmentTitleStyle, t, ), + urlAttachmentTitleMaxLine: lerpDouble( + a.urlAttachmentTitleMaxLine, + b.urlAttachmentTitleMaxLine, + t, + )?.round(), + urlAttachmentTextMaxLine: lerpDouble( + a.urlAttachmentTextMaxLine, + b.urlAttachmentTextMaxLine, + t, + )?.round(), ); } @@ -206,6 +225,7 @@ class StreamMessageThemeData with Diagnosticable { urlAttachmentTitleStyle: other.urlAttachmentTitleStyle, urlAttachmentTextStyle: other.urlAttachmentTextStyle, urlAttachmentTitleMaxLine: other.urlAttachmentTitleMaxLine, + urlAttachmentTextMaxLine: other.urlAttachmentTextMaxLine, ); } @@ -229,7 +249,8 @@ class StreamMessageThemeData with Diagnosticable { urlAttachmentHostStyle == other.urlAttachmentHostStyle && urlAttachmentTitleStyle == other.urlAttachmentTitleStyle && urlAttachmentTextStyle == other.urlAttachmentTextStyle && - urlAttachmentTitleMaxLine == other.urlAttachmentTitleMaxLine; + urlAttachmentTitleMaxLine == other.urlAttachmentTitleMaxLine && + urlAttachmentTextMaxLine == other.urlAttachmentTextMaxLine; @override int get hashCode => @@ -248,7 +269,8 @@ class StreamMessageThemeData with Diagnosticable { urlAttachmentHostStyle.hashCode ^ urlAttachmentTitleStyle.hashCode ^ urlAttachmentTextStyle.hashCode ^ - urlAttachmentTitleMaxLine.hashCode; + urlAttachmentTitleMaxLine.hashCode ^ + urlAttachmentTextMaxLine.hashCode; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -284,6 +306,10 @@ class StreamMessageThemeData with Diagnosticable { ..add(DiagnosticsProperty( 'urlAttachmentTitleMaxLine', urlAttachmentTitleMaxLine, + )) + ..add(DiagnosticsProperty( + 'urlAttachmentTextMaxLine', + urlAttachmentTextMaxLine, )); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart index 04efaea6d..e7449343d 100644 --- a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart @@ -195,15 +195,12 @@ class StreamChatThemeData { width: 32, ), ), - messageLinksStyle: TextStyle( - color: accentColor, - ), + messageLinksStyle: TextStyle(color: accentColor), urlAttachmentBackgroundColor: colorTheme.linkBg, urlAttachmentHostStyle: textTheme.bodyBold.copyWith(color: accentColor), - urlAttachmentTitleStyle: - textTheme.body.copyWith(fontWeight: FontWeight.w700), - urlAttachmentTextStyle: - textTheme.body.copyWith(fontWeight: FontWeight.w400), + urlAttachmentTitleStyle: textTheme.footnoteBold, + urlAttachmentTextStyle: textTheme.footnote, + urlAttachmentTitleMaxLine: 1, ), otherMessageTheme: StreamMessageThemeData( reactionsBackgroundColor: colorTheme.borders, @@ -215,9 +212,7 @@ class StreamChatThemeData { messageAuthorStyle: textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), repliesStyle: textTheme.footnoteBold.copyWith(color: accentColor), - messageLinksStyle: TextStyle( - color: accentColor, - ), + messageLinksStyle: TextStyle(color: accentColor), messageBackgroundColor: colorTheme.barsBg, messageBorderColor: colorTheme.borders, avatarTheme: StreamAvatarThemeData( @@ -229,10 +224,9 @@ class StreamChatThemeData { ), urlAttachmentBackgroundColor: colorTheme.linkBg, urlAttachmentHostStyle: textTheme.bodyBold.copyWith(color: accentColor), - urlAttachmentTitleStyle: - textTheme.body.copyWith(fontWeight: FontWeight.w700), - urlAttachmentTextStyle: - textTheme.body.copyWith(fontWeight: FontWeight.w400), + urlAttachmentTitleStyle: textTheme.footnoteBold, + urlAttachmentTextStyle: textTheme.footnote, + urlAttachmentTitleMaxLine: 1, ), messageInputTheme: StreamMessageInputThemeData( borderRadius: BorderRadius.circular(20), diff --git a/packages/stream_chat_flutter/lib/src/utils/device_segmentation.dart b/packages/stream_chat_flutter/lib/src/utils/device_segmentation.dart index 3f815d0c5..bd65820c5 100644 --- a/packages/stream_chat_flutter/lib/src/utils/device_segmentation.dart +++ b/packages/stream_chat_flutter/lib/src/utils/device_segmentation.dart @@ -4,7 +4,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; bool get isWeb => CurrentPlatform.isWeb; /// Returns true if the app is running in a mobile device. -bool get isMobileDevice => true; +bool get isMobileDevice => CurrentPlatform.isIos || CurrentPlatform.isAndroid; /// Returns true if the app is running in a desktop device. bool get isDesktopDevice => @@ -22,7 +22,7 @@ bool get isDesktopVideoPlayerSupported => bool get isMobileDeviceOrWeb => isWeb || isMobileDevice; /// Returns true if the app is running in a desktop or web. -bool get isDesktopDeviceOrWeb => false; +bool get isDesktopDeviceOrWeb => isWeb || isDesktopDevice; /// Returns true if the app is running in a flutter test environment. bool get isTestEnvironment => CurrentPlatform.isFlutterTest; diff --git a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart index 6cc581bf0..17cab8209 100644 --- a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart +++ b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart @@ -90,6 +90,7 @@ typedef ChannelInfoCallback = void Function(Channel); /// {@template channelPreviewBuilder} /// Builder used to create a custom [ChannelPreview] for a [Channel] /// {@endtemplate} +@Deprecated('Use StreamChannelListViewIndexedWidgetBuilder instead') typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel); /// {@template viewInfoCallback} @@ -348,6 +349,7 @@ typedef KeyEventPredicate = bool Function(FocusNode, KeyEvent); /// {@template userItemBuilder} /// Builder used to create a custom [ListUserItem] from a [User] /// {@endtemplate} +// ignore: avoid_positional_boolean_parameters typedef UserItemBuilder = Widget Function(BuildContext, User, bool); /// The action to perform when the "scroll to bottom" button is pressed diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 3a0d5dd17..c360f1ec5 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.1.0 +version: 6.2.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -27,7 +27,7 @@ dependencies: flutter_portal: ^1.0.0 flutter_svg: ^2.0.4 http_parser: ^4.0.0 - image_gallery_saver: ^1.7.1 + image_gallery_saver: ^2.0.1 image_picker: ^0.8.2 jiffy: ^5.0.0 lottie: ^2.0.0 @@ -37,8 +37,8 @@ dependencies: photo_view: ^0.14.0 rxdart: ^0.27.0 share_plus: ^6.3.0 - shimmer: ^2.0.0 - stream_chat_flutter_core: ^6.1.0 + shimmer: ^3.0.0 + stream_chat_flutter_core: ^6.2.0 synchronized: ^3.0.0 thumblr: ^0.0.4 url_launcher: ^6.1.0 diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/horizontal_scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/horizontal_scrollable_positioned_list_test.dart index 73568c193..085abd220 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/horizontal_scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/horizontal_scrollable_positioned_list_test.dart @@ -22,9 +22,8 @@ void main() { EdgeInsets? padding, int initialScrollIndex = 0, }) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/positioned_list_test.dart index 96e10a4c1..887165a51 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/positioned_list_test.dart @@ -24,9 +24,8 @@ void main() { double anchor = 0, int itemCount = defaultItemCount, }) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_positioned_list_test.dart index 272e2459e..828f17e39 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_positioned_list_test.dart @@ -23,9 +23,8 @@ void main() { double anchor = 0, int itemCount = defaultItemCount, }) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_scrollable_positioned_list_test.dart index cb8d07e3a..3888ac91c 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_scrollable_positioned_list_test.dart @@ -21,9 +21,8 @@ void main() { EdgeInsets? padding, int initialIndex = 0, }) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/scrollable_positioned_list_test.dart index 55fe0c7ec..0b44f7d8c 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/scrollable_positioned_list_test.dart @@ -37,9 +37,8 @@ void main() { double? minCacheExtent, bool variableHeight = false, }) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( @@ -105,9 +104,8 @@ void main() { testWidgets('List positioned with 0 at top - use default values', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( @@ -1543,9 +1541,8 @@ void main() { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( @@ -1600,9 +1597,8 @@ void main() { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( @@ -1661,9 +1657,8 @@ void main() { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( @@ -1732,9 +1727,8 @@ void main() { testWidgets('Jump to 100 then set itemCount to 0', (WidgetTester tester) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); @@ -1781,9 +1775,8 @@ void main() { testWidgets('List positioned with 100 at top then set itemCount to 100', (WidgetTester tester) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); final itemCount = ValueNotifier(defaultItemCount); @@ -1823,9 +1816,8 @@ void main() { testWidgets('List positioned with 499 at bottom then set itemCount to 100', (WidgetTester tester) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); final itemCount = ValueNotifier(defaultItemCount); @@ -1943,9 +1935,8 @@ void main() { }); testWidgets('Rebuild with scroll controller', (WidgetTester tester) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); final key = ValueNotifier(const ValueKey('key')); final itemScrollController = ItemScrollController(); @@ -1986,9 +1977,8 @@ void main() { testWidgets('Double rebuild with scroll controller', (WidgetTester tester) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); final outerKey = ValueNotifier(const ValueKey('outerKey')); final innerKey = GlobalKey(); final listKey = ValueNotifier(const ValueKey(null)); @@ -2036,9 +2026,8 @@ void main() { }); testWidgets('Key change with scroll controller', (WidgetTester tester) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); final key = ValueNotifier(const ValueKey('key')); final itemScrollController = ItemScrollController(); @@ -2070,9 +2059,8 @@ void main() { }); testWidgets('Scroll after rebuild', (WidgetTester tester) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); final key = ValueNotifier(const ValueKey('key')); final itemScrollController = ItemScrollController(); @@ -2113,9 +2101,8 @@ void main() { testWidgets('Scroll after rebuild when resusing state', (WidgetTester tester) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); final containerKey = ValueNotifier(const ValueKey('key')); final scrollKey = GlobalKey(); final itemScrollController = ItemScrollController(); @@ -2158,9 +2145,8 @@ void main() { testWidgets('Scroll after changing scroll controller', (WidgetTester tester) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); final itemScrollController0 = ItemScrollController(); final itemScrollController1 = ItemScrollController(); @@ -2205,9 +2191,8 @@ void main() { testWidgets('Scroll after swapping scroll controllers', (WidgetTester tester) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); final itemScrollController0 = ItemScrollController(); final itemScrollController1 = ItemScrollController(); diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_positioned_list_test.dart index 47f75a5ea..f6dc2b5ce 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_positioned_list_test.dart @@ -25,9 +25,8 @@ void main() { double anchor = 0, int itemCount = defaultItemCount, }) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_scrollable_positioned_list_test.dart index c7439d5cc..c1d00df07 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_scrollable_positioned_list_test.dart @@ -33,9 +33,8 @@ void main() { bool addRepaintBoundaries = true, bool addAutomaticKeepAlives = true, }) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( @@ -100,9 +99,8 @@ void main() { testWidgets('List positioned with 0 at top - use default values', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( @@ -484,9 +482,8 @@ void main() { testWidgets('Empty list then update to single item list', (WidgetTester tester) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); @@ -526,9 +523,8 @@ void main() { testWidgets('ItemPositions: Empty list then update to 10 items list', (WidgetTester tester) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/seperated_horizontal_scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/seperated_horizontal_scrollable_positioned_list_test.dart index f92c9903a..c1276c100 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/seperated_horizontal_scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/seperated_horizontal_scrollable_positioned_list_test.dart @@ -24,9 +24,8 @@ void main() { EdgeInsets? padding, int initialScrollIndex = 0, }) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_position_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_position_list_test.dart index c5d94670a..8cf4303c4 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_position_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_position_list_test.dart @@ -25,9 +25,8 @@ void main() { int itemCount = defaultItemCount, bool reverse = false, }) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( @@ -421,9 +420,8 @@ void main() { testWidgets('test nested positioned list', (WidgetTester tester) async { const itemCount = 50; const key = Key('short_list'); - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_scrollable_position_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_scrollable_position_list_test.dart index cc3725aa9..c324d217b 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_scrollable_position_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_scrollable_position_list_test.dart @@ -21,9 +21,8 @@ void main() { EdgeInsets? padding, int initialIndex = 0, }) async { - tester.binding.window.devicePixelRatioTestValue = 1.0; - tester.binding.window.physicalSizeTestValue = - const Size(screenWidth, screenHeight); + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(screenWidth, screenHeight); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart b/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart index 5ae770ce9..eec8326d4 100644 --- a/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart +++ b/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart @@ -39,20 +39,27 @@ void main() { }); setUp(() { - methodChannel.setMockMethodCallHandler((MethodCall methodCall) async { - if (methodCall.method == 'listen') { - try { - await ServicesBinding.instance.defaultBinaryMessenger - .handlePlatformMessage( - methodChannel.name, - methodChannel.codec.encodeSuccessEnvelope('wifi'), - (_) {}, - ); - } catch (e) { - print(e); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + methodChannel, + (MethodCall methodCall) async { + if (methodCall.method == 'listen') { + try { + await TestDefaultBinaryMessengerBinding + .instance.defaultBinaryMessenger + .handlePlatformMessage( + methodChannel.name, + methodChannel.codec.encodeSuccessEnvelope('wifi'), + (_) {}, + ); + } catch (e) { + print(e); + } } - } - }); + + return null; + }, + ); }); testWidgets( @@ -118,7 +125,8 @@ void main() { ); tearDown(() { - methodChannel.setMockMethodCallHandler(null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); }); /*testGoldens( diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/edit_message_sheet_test.dart b/packages/stream_chat_flutter/test/src/bottom_sheets/edit_message_sheet_test.dart index 828c40d1f..48ccde03a 100644 --- a/packages/stream_chat_flutter/test/src/bottom_sheets/edit_message_sheet_test.dart +++ b/packages/stream_chat_flutter/test/src/bottom_sheets/edit_message_sheet_test.dart @@ -11,10 +11,13 @@ void main() { const methodChannel = MethodChannel('dev.fluttercommunity.plus/connectivity_status'); setUp(() { - methodChannel.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, + (MethodCall methodCall) async { if (methodCall.method == 'listen') { try { - await ServicesBinding.instance.defaultBinaryMessenger + await TestDefaultBinaryMessengerBinding + .instance.defaultBinaryMessenger .handlePlatformMessage( methodChannel.name, methodChannel.codec.encodeSuccessEnvelope('wifi'), @@ -24,6 +27,7 @@ void main() { print(e); } } + return null; }); }); @@ -87,7 +91,8 @@ void main() { ); tearDown(() { - methodChannel.setMockMethodCallHandler(null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); }); }); } diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/error_alert_sheet_test.dart b/packages/stream_chat_flutter/test/src/bottom_sheets/error_alert_sheet_test.dart index 4e6f8b733..cc4618178 100644 --- a/packages/stream_chat_flutter/test/src/bottom_sheets/error_alert_sheet_test.dart +++ b/packages/stream_chat_flutter/test/src/bottom_sheets/error_alert_sheet_test.dart @@ -11,10 +11,13 @@ void main() { const methodChannel = MethodChannel('dev.fluttercommunity.plus/connectivity_status'); setUp(() { - methodChannel.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, + (MethodCall methodCall) async { if (methodCall.method == 'listen') { try { - await ServicesBinding.instance.defaultBinaryMessenger + await TestDefaultBinaryMessengerBinding + .instance.defaultBinaryMessenger .handlePlatformMessage( methodChannel.name, methodChannel.codec.encodeSuccessEnvelope('wifi'), @@ -24,6 +27,7 @@ void main() { print(e); } } + return null; }); }); @@ -93,7 +97,8 @@ void main() { ); tearDown(() { - methodChannel.setMockMethodCallHandler(null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); }); }); } diff --git a/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart b/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart index e1c1f29ad..c8b5cc719 100644 --- a/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart +++ b/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart @@ -40,10 +40,12 @@ void main() { }); setUp(() { - methodChannel.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, (MethodCall methodCall) async { if (methodCall.method == 'listen') { try { - await ServicesBinding.instance.defaultBinaryMessenger + await TestDefaultBinaryMessengerBinding + .instance.defaultBinaryMessenger .handlePlatformMessage( methodChannel.name, methodChannel.codec.encodeSuccessEnvelope('wifi'), @@ -53,6 +55,7 @@ void main() { print(e); } } + return null; }); }); @@ -106,6 +109,7 @@ void main() { }); tearDown(() { - methodChannel.setMockMethodCallHandler(null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); }); } diff --git a/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart b/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart index 639737705..bc8a3a7a5 100644 --- a/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart +++ b/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart @@ -40,10 +40,12 @@ void main() { }); setUp(() { - methodChannel.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, (MethodCall methodCall) async { if (methodCall.method == 'listen') { try { - await ServicesBinding.instance.defaultBinaryMessenger + await TestDefaultBinaryMessengerBinding + .instance.defaultBinaryMessenger .handlePlatformMessage( methodChannel.name, methodChannel.codec.encodeSuccessEnvelope('wifi'), @@ -53,6 +55,7 @@ void main() { print(e); } } + return null; }); }); @@ -110,6 +113,7 @@ void main() { }); tearDown(() { - methodChannel.setMockMethodCallHandler(null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart index 3c62baeaa..f267d388f 100644 --- a/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart @@ -69,11 +69,11 @@ final _messageInputThemeControlMidLerp = StreamMessageInputThemeData( borderRadius: BorderRadius.circular(20), sendAnimationDuration: const Duration(milliseconds: 300), inputBackgroundColor: const Color(0xff87898b), - actionButtonColor: const Color(0xff005fff), + actionButtonColor: const Color(0xff196eff), actionButtonIdleColor: const Color(0xff7a7a7a), - sendButtonColor: const Color(0xff005fff), + sendButtonColor: const Color(0xff196eff), sendButtonIdleColor: const Color(0xff848585), - expandButtonColor: const Color(0xff005fff), + expandButtonColor: const Color(0xff196eff), inputTextStyle: const TextStyle( color: Color(0xff7f7f7f), fontSize: 14, diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 3359bf8c5..c7869125c 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,9 +1,18 @@ +## 6.2.0 + +- Fixed `StreamMessageInputController.textPatternStyle` not matching case-insensitive patterns. +- Updated `connectivity_plus` dependency to `^4.0.0` +- Fixed `StreamChannel` shows black screen while loading in some cases. +- Updated `stream_chat` dependency to [`6.2.0`](https://pub.dev/packages/stream_chat/changelog). + ## 6.1.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). - [[#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` + instead of material indicator. ## 6.0.0 diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 042c2eca4..40dd142e1 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -41,7 +41,7 @@ bool Function(Message) defaultMessageFilter(String currentUserId) => /// }, /// loadingBuilder: (context) { /// return Center( -/// child: CircularProgressIndicator(), +/// child: CircularProgressIndicator.adaptive(), /// ); /// }, /// messageListBuilder: (context, list) { diff --git a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart index ff5c03a48..ae730c06b 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart @@ -41,7 +41,10 @@ class MessageTextFieldController extends TextEditingController { } return TextSpan(text: text, style: style).splitMapJoin( - RegExp(pattern.keys.map((it) => it.pattern).join('|')), + RegExp( + pattern.keys.map((it) => it.pattern).join('|'), + caseSensitive: false, + ), onMatch: (match) { final text = match[0]!; final key = pattern.keys.firstWhere((it) => it.hasMatch(text)); 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 bbfdea5bc..9cfc34640 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -251,14 +251,14 @@ class StreamChannelState extends State { channel.state!.truncate(); if (messageId == null) { - await channel.query( + final state = await channel.query( messagesPagination: PaginationParams( limit: limit, ), preferOffline: preferOffline, ); channel.state!.isUpToDate = true; - return null; + return state; } return channel.query( @@ -445,13 +445,13 @@ class StreamChannelState extends State { final dataLoaded = snapshot.data?.every((it) => it) == true; if (widget.showLoading && !dataLoaded) { return const Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator.adaptive(), ); } return widget.child; }, ); - if (initialMessageId != null) { + if (_futures.length > 1) { child = Material(child: child); } return child; diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 031099b86..3f602978f 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.1.0 +version: 6.2.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -11,13 +11,13 @@ environment: dependencies: collection: ^1.17.0 - connectivity_plus: ^3.0.2 + connectivity_plus: ^4.0.0 flutter: sdk: flutter freezed_annotation: ^2.0.3 meta: ^1.8.0 rxdart: ^0.27.0 - stream_chat: ^6.1.0 + stream_chat: ^6.2.0 dev_dependencies: build_runner: ^2.3.3 diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md index b435fd724..ab1abd10b 100644 --- a/packages/stream_chat_localizations/CHANGELOG.md +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.2.0 + +* Updated `stream_chat_flutter` dependency to [`6.2.0`](https://pub.dev/packages/stream_chat_flutter/changelog). + ## 5.1.0 * Updated `dart` sdk environment range to support `3.0.0`. diff --git a/packages/stream_chat_localizations/example/lib/add_new_lang.dart b/packages/stream_chat_localizations/example/lib/add_new_lang.dart index 3f3b5bdbb..bab8101b8 100644 --- a/packages/stream_chat_localizations/example/lib/add_new_lang.dart +++ b/packages/stream_chat_localizations/example/lib/add_new_lang.dart @@ -577,10 +577,10 @@ class ChannelPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: const StreamChannelHeader(), + return const Scaffold( + appBar: StreamChannelHeader(), body: Column( - children: const [ + children: [ Expanded( child: StreamMessageListView(), ), diff --git a/packages/stream_chat_localizations/example/lib/main.dart b/packages/stream_chat_localizations/example/lib/main.dart index ae8aa2b92..22abb65fa 100644 --- a/packages/stream_chat_localizations/example/lib/main.dart +++ b/packages/stream_chat_localizations/example/lib/main.dart @@ -106,10 +106,10 @@ class ChannelPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: const StreamChannelHeader(), + return const Scaffold( + appBar: StreamChannelHeader(), body: Column( - children: const [ + children: [ Expanded( child: StreamMessageListView(), ), diff --git a/packages/stream_chat_localizations/example/lib/override_lang.dart b/packages/stream_chat_localizations/example/lib/override_lang.dart index c37094ccc..1dc05f910 100644 --- a/packages/stream_chat_localizations/example/lib/override_lang.dart +++ b/packages/stream_chat_localizations/example/lib/override_lang.dart @@ -133,10 +133,10 @@ class ChannelPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: const StreamChannelHeader(), + return const Scaffold( + appBar: StreamChannelHeader(), body: Column( - children: const [ + children: [ Expanded( child: StreamMessageListView(), ), diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index e55f63010..dbcf7ea84 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.1.0 +version: 5.2.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.1.0 + stream_chat_flutter: ^6.2.0 dev_dependencies: dart_code_metrics: ^5.7.2 diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index 3e56f2ec3..40ee94810 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,3 +1,10 @@ +## 6.2.0 + +- Added support for `StreamChatPersistenceClient.isConnected` for checking if the client is connected to the database. +- [[#1422]](https://github.com/GetStream/stream-chat-flutter/issues/1422) Removed default values + from `UserEntity` `createdAt` and `updatedAt` fields. +- Updated `stream_chat` dependency to [`6.2.0`](https://pub.dev/packages/stream_chat/changelog). + ## 6.1.0 - Updated `dart` sdk environment range to support `3.0.0`. diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart index acac4993f..e7a74f584 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart @@ -81,13 +81,13 @@ class ChannelQueryDao extends DatabaseAccessor final cachedChannelCids = await getCachedChannelCids(filter); final query = select(channels)..where((c) => c.cid.isIn(cachedChannelCids)); - final cachedChannels = await (query.join([ + final cachedChannels = await query.join([ leftOuterJoin(users, channels.createdById.equalsExp(users.id)), ]).map((row) { final createdByEntity = row.readTableOrNull(users); final channelEntity = row.readTable(channels); return channelEntity.toChannelModel(createdBy: createdByEntity?.toUser()); - })).get(); + }).get(); var chainedComparator = (ChannelModel a, ChannelModel b) { final dateA = a.lastMessageAt ?? a.createdAt; diff --git a/packages/stream_chat_persistence/lib/src/dao/member_dao.dart b/packages/stream_chat_persistence/lib/src/dao/member_dao.dart index ccf843fb4..fb345d3bf 100644 --- a/packages/stream_chat_persistence/lib/src/dao/member_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/member_dao.dart @@ -1,10 +1,8 @@ import 'package:drift/drift.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; - import 'package:stream_chat_persistence/src/entity/members.dart'; import 'package:stream_chat_persistence/src/entity/users.dart'; - import 'package:stream_chat_persistence/src/mapper/mapper.dart'; part 'member_dao.g.dart'; @@ -39,9 +37,9 @@ class MemberDao extends DatabaseAccessor ) { final entities = channelWithMembers.entries .map((entry) => - (entry.value?.map( + entry.value?.map( (member) => member.toEntity(cid: entry.key), - )) ?? + ) ?? []) .expand((it) => it) .toList(growable: false); diff --git a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart index e186fbb3c..131a5768e 100644 --- a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart +++ b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart @@ -49,7 +49,7 @@ class DriftChatDatabase extends _$DriftChatDatabase { // you should bump this number whenever you change or add a table definition. @override - int get schemaVersion => 10; + int get schemaVersion => 11; @override MigrationStrategy get migration => MigrationStrategy( diff --git a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart index 6f8b0e318..0dcd0107f 100644 --- a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart @@ -3651,18 +3651,14 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); + 'created_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); + 'updated_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); static const VerificationMeta _lastActiveMeta = const VerificationMeta('lastActive'); @override @@ -3773,9 +3769,9 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { language: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}language']), createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at']), updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at']), lastActive: attachedDatabase.typeMapping .read(DriftSqlType.dateTime, data['${effectivePrefix}last_active']), online: attachedDatabase.typeMapping @@ -3808,10 +3804,10 @@ class UserEntity extends DataClass implements Insertable { final String? language; /// Date of user creation - final DateTime createdAt; + final DateTime? createdAt; /// Date of last user update - final DateTime updatedAt; + final DateTime? updatedAt; /// Date of last user connection final DateTime? lastActive; @@ -3828,8 +3824,8 @@ class UserEntity extends DataClass implements Insertable { {required this.id, this.role, this.language, - required this.createdAt, - required this.updatedAt, + this.createdAt, + this.updatedAt, this.lastActive, required this.online, required this.banned, @@ -3844,8 +3840,12 @@ class UserEntity extends DataClass implements Insertable { if (!nullToAbsent || language != null) { map['language'] = Variable(language); } - map['created_at'] = Variable(createdAt); - map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || updatedAt != null) { + map['updated_at'] = Variable(updatedAt); + } if (!nullToAbsent || lastActive != null) { map['last_active'] = Variable(lastActive); } @@ -3865,8 +3865,8 @@ class UserEntity extends DataClass implements Insertable { id: serializer.fromJson(json['id']), role: serializer.fromJson(json['role']), language: serializer.fromJson(json['language']), - createdAt: serializer.fromJson(json['createdAt']), - updatedAt: serializer.fromJson(json['updatedAt']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), lastActive: serializer.fromJson(json['lastActive']), online: serializer.fromJson(json['online']), banned: serializer.fromJson(json['banned']), @@ -3880,8 +3880,8 @@ class UserEntity extends DataClass implements Insertable { 'id': serializer.toJson(id), 'role': serializer.toJson(role), 'language': serializer.toJson(language), - 'createdAt': serializer.toJson(createdAt), - 'updatedAt': serializer.toJson(updatedAt), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), 'lastActive': serializer.toJson(lastActive), 'online': serializer.toJson(online), 'banned': serializer.toJson(banned), @@ -3893,8 +3893,8 @@ class UserEntity extends DataClass implements Insertable { {String? id, Value role = const Value.absent(), Value language = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), Value lastActive = const Value.absent(), bool? online, bool? banned, @@ -3903,8 +3903,8 @@ class UserEntity extends DataClass implements Insertable { id: id ?? this.id, role: role.present ? role.value : this.role, language: language.present ? language.value : this.language, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, lastActive: lastActive.present ? lastActive.value : this.lastActive, online: online ?? this.online, banned: banned ?? this.banned, @@ -3948,8 +3948,8 @@ class UsersCompanion extends UpdateCompanion { final Value id; final Value role; final Value language; - final Value createdAt; - final Value updatedAt; + final Value createdAt; + final Value updatedAt; final Value lastActive; final Value online; final Value banned; @@ -4010,8 +4010,8 @@ class UsersCompanion extends UpdateCompanion { {Value? id, Value? role, Value? language, - Value? createdAt, - Value? updatedAt, + Value? createdAt, + Value? updatedAt, Value? lastActive, Value? online, Value? banned, diff --git a/packages/stream_chat_persistence/lib/src/entity/users.dart b/packages/stream_chat_persistence/lib/src/entity/users.dart index a5db56cc0..56b22e70a 100644 --- a/packages/stream_chat_persistence/lib/src/entity/users.dart +++ b/packages/stream_chat_persistence/lib/src/entity/users.dart @@ -15,10 +15,10 @@ class Users extends Table { TextColumn get language => text().nullable()(); /// Date of user creation - DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get createdAt => dateTime().nullable()(); /// Date of last user update - DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().nullable()(); /// Date of last user connection DateTimeColumn get lastActive => dateTime().nullable()(); diff --git a/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart b/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart index 3d5a5bdbc..374bfa599 100644 --- a/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart +++ b/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart @@ -58,7 +58,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { bool get _debugIsConnected { assert(() { - if (db == null) { + if (!isConnected) { throw StateError(''' $runtimeType hasn't been connected yet or used after `disconnect` was called. Consider calling `connect` to create a connection. @@ -79,6 +79,9 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { webUseIndexedDbIfSupported: _webUseIndexedDbIfSupported, ); + @override + bool get isConnected => db != null; + @override Future connect( String userId, { @@ -405,7 +408,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { @override Future disconnect({bool flush = false}) async { _logger.info('disconnect'); - if (db != null) { + if (isConnected) { _logger.info('Disconnecting'); if (flush) { _logger.info('Flushing'); diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 341faf1fe..9c5bd9caf 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.1.0 +version: 6.2.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.2 path_provider: ^2.0.1 sqlite3_flutter_libs: ^0.5.0 - stream_chat: ^6.1.0 + stream_chat: ^6.2.0 dev_dependencies: build_runner: ^2.3.3 diff --git a/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart index 089057190..91efdb6ea 100644 --- a/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart @@ -132,7 +132,7 @@ void main() { // Should match lastMessageAt date expect( updatedChannel.lastMessageAt, - isSameDateAs(insertedChannel.lastMessageAt!), + isSameDateAs(insertedChannel.lastMessageAt), ); } }); @@ -177,7 +177,7 @@ void main() { // Should match lastMessageAt date expect( updatedChannel.lastMessageAt, - isSameDateAs(insertedChannel.lastMessageAt!), + isSameDateAs(insertedChannel.lastMessageAt), ); } }); @@ -220,7 +220,7 @@ void main() { // Should match lastMessageAt date expect( updatedChannel.lastMessageAt, - isSameDateAs(insertedChannel.lastMessageAt!), + isSameDateAs(insertedChannel.lastMessageAt), ); } }); diff --git a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart index 1d4d151b5..d96fcd02e 100644 --- a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart @@ -66,7 +66,7 @@ void main() { expect(fetchedMember.updatedAt, isSameDateAs(member.updatedAt)); expect( fetchedMember.inviteAcceptedAt, - isSameDateAs(member.inviteAcceptedAt!), + isSameDateAs(member.inviteAcceptedAt), ); } }); @@ -93,7 +93,7 @@ void main() { expect(fetchedMember.updatedAt, isSameDateAs(member.updatedAt)); expect( fetchedMember.inviteAcceptedAt, - isSameDateAs(member.inviteAcceptedAt!), + isSameDateAs(member.inviteAcceptedAt), ); } diff --git a/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart index b8bcc2ed2..73dd29981 100644 --- a/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart @@ -37,8 +37,8 @@ void main() { expect(channelModel.updatedAt, isSameDateAs(entity.updatedAt)); expect(channelModel.memberCount, entity.memberCount); expect(channelModel.cid, entity.cid); - expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt!)); - expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt!)); + expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt)); + expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt)); expect(channelModel.extraData, entity.extraData); expect(channelModel.createdBy!.id, entity.createdById); }); @@ -77,8 +77,8 @@ void main() { expect(channelModel.updatedAt, isSameDateAs(entity.updatedAt)); expect(channelModel.memberCount, entity.memberCount); expect(channelModel.cid, entity.cid); - expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt!)); - expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt!)); + expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt)); + expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt)); expect(channelModel.extraData, entity.extraData); expect(channelModel.createdBy!.id, entity.createdById); }); @@ -115,8 +115,8 @@ void main() { expect(channelEntity.updatedAt, isSameDateAs(model.updatedAt)); expect(channelEntity.memberCount, model.memberCount); expect(channelEntity.cid, model.cid); - expect(channelEntity.lastMessageAt, isSameDateAs(model.lastMessageAt!)); - expect(channelEntity.deletedAt, isSameDateAs(model.deletedAt!)); + expect(channelEntity.lastMessageAt, isSameDateAs(model.lastMessageAt)); + expect(channelEntity.deletedAt, isSameDateAs(model.deletedAt)); expect(channelEntity.extraData, model.extraData); expect(channelEntity.createdById, model.createdBy!.id); }); diff --git a/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart index 206651bd0..001872aa1 100644 --- a/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart @@ -29,8 +29,8 @@ void main() { expect(member.createdAt, isSameDateAs(entity.createdAt)); expect(member.updatedAt, isSameDateAs(entity.updatedAt)); expect(member.channelRole, entity.channelRole); - expect(member.inviteAcceptedAt, isSameDateAs(entity.inviteAcceptedAt!)); - expect(member.inviteRejectedAt, isSameDateAs(entity.inviteRejectedAt!)); + expect(member.inviteAcceptedAt, isSameDateAs(entity.inviteAcceptedAt)); + expect(member.inviteRejectedAt, isSameDateAs(entity.inviteRejectedAt)); expect(member.invited, entity.invited); expect(member.banned, entity.banned); expect(member.shadowBanned, entity.shadowBanned); @@ -59,8 +59,8 @@ void main() { expect(entity.createdAt, isSameDateAs(member.createdAt)); expect(entity.updatedAt, isSameDateAs(member.updatedAt)); expect(entity.channelRole, member.channelRole); - expect(entity.inviteAcceptedAt, isSameDateAs(member.inviteAcceptedAt!)); - expect(entity.inviteRejectedAt, isSameDateAs(member.inviteRejectedAt!)); + expect(entity.inviteAcceptedAt, isSameDateAs(member.inviteAcceptedAt)); + expect(entity.inviteRejectedAt, isSameDateAs(member.inviteRejectedAt)); expect(entity.invited, member.invited); expect(entity.banned, member.banned); expect(entity.shadowBanned, member.shadowBanned); diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart index 8a06f02d5..ccb9108eb 100644 --- a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -96,11 +96,11 @@ void main() { expect(message.updatedAt, isSameDateAs(entity.updatedAt)); expect(message.extraData, entity.extraData); expect(message.user!.id, entity.userId); - expect(message.deletedAt, isSameDateAs(entity.deletedAt!)); + expect(message.deletedAt, isSameDateAs(entity.deletedAt)); expect(message.text, entity.messageText); expect(message.pinned, entity.pinned); - expect(message.pinExpires, isSameDateAs(entity.pinExpires!)); - expect(message.pinnedAt, isSameDateAs(entity.pinnedAt!)); + expect(message.pinExpires, isSameDateAs(entity.pinExpires)); + expect(message.pinnedAt, isSameDateAs(entity.pinnedAt)); expect(message.pinnedBy!.id, entity.pinnedByUserId); expect(message.reactionCounts, entity.reactionCounts); expect(message.reactionScores, entity.reactionScores); @@ -191,11 +191,11 @@ void main() { expect(entity.updatedAt, isSameDateAs(message.updatedAt)); expect(entity.extraData, message.extraData); expect(entity.userId, message.user!.id); - expect(entity.deletedAt, isSameDateAs(message.deletedAt!)); + expect(entity.deletedAt, isSameDateAs(message.deletedAt)); expect(entity.messageText, message.text); expect(entity.pinned, message.pinned); - expect(entity.pinExpires, isSameDateAs(message.pinExpires!)); - expect(entity.pinnedAt, isSameDateAs(message.pinnedAt!)); + expect(entity.pinExpires, isSameDateAs(message.pinExpires)); + expect(entity.pinnedAt, isSameDateAs(message.pinnedAt)); expect(entity.pinnedByUserId, message.pinnedBy!.id); expect(entity.reactionCounts, message.reactionCounts); expect(entity.reactionScores, message.reactionScores); diff --git a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart index 9c4d99a20..fd3e075c4 100644 --- a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart @@ -89,11 +89,11 @@ void main() { expect(message.updatedAt, isSameDateAs(entity.updatedAt)); expect(message.extraData, entity.extraData); expect(message.user!.id, entity.userId); - expect(message.deletedAt, isSameDateAs(entity.deletedAt!)); + expect(message.deletedAt, isSameDateAs(entity.deletedAt)); expect(message.text, entity.messageText); expect(message.pinned, entity.pinned); - expect(message.pinExpires, isSameDateAs(entity.pinExpires!)); - expect(message.pinnedAt, isSameDateAs(entity.pinnedAt!)); + expect(message.pinExpires, isSameDateAs(entity.pinExpires)); + expect(message.pinnedAt, isSameDateAs(entity.pinnedAt)); expect(message.pinnedBy!.id, entity.pinnedByUserId); expect(message.reactionCounts, entity.reactionCounts); expect(message.reactionScores, entity.reactionScores); @@ -179,11 +179,11 @@ void main() { expect(entity.updatedAt, isSameDateAs(message.updatedAt)); expect(entity.extraData, message.extraData); expect(entity.userId, message.user!.id); - expect(entity.deletedAt, isSameDateAs(message.deletedAt!)); + expect(entity.deletedAt, isSameDateAs(message.deletedAt)); expect(entity.messageText, message.text); expect(entity.pinned, message.pinned); - expect(entity.pinExpires, isSameDateAs(message.pinExpires!)); - expect(entity.pinnedAt, isSameDateAs(message.pinnedAt!)); + expect(entity.pinExpires, isSameDateAs(message.pinExpires)); + expect(entity.pinnedAt, isSameDateAs(message.pinnedAt)); expect(entity.pinnedByUserId, message.pinnedBy!.id); expect(entity.reactionCounts, message.reactionCounts); expect(entity.reactionScores, message.reactionScores); diff --git a/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart index 8f9340a9a..b41e9d597 100644 --- a/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart @@ -27,7 +27,7 @@ void main() { expect(user.language, entity.language); expect(user.createdAt, isSameDateAs(entity.createdAt)); expect(user.updatedAt, isSameDateAs(entity.updatedAt)); - expect(user.lastActive, isSameDateAs(entity.lastActive!)); + expect(user.lastActive, isSameDateAs(entity.lastActive)); expect(user.online, entity.online); expect(user.banned, entity.banned); expect(user.extraData, entity.extraData); @@ -52,7 +52,7 @@ void main() { expect(entity.language, user.language); expect(entity.createdAt, isSameDateAs(user.createdAt)); expect(entity.updatedAt, isSameDateAs(user.updatedAt)); - expect(entity.lastActive, isSameDateAs(user.lastActive!)); + expect(entity.lastActive, isSameDateAs(user.lastActive)); expect(entity.online, user.online); expect(entity.banned, user.banned); expect(entity.extraData, user.extraData); diff --git a/packages/stream_chat_persistence/test/src/utils/date_matcher.dart b/packages/stream_chat_persistence/test/src/utils/date_matcher.dart index 5f55f46a5..d19d25f2a 100644 --- a/packages/stream_chat_persistence/test/src/utils/date_matcher.dart +++ b/packages/stream_chat_persistence/test/src/utils/date_matcher.dart @@ -1,21 +1,22 @@ import 'package:flutter_test/flutter_test.dart'; -Matcher isSameDateAs(DateTime targetDate) => +Matcher isSameDateAs(DateTime? targetDate) => _IsSameDateAs(targetDate: targetDate); class _IsSameDateAs extends Matcher { const _IsSameDateAs({required this.targetDate}); - final DateTime targetDate; + final DateTime? targetDate; @override - bool matches(covariant DateTime date, Map matchState) => - date.year == targetDate.year && - date.month == targetDate.month && - date.day == targetDate.day && - date.hour == targetDate.hour && - date.minute == targetDate.minute && - date.second == targetDate.second; + bool matches(covariant DateTime date, Map matchState) { + return date.year == targetDate?.year && + date.month == targetDate?.month && + date.day == targetDate?.day && + date.hour == targetDate?.hour && + date.minute == targetDate?.minute && + date.second == targetDate?.second; + } @override Description describe(Description description) => diff --git a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart index c99668fd9..6791e07cc 100644 --- a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart +++ b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart @@ -16,9 +16,9 @@ void main() { const userId = 'testUserId'; test('successfully connects with the Database', () async { final client = StreamChatPersistenceClient(logLevel: Level.ALL); - expect(client.db, isNull); + expect(client.isConnected, false); await client.connect(userId, databaseProvider: testDatabaseProvider); - expect(client.db, isNotNull); + expect(client.isConnected, true); expect(client.db, isA()); expect(client.db!.userId, userId); @@ -29,10 +29,9 @@ void main() { test('throws if already connected', () async { final client = StreamChatPersistenceClient(logLevel: Level.ALL); - expect(client.db, isNull); + expect(client.isConnected, false); await client.connect(userId, databaseProvider: testDatabaseProvider); - expect(client.db, isNotNull); - expect(client.db, isNotNull); + expect(client.isConnected, true); expect(client.db, isA()); expect(client.db!.userId, userId); expect( @@ -50,9 +49,9 @@ void main() { const userId = 'testUserId'; final client = StreamChatPersistenceClient(logLevel: Level.ALL); await client.connect(userId, databaseProvider: testDatabaseProvider); - expect(client.db, isNotNull); + expect(client.isConnected, true); await client.disconnect(flush: true); - expect(client.db, isNull); + expect(client.isConnected, false); }); test('client function throws stateError if db is not yet connected', () {