From 73460da38e0cde1e1b450d03c54784fba8d15a76 Mon Sep 17 00:00:00 2001 From: geweald Date: Tue, 6 Dec 2022 13:38:12 +0100 Subject: [PATCH 01/20] feat(ui): sending indicator in BottomRow --- packages/stream_chat_flutter/CHANGELOG.md | 1 + .../lib/src/message_widget/bottom_row.dart | 71 +++++++++++++++++-- .../src/message_widget/message_widget.dart | 5 +- .../message_widget_content.dart | 53 +++++++------- 4 files changed, 96 insertions(+), 34 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 6c3980d2..2df0abad 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -5,6 +5,7 @@ - Updated `lottie` dependency to `^2.0.0` - Updated `desktop_drop` dependency to `^0.4.0` - Updated `connectivity_plus` dependency to `^3.0.2` +- Added third parameter (default `BottomRow` widget with `copyWith` method available) to `bottomRowBuilder` of `StreamMessageWidget` to allow easier customization. 🐞 Fixed - [[#1379]](https://github.com/GetStream/stream-chat-flutter/issues/1379) Fixed "Issues with photo attachments on web", where the cached image attachment would not render while uploading. 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 a77b6de2..5b1b1466 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 @@ -32,6 +32,7 @@ class BottomRow extends StatelessWidget { this.deletedBottomRowBuilder, this.onThreadTap, this.usernameBuilder, + this.sendingIndicatorBuilder, }); /// {@macro messageIsDeleted} @@ -88,6 +89,61 @@ class BottomRow extends StatelessWidget { /// {@macro usernameBuilder} final Widget Function(BuildContext, Message)? usernameBuilder; + /// {@macro sendingIndicatorBuilder} + final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; + + /// {@template copyWith} + /// Creates a copy of [BottomRow] with specified attributes + /// overridden. + /// {@endtemplate} + BottomRow copyWith({ + Key? key, + bool? isDeleted, + Message? message, + bool? showThreadReplyIndicator, + bool? showInChannel, + bool? showTimeStamp, + bool? showUsername, + bool? reverse, + bool? showSendingIndicator, + bool? hasUrlAttachments, + bool? isGiphy, + bool? isOnlyEmoji, + StreamMessageThemeData? messageTheme, + StreamChatThemeData? streamChatTheme, + bool? hasNonUrlAttachments, + StreamChatState? streamChat, + Widget Function(BuildContext, Message)? deletedBottomRowBuilder, + void Function(Message)? onThreadTap, + Widget Function(BuildContext, Message)? usernameBuilder, + Widget Function(BuildContext, Message)? sendingIndicatorBuilder, + }) => + BottomRow( + key: key ?? this.key, + isDeleted: isDeleted ?? this.isDeleted, + message: message ?? this.message, + showThreadReplyIndicator: + showThreadReplyIndicator ?? this.showThreadReplyIndicator, + showInChannel: showInChannel ?? this.showInChannel, + showTimeStamp: showTimeStamp ?? this.showTimeStamp, + showUsername: showUsername ?? this.showUsername, + reverse: reverse ?? this.reverse, + showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator, + hasUrlAttachments: hasUrlAttachments ?? this.hasUrlAttachments, + isGiphy: isGiphy ?? this.isGiphy, + isOnlyEmoji: isOnlyEmoji ?? this.isOnlyEmoji, + messageTheme: messageTheme ?? this.messageTheme, + streamChatTheme: streamChatTheme ?? this.streamChatTheme, + hasNonUrlAttachments: hasNonUrlAttachments ?? this.hasNonUrlAttachments, + streamChat: streamChat ?? this.streamChat, + deletedBottomRowBuilder: + deletedBottomRowBuilder ?? this.deletedBottomRowBuilder, + onThreadTap: onThreadTap ?? this.onThreadTap, + usernameBuilder: usernameBuilder ?? this.usernameBuilder, + sendingIndicatorBuilder: + sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, + ); + @override Widget build(BuildContext context) { if (isDeleted) { @@ -147,13 +203,14 @@ class BottomRow extends StatelessWidget { ), if (showSendingIndicator) WidgetSpan( - child: SendingIndicatorWrapper( - messageTheme: messageTheme, - message: message, - hasNonUrlAttachments: hasNonUrlAttachments, - streamChat: streamChat, - streamChatTheme: streamChatTheme, - ), + child: sendingIndicatorBuilder?.call(context, message) ?? + SendingIndicatorWrapper( + messageTheme: messageTheme, + message: message, + hasNonUrlAttachments: hasNonUrlAttachments, + streamChat: streamChat, + streamChatTheme: streamChatTheme, + ), ), ]); 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 d2675a0a..37869edc 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 @@ -8,6 +8,7 @@ import 'package:stream_chat_flutter/src/context_menu_items/context_menu_reaction import 'package:stream_chat_flutter/src/context_menu_items/stream_chat_context_menu_item.dart'; import 'package:stream_chat_flutter/src/dialogs/dialogs.dart'; import 'package:stream_chat_flutter/src/message_actions_modal/message_actions_modal.dart'; +import 'package:stream_chat_flutter/src/message_widget/bottom_row.dart'; import 'package:stream_chat_flutter/src/message_widget/message_widget_content.dart'; import 'package:stream_chat_flutter/src/message_widget/reactions/message_reactions_modal.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -306,7 +307,7 @@ class StreamMessageWidget extends StatefulWidget { /// {@template bottomRowBuilder} /// Widget builder for building a bottom row below the message /// {@endtemplate} - final Widget Function(BuildContext, Message)? bottomRowBuilder; + final Widget Function(BuildContext, Message, BottomRow)? bottomRowBuilder; /// {@template deletedBottomRowBuilder} /// Widget builder for building a bottom row below a deleted message @@ -538,7 +539,7 @@ class StreamMessageWidget extends StatefulWidget { Widget Function(BuildContext, Message)? editMessageInputBuilder, Widget Function(BuildContext, Message)? textBuilder, Widget Function(BuildContext, Message)? usernameBuilder, - Widget Function(BuildContext, Message)? bottomRowBuilder, + Widget Function(BuildContext, Message, BottomRow)? bottomRowBuilder, Widget Function(BuildContext, Message)? deletedBottomRowBuilder, void Function(BuildContext, Message)? onMessageActions, Message? message, diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart index d209fbee..5cf9f3e0 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart @@ -152,7 +152,7 @@ class MessageWidgetContent extends StatelessWidget { final double bottomRowPadding; /// {@macro bottomRowBuilder} - final Widget Function(BuildContext, Message)? bottomRowBuilder; + final Widget Function(BuildContext, Message, BottomRow)? bottomRowBuilder; /// {@macro showInChannelIndicator} final bool showInChannel; @@ -207,30 +207,7 @@ class MessageWidgetContent extends StatelessWidget { right: reverse ? bottomRowPadding : 0, bottom: isPinned && showPinHighlight ? 6.0 : 0.0, ), - child: bottomRowBuilder?.call( - context, - message, - ) ?? - BottomRow( - message: message, - reverse: reverse, - messageTheme: messageTheme, - hasUrlAttachments: hasUrlAttachments, - isOnlyEmoji: isOnlyEmoji, - isDeleted: message.isDeleted, - isGiphy: isGiphy, - showInChannel: showInChannel, - showSendingIndicator: showSendingIndicator, - showThreadReplyIndicator: showThreadReplyIndicator, - showTimeStamp: showTimeStamp, - showUsername: showUsername, - streamChatTheme: streamChatTheme, - onThreadTap: onThreadTap, - deletedBottomRowBuilder: deletedBottomRowBuilder, - streamChat: streamChat, - hasNonUrlAttachments: hasNonUrlAttachments, - usernameBuilder: usernameBuilder, - ), + child: _buildBottomRow(context), ), Padding( padding: EdgeInsets.only( @@ -457,4 +434,30 @@ class MessageWidgetContent extends StatelessWidget { ), ); } + + Widget _buildBottomRow(BuildContext context) { + final defaultWidget = BottomRow( + message: message, + reverse: reverse, + messageTheme: messageTheme, + hasUrlAttachments: hasUrlAttachments, + isOnlyEmoji: isOnlyEmoji, + isDeleted: message.isDeleted, + isGiphy: isGiphy, + showInChannel: showInChannel, + showSendingIndicator: showSendingIndicator, + showThreadReplyIndicator: showThreadReplyIndicator, + showTimeStamp: showTimeStamp, + showUsername: showUsername, + streamChatTheme: streamChatTheme, + onThreadTap: onThreadTap, + deletedBottomRowBuilder: deletedBottomRowBuilder, + streamChat: streamChat, + hasNonUrlAttachments: hasNonUrlAttachments, + usernameBuilder: usernameBuilder, + ); + + return bottomRowBuilder?.call(context, message, defaultWidget) ?? + defaultWidget; + } } From 505c592066a8c18139c2b6803dc9b97a7d72dfb5 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Wed, 7 Dec 2022 15:03:18 +0200 Subject: [PATCH 02/20] chore: bump dart vlc dependency --- .../example/linux/flutter/generated_plugin_registrant.cc | 4 ---- .../example/linux/flutter/generated_plugins.cmake | 1 - packages/stream_chat_flutter/pubspec.yaml | 4 ++-- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat_flutter/example/linux/flutter/generated_plugin_registrant.cc b/packages/stream_chat_flutter/example/linux/flutter/generated_plugin_registrant.cc index 0ef32934..bb19d0b5 100644 --- a/packages/stream_chat_flutter/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/stream_chat_flutter/example/linux/flutter/generated_plugin_registrant.cc @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -21,9 +20,6 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) desktop_drop_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin"); desktop_drop_plugin_register_with_registrar(desktop_drop_registrar); - g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); - file_selector_plugin_register_with_registrar(file_selector_linux_registrar); g_autoptr(FlPluginRegistrar) screen_retriever_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); diff --git a/packages/stream_chat_flutter/example/linux/flutter/generated_plugins.cmake b/packages/stream_chat_flutter/example/linux/flutter/generated_plugins.cmake index deceb06e..fb9923e9 100644 --- a/packages/stream_chat_flutter/example/linux/flutter/generated_plugins.cmake +++ b/packages/stream_chat_flutter/example/linux/flutter/generated_plugins.cmake @@ -5,7 +5,6 @@ list(APPEND FLUTTER_PLUGIN_LIST dart_vlc desktop_drop - file_selector_linux screen_retriever sqlite3_flutter_libs url_launcher_linux diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 31ab0cb6..099282c3 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -6,7 +6,7 @@ repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues environment: - sdk: '>=2.17.0 <3.0.0' + sdk: ">=2.17.0 <3.0.0" flutter: ">=1.17.0" dependencies: @@ -14,7 +14,7 @@ dependencies: chewie: ^1.3.4 collection: ^1.15.0 contextmenu: ^3.0.0 - dart_vlc: ^0.3.0 + dart_vlc: ^0.4.0 desktop_drop: ^0.4.0 diacritic: ^0.1.3 dio: ^4.0.6 From 122f52fae39ffd6d57b5a5d422ed7436c4d01701 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Wed, 7 Dec 2022 15:05:31 +0200 Subject: [PATCH 03/20] doc: update changelog --- packages/stream_chat_flutter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 6bc7a25f..a5f4fba8 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -5,6 +5,7 @@ - Updated `lottie` dependency to `^2.0.0` - Updated `desktop_drop` dependency to `^0.4.0` - Updated `connectivity_plus` dependency to `^3.0.2` +- Updated `dart_vlc` dependency to `^0.4.0` 🐞 Fixed - [[#1379]](https://github.com/GetStream/stream-chat-flutter/issues/1379) Fixed "Issues with photo attachments on web", where the cached image attachment would not render while uploading. From dfb1361e5b426b511d1e82386510e7bc3b5c2ac6 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Thu, 8 Dec 2022 17:13:04 +0200 Subject: [PATCH 04/20] docs: update push notifications guide --- .../adding_push_notifications_v2.mdx | 163 ++++++++++++------ 1 file changed, 108 insertions(+), 55 deletions(-) diff --git a/docusaurus/docs/Flutter/05-guides/05-push-notifications/adding_push_notifications_v2.mdx b/docusaurus/docs/Flutter/05-guides/05-push-notifications/adding_push_notifications_v2.mdx index 351e8021..8459b323 100644 --- a/docusaurus/docs/Flutter/05-guides/05-push-notifications/adding_push_notifications_v2.mdx +++ b/docusaurus/docs/Flutter/05-guides/05-push-notifications/adding_push_notifications_v2.mdx @@ -8,20 +8,56 @@ Adding Push Notifications (V2) To Your Application ### Introduction +This guide details how to add push notifications to your app. + Push notifications are a core part of the experience for a messaging app. Users often need to be notified of new messages and old notifications sometimes need to be updated silently. -This guide details how to add push notifications to your app. +Stream Chat sends push notification to channel members that have at least one registered device. +Push notifications are only sent for new messages and not for other events. +You can use [Webhooks](https://getstream.io/chat/docs/android/webhooks_overview/) to send push notifications on other types of events. You can read more about Stream’s [push delivery logic](https://getstream.io/chat/docs/flutter-dart/push_introduction/?language=dart#push-delivery-rules). +To receive push notifications from Stream Chat, you'll need to: + +1. Configure your push notification provider on the Stream Dashboard. +2. Add the client-side integration. For Flutter this guide demonstrates using Firebase Cloud Messaging (FCM). + +### Push Delivery Rules + +Push message delivery behaves according to these rules: + +- Push notifications are sent only for new messages. +- Only channel members receive push messages. +- Members receive push notifications regardless of their online status. +- Replies inside a [thread](https://getstream.io/chat/docs/threads/) are only sent to users that are part of that thread: + - They posted at least one message + - They were mentioned +- Messages from muted users are not sent. +- Messages from muted channels are not sent. +- Messages are sent to all registered devices for a user (up to 25). +- The message doesn't contain the flag `skip_push` as true. +- `push_notifications` is enabled (default) on the channel type for message is sent. + +:::info + +If you would like get push notifications only when users are offline, please contact support. + +::: + +:::caution + +Push notifications require membership. Watching a channel isn't enough. + +::: + ### Setup FCM To integrate push notifications in your Flutter app, you need to use the package [firebase_messaging](https://pub.dev/packages/firebase_messaging). - -Follow the [Firebase documentation](https://firebase.flutter.dev/docs/messaging/overview/) to set up the plugin for Android and iOS. - +Follow the [Flutter Firebase documentation](https://firebase.flutter.dev/docs/messaging/overview/) to set up the plugin for Android and iOS. +Additional setup and instructions can be found [here](https://firebase.google.com/docs/cloud-messaging/flutter/client). Be sure to read this documentation to understand Firebase messaging functionality. Once that's done, FCM should be able to send push notifications to your devices. @@ -29,9 +65,9 @@ Once that's done, FCM should be able to send push notifications to your devices. #### Step 1 - Get the Firebase Credentials -These credentials are the [private key file](https://firebase.google.com/docs/admin/setup#:~:text=To%20generate%20a%20private%20key%20file%20for%20your%20service%20account%3A) for your service account, in firebase console. +These credentials are the [private key file](https://firebase.google.com/docs/admin/setup#:~:text=To%20generate%20a%20private%20key%20file%20for%20your%20service%20account%3A) for your service account, in Firebase console. -To generate a private key file for your service account, in the Firebase console: +To generate a private key file for your service account in the Firebase console: - Open Settings > Service Accounts. @@ -39,7 +75,7 @@ To generate a private key file for your service account, in the Firebase console - Securely store the JSON file containing the key. -This JSON file contains the credentials which needs to be uploaded to Stream’s server as explained in next step. +This JSON file contains the credentials that need to be uploaded to Stream’s server, as explained in the next step. #### Step 2 - Upload the Firebase Credentials to Stream @@ -47,11 +83,11 @@ You can upload your Firebase credentials using either the dashboard or the app s ##### Using the Stream Dashboard -1. Go to the **Chat Overview** page on Stream Dashboard +1. Go to the **Chat Overview** page on Stream Dashboard. ![](../../assets/chat_overview_page-2fbd5bbfb70c5623bd37ff7d6c41bf4d.png) -2. Enable **Firebase Notification** toggle on **Chat Overview** +2. Enable **Firebase Notification** toggle on **Chat Overview**. ![](../../assets/firebase_notifications_toggle-5aeabfcbdc24cb8f1fea7d41d0e845fc.png) @@ -61,7 +97,7 @@ You can upload your Firebase credentials using either the dashboard or the app s You can also enable Firebase notifications and upload the Firebase credentials using one of our server SDKs. -For example, using the JavaScript SDK: +For example, using the Stream JavaScript SDK: ```js const client = StreamChat.getInstance('api_key', 'api_secret'); @@ -76,55 +112,56 @@ client.updateAppSettings({ ), }); ``` + ### Registering a Device With Stream Backend -Once you configure a Firebase server key and set it up on Stream dashboard then a device that is supposed to receive push notifications needs to be registered on the Stream backend. This is usually done by listening for Firebase device token updates and passing them to the backend as follows: +Once you configure a Firebase server key and set it up on the Stream dashboard, a device that is supposed to receive push notifications needs to be registered on the Stream backend. This is usually done by listening for Firebase device token updates and passing them to the backend as follows: ```dart -firebaseMessaging.onTokenRefresh.listen((token) { - client.addDevice(token, PushProvider.firebase); -}); +firebaseMessaging.onTokenRefresh.listen((token) { + client.addDevice(token, PushProvider.firebase); +}); ``` -Push Notifications v2 also supports specifying a name to the push device tokens you register. By setting the optional `pushProviderName` param in the `addDevice` call you can support different configurations between the device and the `PushProvider`. +Push Notifications v2 also supports specifying a name for the push device tokens you register. By setting the optional `pushProviderName` param in the `addDevice` call, you can support different configurations between the device and the `PushProvider`. ```dart -firebaseMessaging.onTokenRefresh.listen((token) { - client.addDevice(token, PushProvider.firebase, pushProviderName: 'my-custom-config'); -}); +firebaseMessaging.onTokenRefresh.listen((token) { + client.addDevice(token, PushProvider.firebase, pushProviderName: 'my-custom-config'); +}); ``` ### Receiving Notifications -Push notifications behave a bit differently depending on whether you are using iOS or Android. +Push notifications behave differently depending on whether you are using iOS or Android. See [here](https://firebase.flutter.dev/docs/messaging/usage#message-types) to understand the difference between **notification** and **data** payloads. #### iOS -On iOS we send both a **notification** and a **data** payload. +On iOS, we send both a **notification** and a **data** payload. This means you don't need to do anything special to get the notification to show up. However, you might want to handle the data payload to perform some logic when the user taps on the notification. To update the template, you can use a backend SDK. -For example, using the javascript SDK: +For example, using the Stream JavaScript SDK: ```js const client = StreamChat.getInstance(‘api_key’, ‘api_secret’); const apn_template = `{ - "aps": { - "alert": { - "title": "New message from {{ sender.name }}", - "body": "{{ truncate message.text 2000 }}" - }, - "mutable-content": 1, - "category": "stream.chat" - }, - "stream": { - "sender": "stream.chat", + "aps": { + "alert": { + "title": "New message from {{ sender.name }}", + "body": "{{ truncate message.text 2000 }}" + }, + "mutable-content": 1, + "category": "stream.chat" + }, + "stream": { + "sender": "stream.chat", "type": "message.new", "version": "v2", "id": "{{ message.id }}", "cid": "{{ channel.cid }}" - } + } }`; client.updateAppSettings({ @@ -134,13 +171,23 @@ client.updateAppSettings({ ``` #### Android -On Android we send only a **data** payload. This gives you more flexibility and lets you decide what to do with the notification. + +On Android, we send only a **data** payload. This gives you more flexibility and lets you decide what to do with the notification. For example, you can listen and generate a notification from them. -To generate a notification when a **data-only** message is received and the app is in background: +The code below demonstrates how to generate a notification when a **data-only** message is received and the app is in the background. + +There are a few things to keep in mind about your background message handler: + +1. It must not be an anonymous function. +2. It must be a top-level function (e.g. not a class method which requires initialization). +3. It must be annotated with @pragma('vm:entry-point') right above the function declaration (otherwise it may be removed during tree shaking for release mode). + +For additional information on background messages, please see the [Firebase documentation](https://firebase.google.com/docs/cloud-messaging/flutter/receive#background_messages). ```dart +@pragma('vm:entry-point') Future onBackgroundMessage(RemoteMessage message) async { final chatClient = StreamChatClient(apiKey); @@ -164,7 +211,7 @@ void handleNotification( final flutterLocalNotificationsPlugin = await setupLocalNotifications(); final messageId = data['id']; final response = await chatClient.getMessage(messageId); - + flutterLocalNotificationsPlugin.show( 1, 'New message from ${response.message.user.name} in ${response.channel.name}', @@ -181,13 +228,13 @@ void handleNotification( FirebaseMessaging.onBackgroundMessage(onBackgroundMessage); ``` -In the above example, you get the message details using the `getMessage` method and then you use the [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications) package to show the actual notification. +In the above example, you get the message details using the `getMessage` method, and then you use the [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications) package to show the actual notification. ##### Using a Template on Android -It's still possible to add a **notification** payload to Android notifications. +Adding a **notification** payload to Android notifications is still possible. You can do so by adding a template using a backend SDK. -For example, using the javascript SDK: +For example, using the Stream JavaScript SDK: ```js const client = StreamChat.getInstance(‘api_key’, ‘api_secret’); @@ -207,11 +254,12 @@ client.updateAppSettings({ ### Possible Issues -Make sure to read the [general push notification docs](https://getstream.io/chat/docs/flutter-dart/push_introduction/?language=dart) in order to avoid known gotchas that may make your relationship with notifications difficult 😢. +Make sure to read the [general push notification docs](https://getstream.io/chat/docs/flutter-dart/push_introduction/?language=dart) to prevent common issues with notifications 😢. ### Testing if Push Notifications are Setup Correctly If you're not sure whether you've set up push notifications correctly, for example, you don't always receive them, or they don’t work reliably, then you can follow these steps to make sure your config is correct and working: + 1. Clone our repo for push testing: `git clone git@github.com:GetStream/chat-push-test.git` 2. `cd flutter` 3. In that folder run `flutter pub get` @@ -221,18 +269,18 @@ If you're not sure whether you've set up push notifications correctly, for examp 7. Run the app 8. Accept push notification permission (iOS only) 9. Tap on `Device ID` and copy it -11. After configuring [stream-cli](https://github.com/GetStream/stream-cli), run the following command using your user ID: +10. After configuring [stream-cli](https://github.com/GetStream/stream-cli), run the following command using your user ID: + ```shell -stream chat:push:test -u +stream chat:push:test -u ``` You should get a test push notification 🥳 - ### Foreground Notifications -Sometimes you may want to show a notification when the app is in the foreground. -For example, when you're in a channel and you receive a new message from someone in another channel. +You may want to show a notification when the app is in the foreground. +For example, when you're in a channel and receive a new message from someone in another channel. For this scenario, you can also use the `flutter_local_notifications` package to show a notification. @@ -248,25 +296,31 @@ FirebaseMessaging.onMessage.listen((message) async { ``` :::note -You should also check that the channel of the message is different than the channel in the foreground. +You should also check that the message's channel differs from the channel in the foreground. How you do this depends on your app infrastructure and how you handle navigation. + Take a look at the [Stream Chat v1 sample app](https://github.com/GetStream/flutter-samples/blob/main/packages/stream_chat_v1/lib/home_page.dart#L11) to see how we're doing it over there. ::: ### Saving Notification Messages to the Offline Storage (Only Android) -When the app is closed you may want to save received messages when you receive them via a notification so that later on when you open the app they're already there. +When the app is closed, you can save incoming messages when you receive them via a notification so that they're already there later when you open the app. -To do this you need to integrate the package [stream_chat_persistence](https://pub.dev/packages/stream_chat_persistence) in our app that exports a persistence client, see [here](https://pub.dev/packages/stream_chat_persistence#usage) how to set it up. +To do this, you need to integrate the package [stream_chat_persistence](https://pub.dev/packages/stream_chat_persistence) that exports a persistence client. See [here](https://pub.dev/packages/stream_chat_persistence#usage) for information on how to set it up. -Then calling `FirebaseMessaging.onBackgroundMessage(...)` you need to use a TOP-LEVEL or STATIC function to handle background messages; here is an example: +Then calling `FirebaseMessaging.onBackgroundMessage(...)` you need to use a TOP-LEVEL or STATIC function to handle background messages. + +For additional information on background messages, please see the [Firebase documentation](https://firebase.google.com/docs/cloud-messaging/flutter/receive#background_messages). + +Here is an example: ```dart +@pragma('vm:entry-point') Future onBackgroundMessage(RemoteMessage message) async { final chatClient = StreamChatClient(apiKey); - final persistenceClient = StreamChatPersistenceClient(); - - await persistenceClient.connect(userId); + final persistenceClient = StreamChatPersistenceClient(); + + await persistenceClient.connect(userId); chatClient.connectUser( User(id: userId), @@ -287,9 +341,9 @@ void handleNotification( final messageId = data['id']; final cid = data['cid']; final response = await chatClient.getMessage(messageId); - await persistenceClient.updateMessages(cid, [response.message]); - - persistenceClient.disconnect(); + await persistenceClient.updateMessages(cid, [response.message]); + + persistenceClient.disconnect(); flutterLocalNotificationsPlugin.show( 1, @@ -306,4 +360,3 @@ void handleNotification( FirebaseMessaging.onBackgroundMessage(onBackgroundMessage); ``` - From bf610a9a3de6a9ceb1277a911c8979216fed0d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Adasiewicz?= Date: Fri, 16 Dec 2022 14:20:40 +0100 Subject: [PATCH 05/20] fix: initializing last synced data --- packages/stream_chat/CHANGELOG.md | 5 +++++ packages/stream_chat/lib/src/client/client.dart | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 2937a2c9..7b0b41ab 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,8 @@ +## Upcomming + +🐞 Fixed +- Fixed initializing last synced date. + ## 5.1.0 ✅ Added diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index a9569c20..4f2dbda3 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -453,6 +453,15 @@ class StreamChatClient { if (persistenceEnabled) { await sync(cids: cids, lastSyncAt: _lastSyncedAt); } + } else { + // channels are empty, assuming it's a fresh start + // and making sure `lastSyncAt` is initialized + if (persistenceEnabled) { + final lastSyncAt = await _chatPersistenceClient?.getLastSyncAt(); + if (lastSyncAt == null) { + await _chatPersistenceClient?.updateLastSyncAt(DateTime.now()); + } + } } handleEvent(Event( type: EventType.connectionRecovered, From cc7231d370f5d5e34ec1d1de48320f2fa6b41cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Adasiewicz?= Date: Fri, 16 Dec 2022 15:08:40 +0100 Subject: [PATCH 06/20] fix tests --- packages/stream_chat/test/src/client/client_test.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/test/src/client/client_test.dart b/packages/stream_chat/test/src/client/client_test.dart index bc6ff843..5389564b 100644 --- a/packages/stream_chat/test/src/client/client_test.dart +++ b/packages/stream_chat/test/src/client/client_test.dart @@ -516,6 +516,9 @@ void main() { }); setUp(() async { + when(() => persistence.updateLastSyncAt(any())) + .thenAnswer((_) => Future.value()); + when(persistence.getLastSyncAt).thenAnswer((_) async => null); client = StreamChatClient(apiKey, chatApi: api, ws: ws) ..chatPersistenceClient = persistence; await client.connectUser(user, token); @@ -532,9 +535,11 @@ void main() { test( '''should update persistence connectionInfo and lastSync when sync succeeds''', () async { + // persistence.updateLastSyncAt might be called when connecting the user. + // Resetting the logs so we start counting invocations correctly. + reset(persistence); const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; final lastSyncAt = DateTime.now(); - when(() => api.general.sync(cids, lastSyncAt)) .thenAnswer((_) async => SyncResponse() ..events = [ @@ -567,6 +572,9 @@ void main() { test( 'should work fine if persistence contains sync params', () async { + // persistence.updateLastSyncAt might be called when connecting the user. + // Resetting the logs so we start counting invocations correctly. + reset(persistence); const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; final lastSyncAt = DateTime.now(); From bc5d452782fac2f090035622efcd0c69fbd09c14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Adasiewicz?= Date: Fri, 16 Dec 2022 15:38:47 +0100 Subject: [PATCH 07/20] add line break in comments --- packages/stream_chat/test/src/client/client_test.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/test/src/client/client_test.dart b/packages/stream_chat/test/src/client/client_test.dart index 5389564b..7a605557 100644 --- a/packages/stream_chat/test/src/client/client_test.dart +++ b/packages/stream_chat/test/src/client/client_test.dart @@ -535,7 +535,8 @@ void main() { test( '''should update persistence connectionInfo and lastSync when sync succeeds''', () async { - // persistence.updateLastSyncAt might be called when connecting the user. + // persistence.updateLastSyncAt might be called + // when connecting the user. // Resetting the logs so we start counting invocations correctly. reset(persistence); const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; @@ -572,7 +573,8 @@ void main() { test( 'should work fine if persistence contains sync params', () async { - // persistence.updateLastSyncAt might be called when connecting the user. + // persistence.updateLastSyncAt might be called + // when connecting the user. // Resetting the logs so we start counting invocations correctly. reset(persistence); const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; From 5c6e67683def03a1498a058a79b9f889b4ccee09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Adasiewicz?= Date: Fri, 16 Dec 2022 15:41:16 +0100 Subject: [PATCH 08/20] fix: attachment actions modal test --- .../attachment_actions_modal_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/attachment_actions_modal/attachment_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/attachment_actions_modal/attachment_actions_modal_test.dart index 2c19fe25..08213b09 100644 --- a/packages/stream_chat_flutter/test/src/attachment_actions_modal/attachment_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_actions_modal/attachment_actions_modal_test.dart @@ -163,7 +163,7 @@ void main() { ); testWidgets( - 'tapping on reply should pop', + 'tapping on reply should invoke callback', (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); @@ -174,7 +174,7 @@ void main() { final themeData = ThemeData(); final streamTheme = StreamChatThemeData.fromTheme(themeData); - final mockObserver = MockNavigatorObserver(); + final mockCallback = MockVoidCallback(); final attachment = Attachment( type: 'image', @@ -192,7 +192,6 @@ void main() { await tester.pumpWidget( MaterialApp( theme: themeData, - navigatorObservers: [mockObserver], home: StreamChat( streamChatThemeData: streamTheme, client: client, @@ -200,13 +199,14 @@ void main() { child: AttachmentActionsModal( message: message, attachment: attachment, + onReply: mockCallback, ), ), ), ), ); await tester.tap(find.text('Reply')); - verify(() => mockObserver.didPop(any(), any())); + verify(mockCallback.call); }, ); From faa2751c05fe1f54cc6cd3e30fb7b0e32afaa886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Adasiewicz?= Date: Tue, 20 Dec 2022 12:44:55 +0100 Subject: [PATCH 09/20] chore: bump file_picker dependency version --- packages/stream_chat_flutter/CHANGELOG.md | 3 ++- packages/stream_chat_flutter/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index a5f4fba8..11eed013 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -6,6 +6,7 @@ - Updated `desktop_drop` dependency to `^0.4.0` - Updated `connectivity_plus` dependency to `^3.0.2` - Updated `dart_vlc` dependency to `^0.4.0` +- Updated `file_picker` dependency to `^5.2.4` 🐞 Fixed - [[#1379]](https://github.com/GetStream/stream-chat-flutter/issues/1379) Fixed "Issues with photo attachments on web", where the cached image attachment would not render while uploading. @@ -1319,4 +1320,4 @@ The property showVideoFullScreen was added mainly because of this issue brianega ## 0.0.1 -- First release \ No newline at end of file +- First release diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 099282c3..954cb87c 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: diacritic: ^0.1.3 dio: ^4.0.6 ezanimation: ^0.6.0 - file_picker: ^4.1.3 + file_picker: ^5.2.4 file_selector: ^0.9.0 flutter: sdk: flutter From 9a45f72adf9d4d7160520c1de1e34e7f8288e373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Tue, 20 Dec 2022 10:28:38 +0100 Subject: [PATCH 10/20] core: Add Huawei and Xiaomi PushProviders --- .../lib/src/core/api/device_api.dart | 6 +++ .../test/src/core/api/device_api_test.dart | 39 ++++++++++++------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/packages/stream_chat/lib/src/core/api/device_api.dart b/packages/stream_chat/lib/src/core/api/device_api.dart index cc0a1952..25a678dd 100644 --- a/packages/stream_chat/lib/src/core/api/device_api.dart +++ b/packages/stream_chat/lib/src/core/api/device_api.dart @@ -6,6 +6,12 @@ enum PushProvider { /// Send notifications using Google's Firebase Cloud Messaging firebase, + /// Send notifications using Huawei's Push Kit + huawei, + + /// Send notifications using Xiaomi's Mi Push Service + xiaomi, + /// Send notifications using Apple's Push Notification service apn, } diff --git a/packages/stream_chat/test/src/core/api/device_api_test.dart b/packages/stream_chat/test/src/core/api/device_api_test.dart index 13cfe4dc..4e04da6b 100644 --- a/packages/stream_chat/test/src/core/api/device_api_test.dart +++ b/packages/stream_chat/test/src/core/api/device_api_test.dart @@ -22,26 +22,37 @@ void main() { test('addDevice should work', () async { const deviceId = 'test-device-id'; - const pushProvider = PushProvider.firebase; - + const pushProvidersMap = { + 'apn': PushProvider.apn, + 'firebase': PushProvider.firebase, + 'huawei': PushProvider.huawei, + 'xiaomi': PushProvider.xiaomi, + }; const path = '/devices'; - when(() => client.post( - path, - data: { - 'id': deviceId, - 'push_provider': pushProvider.name, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + for (final pushProviderMapEntry in pushProvidersMap.entries) { + final data = { + 'id': deviceId, + 'push_provider': pushProviderMapEntry.key, + }; + when(() { + return client.post( + path, + data: data, + ); + }).thenAnswer( + (_) async => successResponse(path, data: {})); - final res = await deviceApi.addDevice(deviceId, pushProvider); + final res = + await deviceApi.addDevice(deviceId, pushProviderMapEntry.value); - expect(res, isNotNull); + expect(res, isNotNull); - verify(() => client.post(path, data: any(named: 'data'))).called(1); + verify(() => client.post(path, data: data)).called(1); + } verifyNoMoreInteractions(client); + expect(pushProvidersMap.length, PushProvider.values.length, + reason: 'All PushProvider should be tested'); }); test('addDevice should work with pushProviderName', () async { From 38cea12d3f831c85fa974a912aa5ec8bcc1415b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Tue, 20 Dec 2022 16:17:52 +0100 Subject: [PATCH 11/20] Update CHANGELOG --- packages/stream_chat/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 2937a2c9..e6d011f1 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcomming + +✅ Added + +- Added `Huawei` and `Xiaomi` PushProviders. + ## 5.1.0 ✅ Added From 237cd67080db65608c95251bfd7645031e1dcd01 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 22 Dec 2022 15:44:23 +0530 Subject: [PATCH 12/20] feat(ui): added a new `bottomRowBuilderWithDefaultWidget` param in `MessageWidget`. - Deprecated `StreamMessageWidget.bottomRowBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`. - Deprecated `StreamMessageWidget.deletedBottomRowBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`. - Deprecated `StreamMessageWidget.usernameBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`. Signed-off-by: xsahil03x --- packages/stream_chat_flutter/CHANGELOG.md | 5 +- .../src/message_widget/message_widget.dart | 74 +++++++++++++++---- .../message_widget_content.dart | 56 ++++++++++++-- 3 files changed, 110 insertions(+), 25 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index b9070ada..e925b431 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,7 +1,7 @@ ## Upcomming ✅ Added -- Added third parameter (default `BottomRow` widget with `copyWith` method available) to `bottomRowBuilder` of `StreamMessageWidget` to allow easier customization. +- Added a new `bottomRowBuilderWithDefaultWidget` parameter to `StreamMessageWidget` which contains a third parameter (default `BottomRow` widget with `copyWith` method available) to allow easier customization. 🔄 Changed @@ -10,6 +10,9 @@ - Updated `connectivity_plus` dependency to `^3.0.2` - Updated `dart_vlc` dependency to `^0.4.0` - Updated `file_picker` dependency to `^5.2.4` +- Deprecated `StreamMessageWidget.bottomRowBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`. +- Deprecated `StreamMessageWidget.deletedBottomRowBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`. +- Deprecated `StreamMessageWidget.usernameBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`. 🐞 Fixed - [[#1379]](https://github.com/GetStream/stream-chat-flutter/issues/1379) Fixed "Issues with photo attachments on web", where the cached image attachment would not render while uploading. 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 37869edc..2d59fa58 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 @@ -80,8 +80,15 @@ class StreamMessageWidget extends StatefulWidget { this.userAvatarBuilder, this.editMessageInputBuilder, this.textBuilder, - this.bottomRowBuilder, - this.deletedBottomRowBuilder, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.bottomRowBuilder, + this.bottomRowBuilderWithDefaultWidget, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.deletedBottomRowBuilder, this.customAttachmentBuilders, this.padding, this.textPadding = const EdgeInsets.symmetric( @@ -93,11 +100,18 @@ class StreamMessageWidget extends StatefulWidget { this.onQuotedMessageTap, this.customActions = const [], this.onAttachmentTap, - this.usernameBuilder, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.usernameBuilder, this.imageAttachmentThumbnailSize = const Size(400, 400), this.imageAttachmentThumbnailResizeType = 'clip', this.imageAttachmentThumbnailCropType = 'center', - }) : attachmentBuilders = { + }) : assert( + bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null, + 'You can only use one of the two bottom row builders', + ), + attachmentBuilders = { 'image': (context, message, attachments) { final border = RoundedRectangleBorder( side: attachmentBorderSide ?? @@ -307,7 +321,13 @@ class StreamMessageWidget extends StatefulWidget { /// {@template bottomRowBuilder} /// Widget builder for building a bottom row below the message /// {@endtemplate} - final Widget Function(BuildContext, Message, BottomRow)? bottomRowBuilder; + final BottomRowBuilder? bottomRowBuilder; + + /// {@template bottomRowBuilderWithDefaultWidget} + /// Widget builder for building a bottom row below the message. + /// Also contains the default bottom row widget. + /// {@endtemplate} + final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget; /// {@template deletedBottomRowBuilder} /// Widget builder for building a bottom row below a deleted message @@ -538,9 +558,19 @@ class StreamMessageWidget extends StatefulWidget { void Function(Message)? onReplyTap, Widget Function(BuildContext, Message)? editMessageInputBuilder, Widget Function(BuildContext, Message)? textBuilder, - Widget Function(BuildContext, Message)? usernameBuilder, - Widget Function(BuildContext, Message, BottomRow)? bottomRowBuilder, - Widget Function(BuildContext, Message)? deletedBottomRowBuilder, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') Widget Function(BuildContext, Message)? usernameBuilder, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') BottomRowBuilder? bottomRowBuilder, + BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') Widget Function(BuildContext, Message)? deletedBottomRowBuilder, void Function(BuildContext, Message)? onMessageActions, Message? message, StreamMessageThemeData? messageTheme, @@ -588,6 +618,23 @@ class StreamMessageWidget extends StatefulWidget { String? imageAttachmentThumbnailResizeType, String? imageAttachmentThumbnailCropType, }) { + var _bottomRowBuilderWithDefaultWidget = + bottomRowBuilderWithDefaultWidget ?? + this.bottomRowBuilderWithDefaultWidget; + + _bottomRowBuilderWithDefaultWidget ??= (context, message, defaultWidget) { + final _bottomRowBuilder = bottomRowBuilder ?? this.bottomRowBuilder; + if (_bottomRowBuilder != null) { + return _bottomRowBuilder(context, message); + } + + return defaultWidget.copyWith( + onThreadTap: onThreadTap, + usernameBuilder: usernameBuilder, + deletedBottomRowBuilder: deletedBottomRowBuilder, + ); + }; + return StreamMessageWidget( key: key ?? this.key, onMentionTap: onMentionTap ?? this.onMentionTap, @@ -596,10 +643,7 @@ class StreamMessageWidget extends StatefulWidget { editMessageInputBuilder: editMessageInputBuilder ?? this.editMessageInputBuilder, textBuilder: textBuilder ?? this.textBuilder, - usernameBuilder: usernameBuilder ?? this.usernameBuilder, - bottomRowBuilder: bottomRowBuilder ?? this.bottomRowBuilder, - deletedBottomRowBuilder: - deletedBottomRowBuilder ?? this.deletedBottomRowBuilder, + bottomRowBuilderWithDefaultWidget: _bottomRowBuilderWithDefaultWidget, onMessageActions: onMessageActions ?? this.onMessageActions, message: message ?? this.message, messageTheme: messageTheme ?? this.messageTheme, @@ -871,8 +915,6 @@ class _StreamMessageWidgetState extends State showUserAvatar: widget.showUserAvatar, streamChat: _streamChat, translateUserAvatar: widget.translateUserAvatar, - deletedBottomRowBuilder: widget.deletedBottomRowBuilder, - onThreadTap: widget.onThreadTap, shape: widget.shape, borderSide: widget.borderSide, borderRadiusGeometry: widget.borderRadiusGeometry, @@ -880,10 +922,10 @@ class _StreamMessageWidgetState extends State onLinkTap: widget.onLinkTap, onMentionTap: widget.onMentionTap, onQuotedMessageTap: widget.onQuotedMessageTap, - bottomRowBuilder: widget.bottomRowBuilder, + bottomRowBuilderWithDefaultWidget: + widget.bottomRowBuilderWithDefaultWidget, onUserAvatarTap: widget.onUserAvatarTap, userAvatarBuilder: widget.userAvatarBuilder, - usernameBuilder: widget.usernameBuilder, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart index 5cf9f3e0..d02a4364 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart @@ -4,6 +4,18 @@ import 'package:stream_chat_flutter/src/message_widget/message_widget_content_co import 'package:stream_chat_flutter/src/message_widget/reactions/desktop_reactions_builder.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +/// Signature for the builder function that will be called when the message +/// bottom row is built. Includes the [Message]. +typedef BottomRowBuilder = Widget Function(BuildContext, Message); + +/// Signature for the builder function that will be called when the message +/// bottom row is built. Includes the [Message] and the default [BottomRow]. +typedef BottomRowBuilderWithDefaultWidget = Widget Function( + BuildContext, + Message, + BottomRow, +); + /// {@template messageWidgetContent} /// The main content of a [StreamMessageWidget]. /// @@ -51,12 +63,28 @@ class MessageWidgetContent extends StatelessWidget { this.onMentionTap, this.onLinkTap, this.textBuilder, - this.bottomRowBuilder, - this.onThreadTap, - this.deletedBottomRowBuilder, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.bottomRowBuilder, + this.bottomRowBuilderWithDefaultWidget, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.onThreadTap, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.deletedBottomRowBuilder, this.userAvatarBuilder, - this.usernameBuilder, - }); + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.usernameBuilder, + }) : assert( + bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null, + 'You can only use one of the two bottom row builders', + ); /// {@macro reverse} final bool reverse; @@ -152,7 +180,10 @@ class MessageWidgetContent extends StatelessWidget { final double bottomRowPadding; /// {@macro bottomRowBuilder} - final Widget Function(BuildContext, Message, BottomRow)? bottomRowBuilder; + final BottomRowBuilder? bottomRowBuilder; + + /// {@macro bottomRowBuilderWithDefaultWidget} + final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget; /// {@macro showInChannelIndicator} final bool showInChannel; @@ -457,7 +488,16 @@ class MessageWidgetContent extends StatelessWidget { usernameBuilder: usernameBuilder, ); - return bottomRowBuilder?.call(context, message, defaultWidget) ?? - defaultWidget; + if (bottomRowBuilder != null) { + return bottomRowBuilder!(context, message); + } else if (bottomRowBuilderWithDefaultWidget != null) { + return bottomRowBuilderWithDefaultWidget!( + context, + message, + defaultWidget, + ); + } + + return defaultWidget; } } From 5638e0c99d25162566d19c2131a7c058a991ba84 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 22 Dec 2022 16:23:10 +0530 Subject: [PATCH 13/20] chore(ui): minor changes and assertion. Signed-off-by: xsahil03x --- .../src/message_widget/message_widget.dart | 119 +++++++++++------- 1 file changed, 72 insertions(+), 47 deletions(-) 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 2d59fa58..0975320f 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 @@ -618,6 +618,11 @@ class StreamMessageWidget extends StatefulWidget { String? imageAttachmentThumbnailResizeType, String? imageAttachmentThumbnailCropType, }) { + assert( + bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null, + 'You can only use one of the two bottom row builders', + ); + var _bottomRowBuilderWithDefaultWidget = bottomRowBuilderWithDefaultWidget ?? this.bottomRowBuilderWithDefaultWidget; @@ -629,9 +634,10 @@ class StreamMessageWidget extends StatefulWidget { } return defaultWidget.copyWith( - onThreadTap: onThreadTap, - usernameBuilder: usernameBuilder, - deletedBottomRowBuilder: deletedBottomRowBuilder, + onThreadTap: onThreadTap ?? this.onThreadTap, + usernameBuilder: usernameBuilder ?? this.usernameBuilder, + deletedBottomRowBuilder: + deletedBottomRowBuilder ?? this.deletedBottomRowBuilder, ); }; @@ -883,50 +889,69 @@ class _StreamMessageWidgetState extends State ? Alignment.centerRight : Alignment.centerLeft, widthFactor: widget.widthFactor, - child: MessageWidgetContent( - streamChatTheme: _streamChatTheme, - showUsername: showUsername, - showTimeStamp: showTimeStamp, - showThreadReplyIndicator: showThreadReplyIndicator, - showSendingIndicator: showSendingIndicator, - showInChannel: showInChannel, - isGiphy: isGiphy, - isOnlyEmoji: isOnlyEmoji, - hasUrlAttachments: hasUrlAttachments, - messageTheme: widget.messageTheme, - reverse: widget.reverse, - message: widget.message, - hasNonUrlAttachments: hasNonUrlAttachments, - shouldShowReactions: shouldShowReactions, - hasQuotedMessage: hasQuotedMessage, - textPadding: widget.textPadding, - attachmentBuilders: widget.attachmentBuilders, - attachmentPadding: widget.attachmentPadding, - avatarWidth: avatarWidth, - bottomRowPadding: bottomRowPadding, - isFailedState: isFailedState, - isPinned: isPinned, - messageWidget: widget, - showBottomRow: showBottomRow, - showPinHighlight: widget.showPinHighlight, - showReactionPickerIndicator: - widget.showReactionPickerIndicator, - showReactions: showReactions, - showUserAvatar: widget.showUserAvatar, - streamChat: _streamChat, - translateUserAvatar: widget.translateUserAvatar, - shape: widget.shape, - borderSide: widget.borderSide, - borderRadiusGeometry: widget.borderRadiusGeometry, - textBuilder: widget.textBuilder, - onLinkTap: widget.onLinkTap, - onMentionTap: widget.onMentionTap, - onQuotedMessageTap: widget.onQuotedMessageTap, - bottomRowBuilderWithDefaultWidget: - widget.bottomRowBuilderWithDefaultWidget, - onUserAvatarTap: widget.onUserAvatarTap, - userAvatarBuilder: widget.userAvatarBuilder, - ), + child: Builder(builder: (context) { + var _bottomRowBuilderWithDefaultWidget = + widget.bottomRowBuilderWithDefaultWidget; + + _bottomRowBuilderWithDefaultWidget ??= + (context, message, defaultWidget) { + final _bottomRowBuilder = widget.bottomRowBuilder; + if (_bottomRowBuilder != null) { + return _bottomRowBuilder(context, message); + } + + return defaultWidget.copyWith( + onThreadTap: widget.onThreadTap, + usernameBuilder: widget.usernameBuilder, + deletedBottomRowBuilder: widget.deletedBottomRowBuilder, + ); + }; + + return MessageWidgetContent( + streamChatTheme: _streamChatTheme, + showUsername: showUsername, + showTimeStamp: showTimeStamp, + showThreadReplyIndicator: showThreadReplyIndicator, + showSendingIndicator: showSendingIndicator, + showInChannel: showInChannel, + isGiphy: isGiphy, + isOnlyEmoji: isOnlyEmoji, + hasUrlAttachments: hasUrlAttachments, + messageTheme: widget.messageTheme, + reverse: widget.reverse, + message: widget.message, + hasNonUrlAttachments: hasNonUrlAttachments, + shouldShowReactions: shouldShowReactions, + hasQuotedMessage: hasQuotedMessage, + textPadding: widget.textPadding, + attachmentBuilders: widget.attachmentBuilders, + attachmentPadding: widget.attachmentPadding, + avatarWidth: avatarWidth, + bottomRowPadding: bottomRowPadding, + isFailedState: isFailedState, + isPinned: isPinned, + messageWidget: widget, + showBottomRow: showBottomRow, + showPinHighlight: widget.showPinHighlight, + showReactionPickerIndicator: + widget.showReactionPickerIndicator, + showReactions: showReactions, + showUserAvatar: widget.showUserAvatar, + streamChat: _streamChat, + translateUserAvatar: widget.translateUserAvatar, + shape: widget.shape, + borderSide: widget.borderSide, + borderRadiusGeometry: widget.borderRadiusGeometry, + textBuilder: widget.textBuilder, + onLinkTap: widget.onLinkTap, + onMentionTap: widget.onMentionTap, + onQuotedMessageTap: widget.onQuotedMessageTap, + bottomRowBuilderWithDefaultWidget: + _bottomRowBuilderWithDefaultWidget, + onUserAvatarTap: widget.onUserAvatarTap, + userAvatarBuilder: widget.userAvatarBuilder, + ); + }), ), ), ), From fcc78c2188a5deb805630cdf2324d2580e04f46b Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 23 Dec 2022 15:29:36 +0530 Subject: [PATCH 14/20] chore(llc, core, ui): prepare for release v5.2.0. Signed-off-by: xsahil03x --- packages/stream_chat/CHANGELOG.md | 2 +- packages/stream_chat/lib/version.dart | 2 +- packages/stream_chat/pubspec.yaml | 2 +- packages/stream_chat_flutter/CHANGELOG.md | 2 +- packages/stream_chat_flutter/pubspec.yaml | 4 ++-- packages/stream_chat_flutter_core/CHANGELOG.md | 2 +- packages/stream_chat_flutter_core/pubspec.yaml | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 89e6488f..a8e749e0 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcomming +## 5.2.0 ✅ Added diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index 723891fc..8d097258 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 = '5.1.0'; +const PACKAGE_VERSION = '5.2.0'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 1ee1491b..8d765131 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: 5.1.0 +version: 5.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_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index e925b431..899179a5 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcomming +## 5.2.0 ✅ Added - Added a new `bottomRowBuilderWithDefaultWidget` parameter to `StreamMessageWidget` which contains a third parameter (default `BottomRow` widget with `copyWith` method available) to allow easier customization. diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 954cb87c..67903b50 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: 5.1.0 +version: 5.2.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -39,7 +39,7 @@ dependencies: rxdart: ^0.27.0 share_plus: ^4.5.0 shimmer: ^2.0.0 - stream_chat_flutter_core: ^5.1.0 + stream_chat_flutter_core: ^5.2.0 synchronized: ^3.0.0 thumblr: ^0.0.4 url_launcher: ^6.1.0 diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 2103798e..7909c079 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcomming +## 5.2.0 🔄 Changed diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index c3b4cffc..8999e5ab 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: 5.1.0 +version: 5.2.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -17,7 +17,7 @@ dependencies: freezed_annotation: ^2.0.3 meta: ^1.3.0 rxdart: ^0.27.0 - stream_chat: ^5.1.0 + stream_chat: ^5.2.0 dev_dependencies: build_runner: ^2.0.1 dart_code_metrics: ^4.4.0 From 0c1df897f8e16a920093e5af0797e9d19028a614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Wed, 4 Jan 2023 09:16:25 +0100 Subject: [PATCH 15/20] Fix: Trim whitespaces of a message before rendering it --- .../lib/src/message_widget/message_text.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart index f686a3c8..947d76c4 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart @@ -40,7 +40,8 @@ class StreamMessageText extends StatelessWidget { .translate(language) .replaceMentions() .text - ?.replaceAll('\n', '\n\n'); + ?.replaceAll('\n', '\n\n') + .trim(); final themeData = Theme.of(context); return MarkdownBody( data: messageText ?? '', From ea123ad29e1f2e0dd58f9204bce3ef2932e492aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Wed, 4 Jan 2023 12:41:55 +0100 Subject: [PATCH 16/20] Update CHANGELOG --- packages/stream_chat_flutter/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 899179a5..c1ec13ac 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,8 @@ +## Upcoming + +🐞 Fixed +- [[#1424]](https://github.com/GetStream/stream-chat-flutter/issues/1424) Fixed a render issue when showing messages starting with 4 whitespaces. + ## 5.2.0 ✅ Added From 13139afbced648ed13136cf8eb35a985afb4a08b Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 16 Jan 2023 15:32:52 +0530 Subject: [PATCH 17/20] fix(ui): fix attachment picker not able to identify web in mobile. Signed-off-by: xsahil03x --- ...stream_attachment_picker_bottom_sheet.dart | 68 ++++++++----------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart index 3113feb1..11fb966b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart @@ -1,7 +1,7 @@ import 'dart:async'; +import 'package:flutter/foundation.dart' show kIsWeb, defaultTargetPlatform; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Shows a modal material design bottom sheet. @@ -107,44 +107,34 @@ Future showStreamAttachmentPickerModalBottomSheet({ controller: controller, initialAttachments: initialAttachments, builder: (context, controller, child) { - return PlatformWidget( - web: (context) { - return webOrDesktopAttachmentPickerBuilder.call( - context: context, - controller: controller, - customOptions: customOptions?.map( - WebOrDesktopAttachmentPickerOption.fromAttachmentPickerOption, - ), - attachmentThumbnailSize: attachmentThumbnailSize, - attachmentThumbnailFormat: attachmentThumbnailFormat, - attachmentThumbnailQuality: attachmentThumbnailQuality, - attachmentThumbnailScale: attachmentThumbnailScale, - ); - }, - mobile: (context) { - return mobileAttachmentPickerBuilder.call( - context: context, - controller: controller, - customOptions: customOptions, - attachmentThumbnailSize: attachmentThumbnailSize, - attachmentThumbnailFormat: attachmentThumbnailFormat, - attachmentThumbnailQuality: attachmentThumbnailQuality, - attachmentThumbnailScale: attachmentThumbnailScale, - ); - }, - desktop: (context) { - return webOrDesktopAttachmentPickerBuilder.call( - context: context, - controller: controller, - customOptions: customOptions?.map( - WebOrDesktopAttachmentPickerOption.fromAttachmentPickerOption, - ), - attachmentThumbnailSize: attachmentThumbnailSize, - attachmentThumbnailFormat: attachmentThumbnailFormat, - attachmentThumbnailQuality: attachmentThumbnailQuality, - attachmentThumbnailScale: attachmentThumbnailScale, - ); - }, + final currentPlatform = defaultTargetPlatform; + final isWebOrDesktop = kIsWeb || + currentPlatform == TargetPlatform.macOS || + currentPlatform == TargetPlatform.linux || + currentPlatform == TargetPlatform.windows; + + if (isWebOrDesktop) { + return webOrDesktopAttachmentPickerBuilder.call( + context: context, + controller: controller, + customOptions: customOptions?.map( + WebOrDesktopAttachmentPickerOption.fromAttachmentPickerOption, + ), + attachmentThumbnailSize: attachmentThumbnailSize, + attachmentThumbnailFormat: attachmentThumbnailFormat, + attachmentThumbnailQuality: attachmentThumbnailQuality, + attachmentThumbnailScale: attachmentThumbnailScale, + ); + } + + return mobileAttachmentPickerBuilder.call( + context: context, + controller: controller, + customOptions: customOptions, + attachmentThumbnailSize: attachmentThumbnailSize, + attachmentThumbnailFormat: attachmentThumbnailFormat, + attachmentThumbnailQuality: attachmentThumbnailQuality, + attachmentThumbnailScale: attachmentThumbnailScale, ); }, ); From 249a1b0df29eea167ec2db8b9c51edd9f4956cf9 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 17 Jan 2023 14:08:14 +0530 Subject: [PATCH 18/20] chore(ui): update CHANGELOG.md Signed-off-by: xsahil03x --- packages/stream_chat_flutter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index c1ec13ac..dfb83a73 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -2,6 +2,7 @@ 🐞 Fixed - [[#1424]](https://github.com/GetStream/stream-chat-flutter/issues/1424) Fixed a render issue when showing messages starting with 4 whitespaces. +- Fixed a bug where the `AttachmentPickerBottomSheet` was not able to identify the mobile browser. ## 5.2.0 From e3fe2a232a7aaec70bba888d526957a19e27acd7 Mon Sep 17 00:00:00 2001 From: Carlos GC Date: Thu, 19 Jan 2023 23:59:49 +0100 Subject: [PATCH 19/20] feat(localizations): add catalan Also managed to fix some weird spanish translations. --- .../stream_chat_localizations/CHANGELOG.md | 10 + packages/stream_chat_localizations/README.md | 3 + .../lib/src/stream_chat_localizations.dart | 4 + .../lib/src/stream_chat_localizations_ca.dart | 452 ++++++++++++++++++ .../lib/src/stream_chat_localizations_es.dart | 12 +- 5 files changed, 475 insertions(+), 6 deletions(-) create mode 100644 packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md index 20c9aaef..07a266d0 100644 --- a/packages/stream_chat_localizations/CHANGELOG.md +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -1,3 +1,13 @@ +## Upcoming + +✅ Added + +* Added support for [Catalan](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart) locale. + +🔄 Changed + +* Some of the `Spanish` translations have been updated/changed for better understanding. + ## 4.0.0 🔄 Changed diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md index b55eb7ad..989c00b2 100644 --- a/packages/stream_chat_localizations/README.md +++ b/packages/stream_chat_localizations/README.md @@ -35,6 +35,7 @@ At the moment we support the following languages: - [Italian](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart) - [French](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart) - [Spanish](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart) +- [Catalan](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart) - [Japanese](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart) - [Korean](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart) - [Portuguese](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart) @@ -75,6 +76,7 @@ class MyApp extends StatelessWidget { Locale('fr'), Locale('it'), Locale('es'), + Locale('ca'), Locale('ja'), Locale('ko'), Locale('pt'), @@ -123,6 +125,7 @@ Example: fr it es + ca ja ko pt diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart index a1464ad8..62248df8 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +part 'stream_chat_localizations_ca.dart'; part 'stream_chat_localizations_de.dart'; part 'stream_chat_localizations_en.dart'; part 'stream_chat_localizations_es.dart'; @@ -28,6 +29,7 @@ const kStreamChatSupportedLanguages = { 'fr', 'it', 'es', + 'ca', 'ja', 'ko', 'pt', @@ -65,6 +67,8 @@ GlobalStreamChatLocalizations? getStreamChatTranslation(Locale locale) { return const StreamChatLocalizationsIt(); case 'es': return const StreamChatLocalizationsEs(); + case 'ca': + return const StreamChatLocalizationsCa(); case 'ja': return const StreamChatLocalizationsJa(); case 'ko': diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart new file mode 100644 index 00000000..9b7c5fc9 --- /dev/null +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart @@ -0,0 +1,452 @@ +part of 'stream_chat_localizations.dart'; + +/// The translations for Catalan (`ca`). +class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { + /// Create an instance of the translation bundle for Catalan. + const StreamChatLocalizationsCa({super.localeName = 'ca'}); + + @override + String get launchUrlError => "No s'ha pogut obrir la url"; + + @override + String get loadingUsersError => "Error de càrrega de l'usuari"; + + @override + String get noUsersLabel => 'Actualment no hi ha usuaris'; + + @override + String get retryLabel => 'Torna-ho a provar'; + + @override + String get userLastOnlineText => 'Última vegada en línia'; + + @override + String get userOnlineText => 'En línia'; + + @override + String userTypingText(Iterable users) { + if (users.isEmpty) return ''; + final first = users.first; + if (users.length == 1) { + return '${first.name} està escrivint'; + } + return '${first.name} y ${users.length - 1} estan escrivint'; + } + + @override + String get threadReplyLabel => 'Respon al fil'; + + @override + String get onlyVisibleToYouText => 'Només visible per vostè'; + + @override + String threadReplyCountText(int count) => + '$count respostes al fil'; + + @override + String attachmentsUploadProgressText({ + required int remaining, + required int total, + }) => + 'Transferència en curs $remaining/$total ...'; + + @override + String pinnedByUserText({ + required User pinnedBy, + required User currentUser, + }) { + final pinnedByCurrentUser = currentUser.id == pinnedBy.id; + if (pinnedByCurrentUser) return 'Fixat per tu'; + return 'Fixat per ${pinnedBy.name}'; + } + + @override + String get sendMessagePermissionError => + 'No tens permís per enviar missatges'; + + @override + String get emptyMessagesText => 'Actualment no hi ha missatges'; + + @override + String get genericErrorText => 'Hi ha hagut un problema'; + + @override + String get loadingMessagesError => + 'Hi ha hagut un error mentres carregava el missatge'; + + @override + String resultCountText(int count) => '$count resultats'; + + @override + String get messageDeletedText => 'Aquest missatge ha estat esborrat.'; + + @override + String get messageDeletedLabel => 'Missatge esborrat'; + + @override + String get messageReactionsLabel => 'Reaccions dels missatges'; + + @override + String get emptyChatMessagesText => 'Encara no hi ha missatges...'; + + @override + String threadSeparatorText(int replyCount) { + if (replyCount == 1) return '1 resposta'; + return '$replyCount respostes'; + } + + @override + String get connectedLabel => 'Connectat'; + + @override + String get disconnectedLabel => 'Desconnectat'; + + @override + String get reconnectingLabel => 'Reconnectant...'; + + @override + String get alsoSendAsDirectMessageLabel => + 'Enviar també com a missatge directe'; + + @override + String get addACommentOrSendLabel => 'Afegir un comentari o enviar'; + + @override + String get searchGifLabel => 'Cerca de GIFs'; + + @override + String get writeAMessageLabel => 'Escriure un missatge'; + + @override + String get instantCommandsLabel => 'Commandes instantànies'; + + @override + String fileTooLargeAfterCompressionError(double limitInMB) => + 'El fitxer és massa gran descargar-lo. ' + 'La mida màxima del fitxer és de $limitInMB MB. ' + 'Hem intentat comprimir-lo, pero ha estat suficient.'; + + @override + String fileTooLargeError(double limitInMB) => + 'El fitxer és massa gran per descargar-lo. ' + 'El límit de mida dels fitxers és de $limitInMB MB.'; + + @override + String get couldNotReadBytesFromFileError => + "No s'han pogut llegir els bytes del fitxer."; + + @override + String get addAFileLabel => 'Afegir un fitxer'; + + @override + String get photoFromCameraLabel => 'Foto de la càmera'; + + @override + String get uploadAFileLabel => 'Transferir un fitxer'; + + @override + String get uploadAPhotoLabel => 'Pujar una foto'; + + @override + String get uploadAVideoLabel => 'Pujar un vídeo'; + + @override + String get videoFromCameraLabel => 'Vídeo de la càmera'; + + @override + String get okLabel => 'Vale'; + + @override + String get somethingWentWrongError => 'Alguna cosa ha anat malament'; + + @override + String get addMoreFilesLabel => 'Afegir més fitxers'; + + @override + String get enablePhotoAndVideoAccessMessage => + "Si us plau, permeti l'accés a les seves fotos" + '\ni vídeos per a que pugui compartir-los.'; + + @override + String get allowGalleryAccessMessage => "Permetre l'accés a la galeria"; + + @override + String get flagMessageLabel => 'Reportar un missatge'; + + @override + String get flagMessageQuestion => + "¿Vol enviar una còpia d'aquest missatge a un" + '\nmoderador per una major investigació?'; + + @override + String get flagLabel => 'REPORTAR'; + + @override + String get cancelLabel => 'CANCELAR'; + + @override + String get flagMessageSuccessfulLabel => 'Missatge reportat'; + + @override + String get flagMessageSuccessfulText => + 'Aquest missatge ha estat reportat a un moderador.'; + + @override + String get deleteLabel => 'ESBORRAR'; + + @override + String get deleteMessageLabel => 'Esborrar el missatge'; + + @override + String get deleteMessageQuestion => + '¿Estàs segur de que vols esborrar aquest\nmissatge de forma permanent?'; + + @override + String get operationCouldNotBeCompletedText => + "L'operació no s'ha pogut completar."; + + @override + String get replyLabel => 'Respondre'; + + @override + String togglePinUnpinText({required bool pinned}) { + if (pinned) return 'Desfixar de la conversa'; + return 'Fixar a la conversa'; + } + + @override + String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) { + if (isDeleteFailed) return 'Reintentar esborrar el misssatge'; + return 'Esborrar el misssatge'; + } + + @override + String get copyMessageLabel => 'Copiar el misssatge'; + + @override + String get editMessageLabel => 'Editar el misssatge'; + + @override + String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) { + if (isUpdateFailed) return 'Reenviar el missatge modificat'; + return 'Reenviar'; + } + + @override + String get photosLabel => 'Fotos'; + + String _getDay(DateTime dateTime) { + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final yesterday = DateTime(now.year, now.month, now.day - 1); + + final date = DateTime(dateTime.year, dateTime.month, dateTime.day); + + if (date == today) { + return 'avui'; + } else if (date == yesterday) { + return 'ahir'; + } else { + return 'el ${Jiffy(date).MMMd}'; + } + } + + @override + String sentAtText({required DateTime date, required DateTime time}) => + '''Enviat el ${_getDay(date)} a les ${Jiffy(time.toLocal()).format('HH:mm')}'''; + + @override + String get todayLabel => 'Avui'; + + @override + String get yesterdayLabel => 'Ahir'; + + @override + String get channelIsMutedText => 'El canal està silenciat'; + + @override + String get noTitleText => 'Sense títol'; + + @override + String get letsStartChattingLabel => '¡Comencem a parlar!'; + + @override + String get sendingFirstMessageLabel => + 'Qué li sembla enviar el seu primer missatge a un amic?'; + + @override + String get startAChatLabel => 'Iniciar una conversa'; + + @override + String get loadingChannelsError => 'Error al carregar els canals'; + + @override + String get deleteConversationLabel => 'Esborrar la conversa'; + + @override + String get deleteConversationQuestion => + 'Estàs segur de que vols esborrar aquesta conversa?'; + + @override + String get streamChatLabel => 'Stream Chat'; + + @override + String get searchingForNetworkText => 'Buscant xarxa'; + + @override + String get offlineLabel => 'Sense connexió...'; + + @override + String get tryAgainLabel => 'Torna-ho a provar'; + + @override + String membersCountText(int count) { + if (count == 1) return '1 membre'; + return '$count membres'; + } + + @override + String watchersCountText(int count) { + if (count == 1) return '1 En línea'; + return '$count En línea'; + } + + @override + String get viewInfoLabel => 'Veure informació'; + + @override + String get leaveGroupLabel => 'Sortir del Grup'; + + @override + String get leaveLabel => 'SORTIR'; + + @override + String get leaveConversationLabel => 'Sortir de la conversa'; + + @override + String get leaveConversationQuestion => + "Estàs segur de que vol sortir d'aquesta conversa?"; + + @override + String get showInChatLabel => 'Mostrar al chat'; + + @override + String get saveImageLabel => 'Guardar la imatge'; + + @override + String get saveVideoLabel => 'Guardar el vídeo'; + + @override + String get uploadErrorLabel => 'ERROR DE TRANSFERENCIA'; + + @override + String get giphyLabel => 'Giphy'; + + @override + String get shuffleLabel => 'Remenar'; + + @override + String get sendLabel => 'Enviar'; + + @override + String get withText => 'amb'; + + @override + String get inText => 'a'; + + @override + String get youText => 'Vostè'; + + @override + String galleryPaginationText({ + required int currentPage, + required int totalPages, + }) => + '${currentPage + 1} de $totalPages'; + + @override + String get fileText => 'Fitxer'; + + @override + String get replyToMessageLabel => 'Respondre al missatge'; + + @override + String attachmentLimitExceedError(int limit) => + 'No és possible afegir més de $limit fitxers adjunts'; + + @override + String get viewLibrary => 'Veure llibreria'; + + @override + String get slowModeOnLabel => 'Mode lent activat'; + + @override + String get downloadLabel => 'Descarregar'; + + @override + String toggleMuteUnmuteUserText({required bool isMuted}) { + if (isMuted) { + return "Activar so de l'usuari"; + } else { + return 'Silenciar usuari'; + } + } + + @override + String toggleMuteUnmuteGroupQuestion({required bool isMuted}) { + if (isMuted) { + return "Estàs segur de que vols activar el so d'aquest grup?"; + } else { + return 'Estàs segur de que vols silenciar aquest grup?'; + } + } + + @override + String toggleMuteUnmuteUserQuestion({required bool isMuted}) { + if (isMuted) { + return "Estàs segur de que vols activar el so d'aquest usuari"; + } else { + return 'Estàs seguro de que vols silenciar aquest usuari?'; + } + } + + @override + String toggleMuteUnmuteAction({required bool isMuted}) { + if (isMuted) { + return 'ACTIVAR SO'; + } else { + return 'SILENCIAR'; + } + } + + @override + String toggleMuteUnmuteGroupText({required bool isMuted}) { + if (isMuted) { + return 'Activar so del grup'; + } else { + return 'Silenciar grup'; + } + } + + @override + String get linkDisabledDetails => + 'No es permet enviar enllaços a aquesta conversa.'; + + @override + String get linkDisabledError => 'Els enllaços estan deshabilitats'; + + @override + String unreadMessagesSeparatorText(int unreadCount) { + if (unreadCount == 1) { + return '1 missatge no llegit'; + } + return '$unreadCount missatges no llegits'; + } + + @override + String get enableFileAccessMessage => "Habiliti l'accés als fitxers" + '\nper poder compartir-los amb amics.'; + + @override + String get allowFileAccessMessage => "Permetre l'accés als fitxers"; +} diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart index 8bbf5775..40f7d52f 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart @@ -84,7 +84,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get messageDeletedLabel => 'Mensaje borrado'; @override - String get messageReactionsLabel => 'Reacciones a los mensajes'; + String get messageReactionsLabel => 'Reacciones de los mensajes'; @override String get emptyChatMessagesText => 'Todavía no hay charlas aquí...'; @@ -165,7 +165,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { @override String get enablePhotoAndVideoAccessMessage => 'Por favor, permita el acceso a sus fotos' - '\ny vídeos para que puedas compartirlos con sus amigos.'; + '\ny vídeos para que pueda compartirlos con sus amigos.'; @override String get allowGalleryAccessMessage => 'Permitir el acceso a su galería'; @@ -206,11 +206,11 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { 'La operación no pudo completarse.'; @override - String get replyLabel => 'Respuesta'; + String get replyLabel => 'Responder'; @override String togglePinUnpinText({required bool pinned}) { - if (pinned) return 'Desfijar a la conversación'; + if (pinned) return 'Desfijar de la conversación'; return 'Fijar a la conversación'; } @@ -389,14 +389,14 @@ No es posible añadir más de $limit archivos adjuntos if (isMuted) { return 'No silenciar usuario'; } else { - return 'Usuario mudo'; + return 'Silenciar usuario'; } } @override String toggleMuteUnmuteGroupQuestion({required bool isMuted}) { if (isMuted) { - return '¿Estás seguro de que quieres activar el silencio de este grupo?'; + return '¿Estás seguro de que quieres activar el sonido de este grupo?'; } else { return '¿Estás seguro de que quieres silenciar a este grupo?'; } From fbf3db25a490a86949ac0cf4049fa6f35ca806e9 Mon Sep 17 00:00:00 2001 From: Carlos GC Date: Sat, 21 Jan 2023 00:03:52 +0100 Subject: [PATCH 20/20] fix: file format --- .../lib/src/stream_chat_localizations_ca.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart index 9b7c5fc9..68b6c3cd 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart @@ -40,8 +40,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get onlyVisibleToYouText => 'Només visible per vostè'; @override - String threadReplyCountText(int count) => - '$count respostes al fil'; + String threadReplyCountText(int count) => '$count respostes al fil'; @override String attachmentsUploadProgressText({ @@ -123,13 +122,13 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { @override String fileTooLargeAfterCompressionError(double limitInMB) => 'El fitxer és massa gran descargar-lo. ' - 'La mida màxima del fitxer és de $limitInMB MB. ' - 'Hem intentat comprimir-lo, pero ha estat suficient.'; + 'La mida màxima del fitxer és de $limitInMB MB. ' + 'Hem intentat comprimir-lo, pero ha estat suficient.'; @override String fileTooLargeError(double limitInMB) => 'El fitxer és massa gran per descargar-lo. ' - 'El límit de mida dels fitxers és de $limitInMB MB.'; + 'El límit de mida dels fitxers és de $limitInMB MB.'; @override String get couldNotReadBytesFromFileError => @@ -176,7 +175,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { @override String get flagMessageQuestion => "¿Vol enviar una còpia d'aquest missatge a un" - '\nmoderador per una major investigació?'; + '\nmoderador per una major investigació?'; @override String get flagLabel => 'REPORTAR';