diff --git a/.github/workflows/pr_title.yml b/.github/workflows/pr_title.yml index 4e7c3b5f..b7d1ad9b 100644 --- a/.github/workflows/pr_title.yml +++ b/.github/workflows/pr_title.yml @@ -37,7 +37,7 @@ jobs: "llc": "packages/stream_chat", "ui": "packages/stream_chat_flutter", "core": "packages/stream_chat_flutter_core", - "localization": "packages/stream_chat_flutter_localizations", + "localization": "packages/stream_chat_localizations", "persistence": "packages/stream_chat_persistence" } env: diff --git a/docusaurus/docs/Flutter/assets/authentication_demo_app.jpg b/docusaurus/docs/Flutter/assets/authentication_demo_app.jpg new file mode 100644 index 00000000..eed57c33 Binary files /dev/null and b/docusaurus/docs/Flutter/assets/authentication_demo_app.jpg differ diff --git a/docusaurus/docs/Flutter/assets/firebase_authentication_dashboard.jpg b/docusaurus/docs/Flutter/assets/firebase_authentication_dashboard.jpg new file mode 100644 index 00000000..c3040557 Binary files /dev/null and b/docusaurus/docs/Flutter/assets/firebase_authentication_dashboard.jpg differ diff --git a/docusaurus/docs/Flutter/assets/message_reaction_theming.png b/docusaurus/docs/Flutter/assets/message_reaction_theming.png new file mode 100644 index 00000000..8cddd6ba Binary files /dev/null and b/docusaurus/docs/Flutter/assets/message_reaction_theming.png differ diff --git a/docusaurus/docs/Flutter/assets/message_rounded_avatar.png b/docusaurus/docs/Flutter/assets/message_rounded_avatar.png new file mode 100644 index 00000000..ec373344 Binary files /dev/null and b/docusaurus/docs/Flutter/assets/message_rounded_avatar.png differ diff --git a/docusaurus/docs/Flutter/assets/message_styles.png b/docusaurus/docs/Flutter/assets/message_styles.png new file mode 100644 index 00000000..db04865c Binary files /dev/null and b/docusaurus/docs/Flutter/assets/message_styles.png differ diff --git a/docusaurus/docs/Flutter/assets/message_theming.png b/docusaurus/docs/Flutter/assets/message_theming.png new file mode 100644 index 00000000..b5d28dde Binary files /dev/null and b/docusaurus/docs/Flutter/assets/message_theming.png differ diff --git a/docusaurus/docs/Flutter/assets/message_widget_actions.png b/docusaurus/docs/Flutter/assets/message_widget_actions.png new file mode 100644 index 00000000..e835a4ed Binary files /dev/null and b/docusaurus/docs/Flutter/assets/message_widget_actions.png differ diff --git a/docusaurus/docs/Flutter/assets/stream_chat_user_database.jpg b/docusaurus/docs/Flutter/assets/stream_chat_user_database.jpg new file mode 100644 index 00000000..fcfe73c8 Binary files /dev/null and b/docusaurus/docs/Flutter/assets/stream_chat_user_database.jpg differ diff --git a/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx b/docusaurus/docs/Flutter/guides/adding_push_notifications.mdx similarity index 87% rename from docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx rename to docusaurus/docs/Flutter/guides/adding_push_notifications.mdx index 36da1373..c90f6814 100644 --- a/docusaurus/docs/Flutter/guides/adding_push_notifcations.mdx +++ b/docusaurus/docs/Flutter/guides/adding_push_notifications.mdx @@ -187,8 +187,33 @@ StreamChat( As you can see we generate a local notification whenever a message.new or notification.message_new event is received. +### 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. + +For this scenario, you can also use the `flutter_local_notifications` package to show a notification. + +You need to listen for new events using `StreamChatClient.on` and handle them accordingly. + +Here we're checking if the event is a `message.new` or `notification.message_new` event, and if the message is from a different user than the current user. In that case we'll show a notification. + +```dart +client.on( + EventType.messageNew, + EventType.notificationMessageNew, +).listen((event) { + if (event.message?.user?.id == client.state.currentUser?.id) { + return; + } + showLocalNotification(event, client.state.currentUser!.id, context); +}); +``` + :::note -Using `flutter_local_notifications` is a great way to implement notifications while the is in foreground too! You can generate a local notification listening to events using the method `streamChatClient.on()` and react to the events you want. +You should also check that the channel of the message is different than 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 diff --git a/docusaurus/docs/Flutter/guides/customize_message_widget.mdx b/docusaurus/docs/Flutter/guides/customize_message_widget.mdx new file mode 100644 index 00000000..af7014fb --- /dev/null +++ b/docusaurus/docs/Flutter/guides/customize_message_widget.mdx @@ -0,0 +1,182 @@ +--- +id: customize_message_widget +sidebar_position: 11 +title: Customizing The MessageWidget +--- + +Customizing Text Messages + +### Introduction + +Every application provides a unique look and feel to their own messaging interface including and not +limited to fonts, colors, and shapes. + +This guide details how to customize the `MessageWidget` in the Stream Chat Flutter UI SDK. + +### Building Custom Messages + +This guide goes into detail about the ability to customize the `MessageWidget`. However, if you want +to customize the default `MessageWidget` in the `MessageListView` provided, you can use the `.copyWith()` method +provided inside the `messageBuilder` parameter of the `MessageListView` like this: + +```dart +MessageListView( + messageBuilder: (context, details, messageList, defaultImpl) { + // Your implementation of the message here + // E.g: return Text(details.message.text ?? ''); + }, +), +``` + +### Theming + +You can customize the `MessageWidget` using the `StreamChatTheme` class, so that you can change the +message theme at the top instead of creating your own `MessageWidget` at the lower implementation level. + +There are several things you can change in the theme including text styles and colors of various elements. + +You can also set a different theme for the user's own messages and messages received by them. + +:::note +Theming allows you to change minor factors like style while using the widget directly allows you much +more customization such as replacing a certain widget with another. Some things can only be customized +through the widget and not the theme. +::: + +Here is an example: + +```dart +StreamChatThemeData( + + /// Sets theme for user's messages + ownMessageTheme: MessageThemeData( + messageBackgroundColor: colorTheme.textHighEmphasis, + ), + + /// Sets theme for received messages + otherMessageTheme: MessageThemeData( + avatarTheme: AvatarThemeData( + borderRadius: BorderRadius.circular(8), + ), + ), + +) +``` + +![](../assets/message_theming.png) + +#### Change message text style + +The `MessageWidget` has multiple `Text` widgets that you can manipulate the styles of. The three main +are the actual message text, user name, message links, and the message timestamp. + +```dart +MessageThemeData( + messageTextStyle: TextStyle(...), + createdAtStyle: TextStyle(...), + messageAuthorStyle: TextStyle(...), + messageLinksStyle: TextStyle(...), +) +``` + +![](../assets/message_styles.png) + +#### Change avatar theme + +You can change the attributes of the avatar (if displayed) using the `avatarTheme` property. + +```dart +MessageThemeData( + avatarTheme: AvatarThemeData( + borderRadius: BorderRadius.circular(8), + ), +) +``` + +![](../assets/message_rounded_avatar.png) + +#### Changing Reaction theme + +You also customize the reactions attached to every message using the theme. + +```dart +MessageThemeData( + reactionsBackgroundColor: Colors.red, + reactionsBorderColor: Colors.redAccent, + reactionsMaskColor: Colors.pink, +), +``` + +![](../assets/message_reaction_theming.png) + +### Changing Message Actions + +When a message is long pressed, the `MessageActionsModal` is shown. + +The `MessageWidget` allows showing or hiding some options if you so choose. + +```dart +MessageWidget( + ... + showUsername = true, + showTimestamp = true, + showReactions = true, + showDeleteMessage = true, + showEditMessage = true, + showReplyMessage = true, + showThreadReplyMessage = true, + showResendMessage = true, + showCopyMessage = true, + showFlagButton = true, + showPinButton = true, + showPinHighlight = true, +), +``` + +![](../assets/message_widget_actions.png) + +### Building attachments + +The `customAttachmentBuilder` property allows you to build any kind of attachment (inbuilt or custom) +in your own way. While a separate guide is written for this, it is included here because of relevance. + +```dart +MessageListView( + messageBuilder: (context, details, messages, defaultMessage) { + return defaultMessage.copyWith( + customAttachmentBuilders: { + 'location': (context, message, attachments) { + final attachmentWidget = Image.network( + _buildMapAttachment( + attachments[0].extraData['latitude'], + attachments[0].extraData['longitude'], + ), + ); + + return wrapAttachmentWidget(context, attachmentWidget, null, true, BorderRadius.circular(8.0)); + } + }, + ); + }, +), +``` + +### Widget Builders + +Some parameters allow you to construct your own widget in place of some elements in the `MessageWidget`. + +These are: +* `userAvatarBuilder` : Allows user to substitute their own widget in place of the user avatar. +* `editMessageInputBuilder` : Allows user to substitute their own widget in place of the input in edit mode. +* `textBuilder` : Allows user to substitute their own widget in place of the text. +* `bottomRowBuilder` : Allows user to substitute their own widget in the bottom of the message when not deleted. +* `deletedBottomRowBuilder` : Allows user to substitute their own widget in the bottom of the message when deleted. + +```dart +MessageWidget( + ... + textBuilder: (context, message) { + // Add your own text implementation here. + }, +), +``` diff --git a/docusaurus/docs/Flutter/guides/user_token_generation_with_firebase_auth.mdx b/docusaurus/docs/Flutter/guides/user_token_generation_with_firebase_auth.mdx new file mode 100644 index 00000000..56192f53 --- /dev/null +++ b/docusaurus/docs/Flutter/guides/user_token_generation_with_firebase_auth.mdx @@ -0,0 +1,448 @@ +--- +id: token_generation_with_firebase +sidebar_position: 5 +title: User Token Generation With Firebase Auth and Cloud Functions +--- + +Securely generate Stream Chat user tokens using Firebase Authentication and Cloud Functions. + +:::note +This guide assumes that you are familiar with Firebase Authentication and Cloud Functions for Flutter and using the Flutter Stream Chat SDK. +::: + +### Introduction + +In this guide, you'll explore how you can use Firebase Auth as an authentication provider and create Firebase Cloud functions to securely +generate Stream Chat user tokens. + +You will use Stream's [NodeJS client](https://getstream.io/chat/docs/node/?language=javascript) for Stream account creation and +token generation, and [Flutter Cloud Functions for Firebase](https://firebase.flutter.dev/docs/functions/overview) to invoke the cloud functions +from your Flutter app. + +Stream supports several different [backend clients](https://getstream.io/chat/sdk/#backend-clients) to integrate with your server. This guide only shows an easy way to integrate Stream Chat authentication using Firebase and Flutter. + +### Flutter Firebase + +See the [Flutter Firebase getting started](https://firebase.flutter.dev/docs/overview) docs for setup and installation instructions. + +You will also need to add the [Flutter Firebase Authentication](https://firebase.flutter.dev/docs/auth/overview), and [Flutter Firebase Cloud Functions](https://firebase.flutter.dev/docs/functions/overview) packages to your app. Depending on the platform that you target, there may be specific configurations that you need to do. + +#### Starting Code + +The following code shows a basic application with **FirebaseAuth** and **FirebaseFunctions**. + +You will extend this later to execute cloud functions. + +```dart +import 'package:cloud_functions/cloud_functions.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'dart:async'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Auth(), + ), + ); + } +} + +class Auth extends StatefulWidget { + Auth({Key? key}) : super(key: key); + + @override + _AuthState createState() => _AuthState(); +} + +class _AuthState extends State { + late FirebaseAuth auth; + late FirebaseFunctions functions; + + @override + void initState() { + super.initState(); + auth = FirebaseAuth.instance; + functions = FirebaseFunctions.instance; + } + + final email = 'test@getstream.io'; + final password = 'password'; + + Future createAccount() async { + // Create Firebase account + await auth.createUserWithEmailAndPassword(email: email, password: password); + print('Firebase account created'); + } + + Future signIn() async { + // Sign in with Firebase + await auth.signInWithEmailAndPassword(email: email, password: password); + print('Firebase signed in'); + } + + Future signOut() async { + // Revoke Stream chat token. + final callable = functions.httpsCallable('revokeStreamUserToken'); + await callable(); + print('Stream user token revoked'); + } + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AuthenticationState( + streamUser: auth.authStateChanges(), + ), + ElevatedButton( + onPressed: createAccount, + child: Text('Create account'), + ), + ElevatedButton( + onPressed: signIn, + child: Text('Sign in'), + ), + ElevatedButton( + onPressed: signOut, + child: Text('Sign out'), + ), + ], + ), + ); + } +} + +class AuthenticationState extends StatelessWidget { + const AuthenticationState({ + Key? key, + required this.streamUser, + }) : super(key: key); + + final Stream streamUser; + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: streamUser, + builder: (context, snapshot) { + if (snapshot.hasData) { + return (snapshot.data != null) + ? Text('Authenticated') + : Text('Not Authenticated'); + } + return Text('Not Authenticated'); + }, + ); + } +} + +``` + +Running the above will give this: + +![](../assets/authentication_demo_app.jpg) + +The `Auth` widget handles all of the authentication logic. It initializes a `FirebaseAuth.instance` and uses that +in the `createAccount`, `signIn` and `signOut` methods. There is a button to envoke each of these methods. + +The `FirebaseFunctions.instance` will be used later in this guide. + +The `AuthenticationState` widget listens to `auth.authStateChanges()` to display a message +indicating if a user is authenticated. + +### Firebase Cloud Functions + +Firebase Cloud Functions allows you to extend Firebase with custom operations that an event can trigger: +- **Internal event**: For example, when creating a new Firebase account this is automatically triggered. +- **External event**: For example, directly calling a cloud function from your Flutter application. + +To set up your local environment to deploy cloud functions, please see the +[Cloud Functions getting started](https://firebase.flutter.dev/docs/overview) docs. + +After initializing your project with cloud functions, you should have a **functions** folder in your project, including a `package.json` file. + +There should be two dependencies already added, **firebase-admin** and **firebase-functions**. You will also need to add the **stream-chat** dependency. + +Navigate to the **functions** folder and run `npm install stream-chat --save-prod`. + +This will install the node module and add it as a dependency to `package.json`. + +Now open `index.js` and add the following (this is the complete example): + +```js +const StreamChat = require('stream-chat').StreamChat; +const functions = require("firebase-functions"); +const admin = require("firebase-admin"); + +admin.initializeApp(); + +const serverClient = StreamChat.getInstance(functions.config().stream.key, functions.config().stream.secret); + + +// When a user is deleted from Firebase their associated Stream account is also deleted. +exports.deleteStreamUser = functions.auth.user().onDelete((user, context) => { + return serverClient.deleteUser(user.uid); +}); + +// Create a Stream user and return auth token. +exports.createStreamUserAndGetToken = functions.https.onCall(async (data, context) => { + // Checking that the user is authenticated. + if (!context.auth) { + // Throwing an HttpsError so that the client gets the error details. + throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' + + 'while authenticated.'); + } else { + try { + // Create user using the serverClient. + await serverClient.upsertUser({ + id: context.auth.uid, + name: context.auth.token.name, + email: context.auth.token.email, + image: context.auth.token.image, + }); + + /// Create and return user auth token. + return serverClient.createToken(context.auth.uid); + } catch (err) { + console.error(`Unable to create user with ID ${context.auth.uid} on Stream. Error ${err}`); + // Throwing an HttpsError so that the client gets the error details. + throw new functions.https.HttpsError('aborted', "Could not create Stream user"); + } + } +}); + +// Get Stream user token. +exports.getStreamUserToken = functions.https.onCall((data, context) => { + // Checking that the user is authenticated. + if (!context.auth) { + // Throwing an HttpsError so that the client gets the error details. + throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' + + 'while authenticated.'); + } else { + try { + return serverClient.createToken(context.auth.uid); + } catch (err) { + console.error(`Unable to get user token with ID ${context.auth.uid} on Stream. Error ${err}`); + // Throwing an HttpsError so that the client gets the error details. + throw new functions.https.HttpsError('aborted', "Could not get Stream user"); + } + } +}); + +// Revoke the authenticated user's Stream chat token. +exports.revokeStreamUserToken = functions.https.onCall((data, context) => { + // Checking that the user is authenticated. + if (!context.auth) { + // Throwing an HttpsError so that the client gets the error details. + throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' + + 'while authenticated.'); + } else { + try { + return serverClient.revokeUserToken(context.auth.uid); + } catch (err) { + console.error(`Unable to revoke user token with ID ${context.auth.uid} on Stream. Error ${err}`); + // Throwing an HttpsError so that the client gets the error details. + throw new functions.https.HttpsError('aborted', "Could not get Stream user"); + } + } +}); + +``` + +First, you import the necessary packages and call `admin.initializeApp();` to set up Firebase cloud functions. + +Next, you initialize the **StreamChat** server client by calling `StreamChat.getInstance`. This function requires your Stream app's +**token** and **secret**. You can get this from the Stream Dashboard for your app. + +Set these values as environment data on Firebase Functions. + +```bash + firebase functions:config:set stream.key="app-key" stream.secret="app-secret" +``` + +*Replace **app-key** and **app-secret** with the values for your Stream app.* + +This creates an object of **stream** with properties **key** and **secret**. To access this environment +data use `functions.config().stream.key` and `functions.config().stream.secret`. + +See the [Firebase environment configuration](https://firebase.google.com/docs/functions/config-env) +documentation for additional information. + +To deploy these functions to Firebase, run: + +```bash +firebase deploy --only functions +``` + +### Create a Stream User and Get the User's Token + +In the `createStreamUserAndGetToken` cloud function you create an `onCall` HTTPS handler, which exposes +a cloud function that can be envoked from your Flutter app. + +```js +// Create a Stream user and return auth token. +exports.createStreamUserAndGetToken = functions.https.onCall(async (data, context) => { + // Checking that the user is authenticated. + if (!context.auth) { + // Throwing an HttpsError so that the client gets the error details. + throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' + + 'while authenticated.'); + } else { + try { + // Create user using the serverClient. + await serverClient.upsertUser({ + id: context.auth.uid, + name: context.auth.token.name, + email: context.auth.token.email, + image: context.auth.token.image, + }); + + /// Create and return user auth token. + return serverClient.createToken(context.auth.uid); + } catch (err) { + console.error(`Unable to create user with ID ${context.auth.uid} on Stream. Error ${err}`); + // Throwing an HttpsError so that the client gets the error details. + throw new functions.https.HttpsError('aborted', "Could not create Stream user"); + } + } +}); +``` + +This function first does a check to see that the client that calls it is authenticated, +by ensuring that `context.auth` is not null. If it is null, then it throws an `HttpsError` with a descriptive +message. This error can be caught in your Flutter application. + +If the caller is authenticated the function proceeds to use the `serverClient` to create a new Stream Chat +user by calling the `upsertUser` method and passing in some user data. It uses the authenticated caller's **uid** as an **id**. + +After the user is created it generates a token for that user. This token is then returned to the caller. + +To call this from Flutter, you will need to use the `cloud_functions` package. + +Update the **createAccount** method in your Flutter code to the following: + +```dart +Future createAccount() async { + // Create Firebase account + await auth.createUserWithEmailAndPassword(email: email, password: password); + print('Firebase account created'); + + // Create Stream user and get token + final callable = functions.httpsCallable('createStreamUserAndGetToken'); + final results = await callable(); + print('Stream account created, token: ${results.data}'); +} +``` + +Calling this method will do the following: +1. Create a new Firebase User and authenticate that user. +2. Call the `createStreamUserAndGetToken` cloud function and get the Stream user token for the authenticated user. + +As you can see, calling a cloud function is easy and will also send all the necessary user authentication information (such as the UID) +in the request. + +Once you have the Stream user token, you can authenticate your Stream Chat user as you normally would. + +Please see our [initialization documention](https://getstream.io/chat/docs/flutter-dart/init_and_users/?language=dart) for more information. + +As you can see below, the User ID matches on both Firebase's and Stream's user database. + +##### Firebase Authentication Database + +![Firebase Auth Database with new user created](../assets/firebase_authentication_dashboard.jpg) + +##### Stream Chat User Database + +![Stream chat user database new account created](../assets/stream_chat_user_database.jpg) + + +### Get the Stream User Token + +The `getStreamUserToken` cloud function is very similar to the `createStreamUserAndGetToken` function. The only difference is +that it only creates a user token and does not create a new user account on Stream. + +Update the **signIn** method in your Flutter code to the following: + +```dart +Future signIn() async { + // Sign in with Firebase + await auth.signInWithEmailAndPassword(email: email, password: password); + print('Firebase signed in'); + + // Get Stream user token + final callable = functions.httpsCallable('getStreamUserToken'); + final results = await callable(); + print('Stream user token retrieved: ${results.data}'); +} +``` + +Calling this method will do the following: +1. Sign in using Firebase Auth. +2. Call the `getStreamUserToken` cloud function to get a Stream user token. + +:::note +The user needs to be authenticated to call this cloud function. Otherwise, the function will throw +the **failed-precondition** error that you specified. +::: + +### Revoke Stream User Token + +You may also want to revoke the Stream user token if you sign out from Firebase. + +Update the `signOut` method in your Flutter code to the following: + +```dart +Future signOut() async { + // Revoke Stream user token. + final callable = functions.httpsCallable('revokeStreamUserToken'); + await callable(); + print('Stream user token revoked'); + + // Sign out Firebase. + await auth.signOut(); + print('Firebase signed out'); +} +``` +:::note +Call the cloud function before signing out from Firebase. +::: + +### Delete Stream User + +When deleting a Firebase user account, it would make sense also to delete the +associated Stream user account. + +The cloud function looks like this: + +```js +// When a user is deleted from Firebase their associated Stream account is also deleted. +exports.deleteStreamUser = functions.auth.user().onDelete((user, context) => { + return serverClient.deleteUser(user.uid); +}); +``` + +In this function, you are listening to delete events on Firebase auth. When an account is deleted, this function will be triggered, and you can get the +user's **uid** and call the `deleteUser` method on the `serverClient`. + +This is not an external cloud function; it can only be triggered when an +account is deleted. + +### Conclusion + +In this guide, you have seen how to securely create Stream Chat tokens using +Firebase Authentication and Cloud Functions. + +The principles shown in this guide can be applied to your preferred authentication +provider and cloud architecture of choice. \ No newline at end of file diff --git a/melos.yaml b/melos.yaml index b645deb0..6fee8f6f 100644 --- a/melos.yaml +++ b/melos.yaml @@ -69,6 +69,12 @@ scripts: select-package: dir-exists: coverage + docs: + run: | + npm install -g https://github.com/GetStream/stream-chat-docusaurus-cli && + npx stream-chat-docusaurus -i -s + description: Runs the docusaurus documentation locally. + environment: sdk: '>=2.12.0 <3.0.0' flutter: '>=1.22.4 <2.0.0' \ No newline at end of file diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index f136ac5d..67a80e17 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.2.1 + +๐Ÿž Fixed + +- Fixed unread indicator not updating correctly +- Fix `channel.show` not working because of null body + ## 2.2.0 ๐Ÿž Fixed diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 999dfb0b..c2c40018 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1488,7 +1488,7 @@ class ChannelClientState { _listenMemberRemoved(); - _computeInitialUnread(); + _computeUnread(); _startCleaning(); @@ -1512,11 +1512,11 @@ class ChannelClientState { final _subscriptions = []; - void _computeInitialUnread() { + void _computeUnread() { final userRead = channelState.read.firstWhereOrNull( (r) => r.user.id == _channel._client.state.currentUser?.id, ); - if (userRead != null) { + if (userRead != null && userRead.unreadMessages > 0) { unreadCount = userRead.unreadMessages; } } @@ -1934,6 +1934,8 @@ class ChannelClientState { read: newReads, pinnedMessages: updatedState.pinnedMessages, ); + + _computeUnread(); } int _sortByCreatedAt(Message a, Message b) => diff --git a/packages/stream_chat/lib/src/core/api/channel_api.dart b/packages/stream_chat/lib/src/core/api/channel_api.dart index 68f0d1c3..619e0980 100644 --- a/packages/stream_chat/lib/src/core/api/channel_api.dart +++ b/packages/stream_chat/lib/src/core/api/channel_api.dart @@ -292,6 +292,7 @@ class ChannelApi { ) async { final response = await _client.post( '${_getChannelUrl(channelId, channelType)}/show', + data: {}, ); return EmptyResponse.fromJson(response.data); } diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index a4490d5d..b208e15c 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 = '2.2.0'; +const PACKAGE_VERSION = '2.2.1'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index ede419ab..a07606ba 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: 2.2.0 +version: 2.2.1 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/api/channel_api_test.dart b/packages/stream_chat/test/src/core/api/channel_api_test.dart index 9110845d..e427ff2e 100644 --- a/packages/stream_chat/test/src/core/api/channel_api_test.dart +++ b/packages/stream_chat/test/src/core/api/channel_api_test.dart @@ -550,14 +550,21 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/show'; - when(() => client.post(path)).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post( + path, + data: {}, + )) + .thenAnswer( + (_) async => successResponse(path, data: {})); final res = await channelApi.showChannel(channelId, channelType); expect(res, isNotNull); - verify(() => client.post(path)).called(1); + verify(() => client.post( + path, + data: {}, + )).called(1); verifyNoMoreInteractions(client); }); diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 91ad7f1d..02e2b214 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.2.1 + +- Updated `stream_chat_flutter_core` dependency to 2.2.1 + ## 2.2.0 โœ… Added diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index dc3bde46..907e9a7f 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: 2.2.0 +version: 2.2.1 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -37,7 +37,7 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 shimmer: ^2.0.0 - stream_chat_flutter_core: ^2.2.0 + stream_chat_flutter_core: ^2.2.1 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index a568d88e..1378ced0 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.2.1 + +- Updated `stream_chat` dependency to 2.2.1 + ## 2.2.0 ๐Ÿ›‘๏ธ Breaking Changes from `2.1.1` diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index bd0eea6f..85b57124 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: 2.2.0 +version: 2.2.1 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -16,7 +16,7 @@ dependencies: sdk: flutter meta: ^1.3.0 rxdart: ^0.27.0 - stream_chat: ^2.2.0 + stream_chat: ^2.2.1 dev_dependencies: fake_async: ^1.2.0 diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md index f5757a28..d8328475 100644 --- a/packages/stream_chat_localizations/CHANGELOG.md +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +๐Ÿž Fixed + +* Fixed typos in `Italian` translations. + ## 1.1.0 โœ… Added diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart index 64e96dc0..e60d4afe 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -255,7 +255,7 @@ Il file รจ troppo grande per essere caricato. Il limite รจ di $limitInMB MB.'''; String get yesterdayLabel => 'Ieri'; @override - String get channelIsMutedText => 'Il canale รจ mutato'; + String get channelIsMutedText => 'Il canale รจ silenziato'; @override String get noTitleText => 'Nessun titolo'; @@ -274,7 +274,7 @@ Il file รจ troppo grande per essere caricato. Il limite รจ di $limitInMB MB.'''; String get loadingChannelsError => 'Errore durante il caricamento dei canali'; @override - String get deleteConversationLabel => 'Elemina conversazione'; + String get deleteConversationLabel => 'Elimina conversazione'; @override String get deleteConversationQuestion =>