diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
deleted file mode 100644
index 14f9a2d8..00000000
--- a/.github/ISSUE_TEMPLATE/bug-report.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: ''
-labels: bug
-assignees: ''
-
----
-
-**Describe the bug**
-A clear and concise description of what the bug is.
-
-**What package are you using? What version?**
-
-**What platform is it about?**
-- [ ] Android
-- [ ] iOS
-- [ ] Web
-- [ ] Windows
-- [ ] MacOS
-- [ ] Linux
-
-**To Reproduce**
-Steps to reproduce the behavior:
-1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
-4. See error
-
-**Expected behavior**
-A clear and concise description of what you expected to happen.
-
-**Screenshots**
-If applicable, add screenshots to help explain your problem.
-
-**Logs **
-Run `flutter analyze` and attach any output of that command below.
-If there are any analysis errors, try resolving them before filing this issue.
-
-Paste the output of running `flutter doctor -v` here.
-
-**Additional context**
-Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
new file mode 100644
index 00000000..64146824
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -0,0 +1,98 @@
+name: Bug Report
+description: Create a report to help us improve
+labels: [bug]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report!
+ - type: dropdown
+ id: packages
+ attributes:
+ label: Which packages are you using?
+ description: You may select more than one.
+ multiple: true
+ options:
+ - stream_chat
+ - stream_chat_flutter
+ - stream_chat_flutter_core
+ - stream_chat_persistance
+ - stream_chat_localizations
+ validations:
+ required: true
+ - type: dropdown
+ id: platforms
+ attributes:
+ label: On what platforms did you experience the issue?
+ description: You may select more than one.
+ multiple: true
+ options:
+ - iOS
+ - Android
+ - Web
+ - Windows
+ - MacOS
+ - Linux
+ validations:
+ required: true
+ - type: textarea
+ id: version
+ attributes:
+ label: What version are you using?
+ description: Please specify the package names and versions
+ placeholder: package - version
+ validations:
+ required: true
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: Also, what did you expect to happen?
+ placeholder: Description of the bug and what was expected.
+ validations:
+ required: true
+ - type: textarea
+ id: repro-steps
+ attributes:
+ label: Steps to reproduce
+ description: How do you trigger this bug? Please walk us through it step by step.
+ value: |
+ 1. Go to '...'
+ 2. Click on '...'
+ 3. Scroll down to '...'
+ ...
+ render: bash
+ validations:
+ required: true
+ - type: textarea
+ id: reproduce
+ attributes:
+ label: Supporting info to reproduce
+ description: Please add any relevant code, screenshots and info needed to reproduce this issue.
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ render: shell
+ - type: textarea
+ id: flutter-analyze
+ attributes:
+ label: Flutter analyze output
+ description: Paste the output of `flutter analyze` here.
+ placeholder: If there are any analysis errors, try resolving them before filing this issue.
+ render: shell
+ - type: textarea
+ id: flutter-doctor
+ attributes:
+ label: Flutter doctor output
+ description: Paste the output of `flutter doctor -v` here.
+ render: shell
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/GetStream/stream-chat-flutter/blob/develop/CODE_OF_CONDUCT.md)
+ options:
+ - label: "I agree to follow this project's Code of Conduct"
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 11fc491e..00000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-title: ''
-labels: enhancement
-assignees: ''
-
----
-
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
-
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
-
-**Additional context**
-Add any other context or screenshots about the feature request here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml
new file mode 100644
index 00000000..997171d2
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yaml
@@ -0,0 +1,66 @@
+name: Feature Request
+description: Suggest an idea for this project
+labels: [enhancement]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to help us improve!
+ - type: dropdown
+ id: packages
+ attributes:
+ label: Please select which package this feature is related to
+ description: You may select more than one.
+ multiple: true
+ options:
+ - stream_chat
+ - stream_chat_flutter
+ - stream_chat_flutter_core
+ - stream_chat_persistance
+ - stream_chat_localizations
+ validations:
+ required: true
+ - type: dropdown
+ id: platforms
+ attributes:
+ label: Which platforms would this feature impact?
+ description: You may select more than one.
+ multiple: true
+ options:
+ - iOS
+ - Android
+ - Web
+ - Windows
+ - MacOS
+ - Linux
+ - type: textarea
+ id: problem
+ attributes:
+ label: Is your feature request related to a problem?
+ description: A clear description of what the problem is.
+ placeholder: "Example: I'm always frustrated when [...]"
+ - type: textarea
+ id: solution
+ attributes:
+ label: "Describe the solution that you'd like"
+ description: A clear description of what you want to happen.
+ placeholder: "Example: When clicking this I want that."
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: "Describe alternatives that you have considered"
+ description: "A clear description of any alternative solutions or features you've considered."
+ placeholder: "Example: Instead of this it should do that."
+ - type: textarea
+ id: additional
+ attributes:
+ label: "Additional context"
+ description: "Add any other context or screenshots about the feature request here."
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/GetStream/stream-chat-flutter/blob/develop/CODE_OF_CONDUCT.md)
+ options:
+ - label: "I agree to follow this project's Code of Conduct"
+ required: true
diff --git a/.github/workflows/pr_title.yml b/.github/workflows/pr_title.yml
index eddb2ac1..b7d1ad9b 100644
--- a/.github/workflows/pr_title.yml
+++ b/.github/workflows/pr_title.yml
@@ -1,4 +1,4 @@
-name: 'PR Title is Conventional'
+name: 'PR is Conventional and Semantic'
on:
pull_request_target:
types:
@@ -9,7 +9,7 @@ on:
- develop
jobs:
- main:
+ conventional_pr_title:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v3.4.0
@@ -25,3 +25,20 @@ jobs:
requireScope: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ semantic_changelog_update:
+ needs: conventional_pr_title # Trigger after the [conventional_pr_title] completes
+ runs-on: ubuntu-latest
+ steps:
+ - uses: GetStream/verify-semantic-changelog-update@main
+ with:
+ scopes: |
+ {
+ "llc": "packages/stream_chat",
+ "ui": "packages/stream_chat_flutter",
+ "core": "packages/stream_chat_flutter_core",
+ "localization": "packages/stream_chat_localizations",
+ "persistence": "packages/stream_chat_persistence"
+ }
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0faad799..3bb5a923 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -52,6 +52,8 @@ Stream's Flutter code is kept in a single mono-repository consisting of multiple
`stream_chat_persistence` - This package provides a persistence client for fetching and saving chat data locally. Stream Chat Persistence uses Moor as a disk cache.
+`stream_chat_localizations` - This package provides a set of localizations for the SDK.
+
### Local Setup
Congratulations! 🎉. You've successfully cloned our repo, and you are ready to make your first contribution. Before you can start making code changes, there are a few things to configure.
diff --git a/README.md b/README.md
index 6758b189..62358d57 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ This repository contains code for our [Dart](https://dart.dev/) and [Flutter](ht
Stream allows developers to rapidly deploy scalable feeds and chat messaging with an industry leading 99.999% uptime SLA guarantee.
## Sample apps and demos
-Our team maintains a dedicated repository for fully-fledged sample applications and demos. Consider checking out [GetStream/flutter-samples](https://github.com/GetStream/flutter-samples) to learn more or get started by looking at our latest [Stream Chat demo](https://github.com/GetStream/flutter-samples/tree/main/stream_chat_v1).
+Our team maintains a dedicated repository for fully-fledged sample applications and demos. Consider checking out [GetStream/flutter-samples](https://github.com/GetStream/flutter-samples) to learn more or get started by looking at our latest [Stream Chat demo](https://github.com/GetStream/flutter-samples/tree/main/packages/stream_chat_v1).
## Free for Makers
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/basics/introduction.mdx b/docusaurus/docs/Flutter/basics/introduction.mdx
index 756bd33f..99170774 100644
--- a/docusaurus/docs/Flutter/basics/introduction.mdx
+++ b/docusaurus/docs/Flutter/basics/introduction.mdx
@@ -8,13 +8,14 @@ Exploring The Basics Of Stream Chat

-Stream Chat is a service that helps you easily build a full chat experience in your Flutter (and more) apps.
+Stream Chat is a service that helps you easily build a full chat experience in your Flutter apps.
+We also support a variety of other SDKs.
This section of the documentation focuses on our Flutter SDK which helps you easily
-ship high quality messaging experiences in apps and programs built with the [Flutter toolkit made
-by Google](https://flutter.dev).
+ship high quality messaging experiences in apps and programs built with the [Flutter toolkit
+made by Google](https://flutter.dev).
-The Stream Chat Flutter SDK comprises of four different packages to choose from ranging from ones
+The Stream Chat Flutter SDK comprises five different packages to choose from, ranging from ones
giving you complete control to ones that give you a rich out-of-the-box chat experience.
The packages that make up the Stream Chat SDK are:
@@ -30,28 +31,28 @@ reusable and customisable UI components.
saving chat data locally.
5. Localizations (stream_chat_localizations): provides a set of localizations for the SDK.
-We recommend building prototypes using the full UI package since it contains UI widgets already
-integrated with Stream's API. [stream_chat_flutter](https://pub.dev/packages/stream_chat_flutter)
-is the fastest way to get up and running using Stream chat in your app.
+We recommend building prototypes using the full UI package, [stream_chat_flutter](https://pub.dev/packages/stream_chat_flutter),
+since it contains UI widgets already integrated with Stream's API. It is the fastest way to get up
+and running using Stream chat in your app.
The Flutter SDK enables you to build any type of chat or messaging experience for Android, iOS, Web
and Desktop.
If you're building a very custom UI and would prefer a more lean package,
-our [core package](https://pub.dev/packages/stream_chat_flutter) will be suited to this use case. Core allows you to build custom,
-expressive UIs while retaining the benefits of our full Flutter SDK.
-APIs for accessing and controlling users, sending messages, etc are seamlessly integrated into
-this package and accessible via providers and builders.
+[stream_chat_flutter_core](https://pub.dev/packages/stream_chat_flutter_core) will be suited to this
+use case. Core allows you to build custom, expressive UIs while retaining the benefits of our full
+Flutter SDK. APIs for accessing and controlling users, sending messages, and so forth are seamlessly integrated
+into this package and accessible via providers and builders.
Before going into the docs, let's take a small detour to look at how the elements of Stream Chat are structured.
### Basic Structure
-There are two core elements in chat, Users and Channels.
+There are two core elements in chat, Users and Channels.
Channels are groups of one or more users that can message each other.
In an app, you need to have a user connected to query channels.
-There is no specific distinction between a chat between two people and a group chat,
+There is no specific distinction between a chat with only two people and a group chat,
but there is a way to create a unique chat between a certain number of people by creating a distinct channel.

@@ -68,7 +69,7 @@ While this is a simplistic overview of the service, the Flutter SDK handles the
Before reading the docs, consider trying our [online API tour](https://getstream.io/chat/get_started/),
it is a nice way to learn how the API works.
-It's in-browser so Javascript-based but the ideas are pretty much the same as Dart.
+It's in-browser so you'll need to use Javascript but the core conceps are pretty much the same as Dart.
You may also like to look at the [Flutter tutorial](https://getstream.io/chat/flutter/tutorial/)
which focuses on using the UI package to get Stream Chat integrated into a Flutter app.
diff --git a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx
index fb1f7bbe..9fe7f6aa 100644
--- a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx
+++ b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx
@@ -146,21 +146,25 @@ Next, we build the Static Maps URL (Add your API key before using the code snipp
}
```
-And then modify the MessageListView and tell it how to build a location attachment:
+And then modify the `MessageListView` and tell it how to build a location attachment, using the `messageBuilder` property and copying the default message implementation overriding the `customAttachmentBuilders` property:
```dart
MessageListView(
- customAttachmentBuilders: {
- 'location': (context, message, attachments) {
- var attachmentWidget = Image.network(
- _buildMapAttachment(
- attachments[0].extraData['latitude'],
- attachments[0].extraData['longitude'],
- ),
- );
+ 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));
- }
+ return wrapAttachmentWidget(context, attachmentWidget, null, true, BorderRadius.circular(8.0));
+ }
+ },
+ );
},
),
```
diff --git a/docusaurus/docs/Flutter/guides/adding_local_data_persistence.mdx b/docusaurus/docs/Flutter/guides/adding_local_data_persistence.mdx
new file mode 100644
index 00000000..e6283ca0
--- /dev/null
+++ b/docusaurus/docs/Flutter/guides/adding_local_data_persistence.mdx
@@ -0,0 +1,76 @@
+---
+id: adding_local_data_persistence
+sidebar_position: 9
+title: Adding Local Data Persistence
+---
+
+Adding Local Data Persistence
+
+### Introduction
+
+Most messaging apps need to work regardless of whether the app is currently connected to the internet.
+Local data persistence stores the fetched data from the backend on a local SQLite database using the
+moor package in Flutter. All packages in the SDK can use local data persistence to store messages
+across multiple platforms.
+
+### Implementation
+
+To add data persistence you can extend the class ChatPersistenceClient and pass an instance to the StreamChatClient.
+
+```dart
+class CustomChatPersistentClient extends ChatPersistenceClient {
+...
+}
+
+final client = StreamChatClient(
+ apiKey ?? kDefaultStreamApiKey,
+ logLevel: Level.INFO,
+)..chatPersistenceClient = CustomChatPersistentClient();
+```
+
+We provide an official persistent client in the [stream_chat_persistence](https://pub.dev/packages/stream_chat_persistence)
+package that works using the library [moor](https://moor.simonbinder.eu), an SQLite ORM.
+
+Add this to your package's `pubspec.yaml` file, using the latest version.
+
+```yaml
+dependencies:
+ stream_chat_persistence: ^latest_version
+```
+
+You should then run `flutter packages get`
+
+The usage is pretty simple.
+
+1. Create a new instance of `StreamChatPersistenceClient` providing `logLevel` and `connectionMode`
+
+```dart
+final chatPersistentClient = StreamChatPersistenceClient(
+ logLevel: Level.INFO,
+ connectionMode: ConnectionMode.background,
+);
+```
+
+2. Pass the instance to the official `StreamChatClient`
+
+```dart
+ final client = StreamChatClient(
+ apiKey ?? kDefaultStreamApiKey,
+ logLevel: Level.INFO,
+ )..chatPersistenceClient = chatPersistentClient;
+```
+
+And you are ready to go...
+
+Note that passing `ConnectionMode.background` the database uses a background isolate to unblock the main thread.
+The `StreamChatClient` uses the `chatPersistentClient` to synchronize the database with the newest
+information every time it receives new data about channels/messages/users.
+
+### Multi-user
+
+The DB file is named after the `userId`, so if you instantiate a client using a different `userId` you will use a different database.
+Calling `client.disconnectUser(flushChatPersistence: true)` flushes all current database data.
+
+### Updating/deleting/sending a message while offline
+
+The information about the action is saved in offline storage. When the client returns online, everything is retried.
diff --git a/docusaurus/docs/Flutter/guides/adding_localization.mdx b/docusaurus/docs/Flutter/guides/adding_localization.mdx
index 37f7299d..c61f26c7 100644
--- a/docusaurus/docs/Flutter/guides/adding_localization.mdx
+++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx
@@ -1,7 +1,7 @@
---
id: adding_localization
sidebar_position: 2
-title: Adding Localization
+title: Adding Localization (l10n) / Internationalization (i18n)
---
Adding Localization To UI Widgets
@@ -14,10 +14,14 @@ We have a dedicated package for adding localization to our UI widgets. It's call
## What is Localization?
-If you deploy your app to users who speak another language, you'll need to internationalize (localize) it. That means you need to write the app in a way that makes it possible to localize values like text and layouts for each language or locale that the app supports. For more information, see the [Flutter documentation](https://flutter.dev/docs/development/accessibility-and-localization/**internationalization**).
+If you deploy your app to users who speak another language, you'll need to internationalize (localize) it. That means you need to write the app in a way that makes it possible to localize values like text and layouts for each language or locale that the app supports. For more information, see the [Flutter documentation](https://flutter.dev/docs/development/accessibility-and-localization/internationalization).
What this package allows you to do is to provide localized strings for the Stream chat widgets. For example, depending on the application locale, the Stream Chat widgets will display the appropriate language. The locale will be set automatically, based on system preferences, or you could set it programmatically in your app. The package supports several different languages, with more to be added. The package allows you to override any supported language or add a new language that isn't supported.
+:::note
+If you want to translate messages, or enable automatic translation, please see the [Translation documentation](https://getstream.io/chat/docs/flutter-dart/translation/?language=dart).
+:::
+
### Supported languages
At the moment we support the following languages:
@@ -26,7 +30,8 @@ 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)
-
+- [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)
More languages will be added in the future. Feel free to [contribute](https://github.com/GetStream/stream-chat-flutter/blob/master/CONTRIBUTING.md) to add more languages.
### Add dependency
@@ -64,6 +69,8 @@ class MyApp extends StatelessWidget {
Locale('fr'),
Locale('it'),
Locale('es'),
+ Locale('ja'),
+ Locale('ko'),
],
// Add GlobalStreamChatLocalizations.delegates
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
@@ -133,6 +140,8 @@ Here is an example of how that would look like:
Locale('fr'),
Locale('it'),
Locale('es'),
+ Locale('ja'),
+ Locale('ko'),
],
// locales are the locales of the device
// supportedLocales are the app supported locales
@@ -175,6 +184,8 @@ Example:
nb
fr
it
- es
+ es
+ ja
+ ko
```
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),
+ ),
+ ),
+
+)
+```
+
+
+
+#### 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(...),
+)
+```
+
+
+
+#### 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),
+ ),
+)
+```
+
+
+
+#### 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,
+),
+```
+
+
+
+### 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,
+),
+```
+
+
+
+### 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/understanding_filters.mdx b/docusaurus/docs/Flutter/guides/understanding_filters.mdx
new file mode 100644
index 00000000..9d169a80
--- /dev/null
+++ b/docusaurus/docs/Flutter/guides/understanding_filters.mdx
@@ -0,0 +1,143 @@
+---
+id: understanding_filters
+sidebar_position: 10
+title: Understanding Filters
+---
+
+Understanding Filters
+
+### Introduction
+
+Filters are used to get a specific subset of objects (channels, users, messages, members, etc) which
+fit the conditions specified. Earlier versions of the SDK contained String-based filters which are now replaced by type-safe
+filters. This guide aims to explain the different types of filters and how to use them.
+
+### Types Of Filters
+
+#### Filter.equal
+
+The 'equal' filter gets the objects where the given key has the specified value.
+
+```dart
+Filter.equal('type', 'messaging'),
+```
+
+#### Filter.notEqual
+
+The 'notEqual' filter gets the objects where the given key does not have the specified value.
+
+```dart
+Filter.notEqual('type', 'messaging'),
+```
+
+#### Filter.greater
+
+The 'greater' filter gets the objects where the given key has a higher value than the specified value.
+
+```dart
+Filter.greater('count', 5),
+```
+
+#### Filter.greaterOrEqual
+
+The 'greaterOrEqual' filter gets the objects where the given key has an equal or higher value than the specified value.
+
+```dart
+Filter.greaterOrEqual('count', 5),
+```
+
+#### Filter.less
+
+The 'less' filter gets the objects where the given key has a lesser value than the specified value.
+
+```dart
+Filter.less('count', 5),
+```
+
+#### Filter.lessOrEqual
+
+The 'lessOrEqual' filter gets the objects where the given key has a lesser or equal value than the specified value.
+
+```dart
+Filter.lessOrEqual('count', 5),
+```
+
+#### Filter.in_
+
+The 'in_' filter allows getting objects where the key matches any in a specified array.
+
+```dart
+Filter.in_('members', [user.id])
+```
+
+:::note
+Since 'in' is a keyword in Dart, the filter has an underscore added. This does not apply to the 'notIn'
+keyword.
+:::
+
+#### Filter.notIn
+
+The 'notIn' filter allows getting objects where the key matches none in a specified array.
+
+```dart
+Filter.notIn('members', [user.id])
+```
+
+#### Filter.query
+
+The 'query' filter matches values by performing text search with the specified value.
+
+```dart
+Filter.query('name', 'demo')
+```
+
+#### Filter.autoComplete
+
+The 'autoComplete' filter matches values with the specified prefix.
+
+```dart
+Filter.autoComplete('name', 'demo')
+```
+
+#### Filter.exists
+
+The 'exists' filter matches values that exist, or don't exist, based on the specified boolean value.
+
+```dart
+Filter.exists('name', true)
+```
+
+### Group Queries
+
+#### Filter.and
+
+The 'and' operator combines multiple queries.
+
+```dart
+final filter = Filter.and([
+ Filter.equal('type', 'messaging'),
+ Filter.in_('members', [user.id])
+])
+```
+
+#### Filter.or
+
+Combines the provided filters and matches the values matched by at least one of the filters.
+
+```dart
+final filter = Filter.or([
+ Filter.in_('bannedUsers', [user.id]),
+ Filter.in_('shadowBannedUsers', [user.id])
+])
+```
+
+#### Filter.nor
+
+Combines the provided filters and matches the values not matched by all the filters.
+
+```dart
+final filter = Filter.nor([
+ Filter.in_('bannedUsers', [user.id]),
+ Filter.in_('shadowBannedUsers', [user.id])
+])
+```
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:
+
+
+
+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
+
+
+
+##### Stream Chat User Database
+
+
+
+
+### 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 e3e31db6..67a80e17 100644
--- a/packages/stream_chat/CHANGELOG.md
+++ b/packages/stream_chat/CHANGELOG.md
@@ -1,4 +1,11 @@
-## Upcoming
+## 2.2.1
+
+🐞 Fixed
+
+- Fixed unread indicator not updating correctly
+- Fix `channel.show` not working because of null body
+
+## 2.2.0
🐞 Fixed
@@ -8,7 +15,9 @@
- `User` and `OwnUser` classes now have an `image` property. Setting an image will also set the 'image' key on `extraData`, so `user.image` and `user.extraData['image']` is the same.
- `User` and `OwnUser` classes now have a `name` property. Setting a name will also set the 'name' key on `extraData`, so `user.name` and `user.extraData['name']` is the same.
-
+- `Channel` class now has extra `image` getter and setter. As well as an `updateImage` to do a partial update after a channel has been initialized.
+- `Channel` class now has extra `name` getter and setter. As well as an `updateName` to do a partial update after a channel has been initialized.
+- Added slow mode which allows a cooldown period after a user sends a message.
## 2.1.1
🐞 Fixed
diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart
index a05587ab..c2c40018 100644
--- a/packages/stream_chat/lib/src/client/channel.dart
+++ b/packages/stream_chat/lib/src/client/channel.dart
@@ -15,20 +15,77 @@ import 'package:stream_chat/src/core/util/utils.dart';
import 'package:stream_chat/src/event_type.dart';
import 'package:stream_chat/stream_chat.dart';
-/// This a the class that manages a specific channel.
+/// Class that manages a specific channel.
+///
+/// #### Channel name
+///
+/// {@template name}
+/// If an optional [name] argument is provided in the constructor then it
+/// will be set on [extraData] with a key of 'name'.
+///
+/// ```dart
+/// final channel = Channel(client, type, id, name: 'Channel name');
+/// print(channel.name == channel.extraData['name']); // true
+/// ```
+///
+/// Before the channel is initialized the name can be set directly:
+/// ```dart
+/// channel.name = 'New channel name';
+/// ```
+///
+/// To update the name after the channel has been initialized, call:
+/// ```dart
+/// channel.updateName('Updated channel name');
+/// ```
+///
+/// This will do a partial update to update the name.
+/// {@endtemplate}
+///
+/// #### Channel image
+///
+/// {@template image}
+/// If an optional [image] argument is provided in the constructor then it
+/// will be set on [extraData] with a key of 'image'.
+///
+/// ```dart
+/// final channel = Channel(client, type, id, image: 'https://getstream.io/image.png');
+/// print(channel.image == channel.extraData['image']); // true
+/// ```
+///
+/// Before the channel is initialized the image can be set directly:
+/// ```dart
+/// channel.image = 'https://getstream.io/new-image';
+/// ```
+///
+/// To update the image after the channel has been initialized, call:
+/// ```dart
+/// channel.updateImage('https://getstream.io/new-image');
+/// ```
+///
+/// This will do a partial update to update the image.
+/// {@endtemplate}
class Channel {
- /// Create a channel client instance.
+ /// Class that manages a specific channel.
+ ///
+ /// Optional [extraData] and [image] properties can be provided. The [image]
+ /// is exposed to easily set a key of 'image' on [extraData].
Channel(
this._client,
this._type,
this._id, {
+ String? name,
+ String? image,
Map? extraData,
}) : _cid = _id != null ? '$_type:$_id' : null,
- _extraData = extraData ?? {} {
- _client.logger.info('New Channel instance not initialized created');
+ _extraData = {
+ ...?extraData,
+ if (name != null) 'name': name,
+ if (image != null) 'image': image,
+ } {
+ _client.logger.info('New Channel instance created, not yet initialized');
}
- /// Create a channel client instance from a [ChannelState] object
+ /// Create a channel client instance from a [ChannelState] object.
Channel.fromState(this._client, ChannelState channelState)
: assert(
channelState.channel != null,
@@ -40,7 +97,7 @@ class Channel {
_extraData = channelState.channel!.extraData {
state = ChannelClientState(this, channelState);
_initializedCompleter.complete(true);
- _client.logger.info('New Channel instance initialized created');
+ _client.logger.info('New Channel instance initialized');
}
/// This client state
@@ -53,155 +110,188 @@ class Channel {
String? _cid;
final Map _extraData;
+ /// Shortcut to set channel name.
+ ///
+ /// {@macro name}
+ set name(String? name) {
+ if (_initializedCompleter.isCompleted) {
+ throw StateError(
+ 'Once the channel is initialized you should use `channel.updateName` '
+ 'to update the channel name',
+ );
+ }
+ _extraData.addAll({'name': name});
+ }
+
+ /// Shortcut to set channel image.
+ ///
+ /// {@macro image}
+ set image(String? image) {
+ if (_initializedCompleter.isCompleted) {
+ throw StateError(
+ 'Once the channel is initialized you should use `channel.updateImage` '
+ 'to update the channel image',
+ );
+ }
+ _extraData.addAll({'image': image});
+ }
+
set extraData(Map extraData) {
if (_initializedCompleter.isCompleted) {
throw StateError(
- 'Once the channel is initialized you should use channel.update '
+ 'Once the channel is initialized you should use `channel.update` '
'to update channel data',
);
}
_extraData.addAll(extraData);
}
- /// Returns true if the channel is muted
+ /// Returns true if the channel is muted.
bool get isMuted =>
_client.state.currentUser?.channelMutes
.any((element) => element.channel.cid == cid) ==
true;
- /// Returns true if the channel is muted as a stream
- Stream? get isMutedStream => _client.state.currentUserStream
+ /// Returns true if the channel is muted, as a stream.
+ Stream get isMutedStream => _client.state.currentUserStream
.map((event) =>
- event!.channelMutes.any((element) => element.channel.cid == cid) ==
+ event?.channelMutes.any((element) => element.channel.cid == cid) ==
true)
.distinct();
- /// True if the channel is a group
+ /// True if the channel is a group.
bool get isGroup => memberCount != 2;
- /// True if the channel is distinct
+ /// True if the channel is distinct.
bool get isDistinct => id?.startsWith('!members') == true;
- /// Channel configuration
+ /// Channel configuration.
ChannelConfig? get config {
_checkInitialized();
- return state?._channelState.channel?.config;
+ return state!._channelState.channel?.config;
}
- /// Channel configuration as a stream
- Stream? get configStream {
+ /// Channel configuration as a stream.
+ Stream get configStream {
_checkInitialized();
- return state?.channelStateStream.map((cs) => cs.channel?.config);
+ return state!.channelStateStream.map((cs) => cs.channel?.config);
}
- /// Channel user creator
+ /// Channel user creator.
User? get createdBy {
_checkInitialized();
- return state?._channelState.channel?.createdBy;
+ return state!._channelState.channel?.createdBy;
}
- /// Channel user creator as a stream
- Stream? get createdByStream {
+ /// Channel user creator as a stream.
+ Stream get createdByStream {
_checkInitialized();
- return state?.channelStateStream.map((cs) => cs.channel?.createdBy);
+ return state!.channelStateStream.map((cs) => cs.channel?.createdBy);
}
- /// Channel frozen status
- bool? get frozen {
+ /// Channel frozen status.
+ bool get frozen {
_checkInitialized();
- return state?._channelState.channel?.frozen;
+ return state!._channelState.channel?.frozen == true;
}
- /// Channel frozen status as a stream
- Stream? get frozenStream {
+ /// Channel frozen status as a stream.
+ Stream get frozenStream {
_checkInitialized();
- return state?.channelStateStream.map((cs) => cs.channel?.frozen);
+ return state!.channelStateStream.map((cs) => cs.channel?.frozen == true);
}
- /// Channel creation date
+ /// Cooldown count
+ int get cooldown {
+ _checkInitialized();
+ return state!._channelState.channel?.cooldown ?? 0;
+ }
+
+ /// Cooldown count as a stream
+ Stream get cooldownStream {
+ _checkInitialized();
+ return state!.channelStateStream.map((cs) => cs.channel?.cooldown ?? 0);
+ }
+
+ /// Stores time at which cooldown was started
+ DateTime? cooldownStartedAt;
+
+ /// Channel creation date.
DateTime? get createdAt {
_checkInitialized();
- return state?._channelState.channel?.createdAt;
+ return state!._channelState.channel?.createdAt;
}
- /// Channel creation date as a stream
- Stream? get createdAtStream {
+ /// Channel creation date as a stream.
+ Stream get createdAtStream {
_checkInitialized();
- return state?.channelStateStream.map((cs) => cs.channel?.createdAt);
+ return state!.channelStateStream.map((cs) => cs.channel?.createdAt);
}
- /// Channel last message date
+ /// Channel last message date.
DateTime? get lastMessageAt {
_checkInitialized();
-
- return state?._channelState.channel?.lastMessageAt;
+ return state!._channelState.channel?.lastMessageAt;
}
- /// Channel last message date as a stream
- Stream? get lastMessageAtStream {
+ /// Channel last message date as a stream.
+ Stream get lastMessageAtStream {
_checkInitialized();
-
- return state?.channelStateStream.map((cs) => cs.channel?.lastMessageAt);
+ return state!.channelStateStream.map((cs) => cs.channel?.lastMessageAt);
}
- /// Channel updated date
+ /// Channel updated date.
DateTime? get updatedAt {
_checkInitialized();
-
- return state?._channelState.channel?.updatedAt;
+ return state!._channelState.channel?.updatedAt;
}
- /// Channel updated date as a stream
- Stream? get updatedAtStream {
+ /// Channel updated date as a stream.
+ Stream get updatedAtStream {
_checkInitialized();
-
- return state?.channelStateStream.map((cs) => cs.channel?.updatedAt);
+ return state!.channelStateStream.map((cs) => cs.channel?.updatedAt);
}
- /// Channel deletion date
+ /// Channel deletion date.
DateTime? get deletedAt {
_checkInitialized();
-
- return state?._channelState.channel?.deletedAt;
+ return state!._channelState.channel?.deletedAt;
}
- /// Channel deletion date as a stream
- Stream? get deletedAtStream {
+ /// Channel deletion date as a stream.
+ Stream get deletedAtStream {
_checkInitialized();
-
- return state?.channelStateStream.map((cs) => cs.channel?.deletedAt);
+ return state!.channelStateStream.map((cs) => cs.channel?.deletedAt);
}
- /// Channel member count
+ /// Channel member count.
int? get memberCount {
_checkInitialized();
-
- return state?._channelState.channel?.memberCount;
+ return state!._channelState.channel?.memberCount;
}
- /// Channel member count as a stream
- Stream? get memberCountStream {
+ /// Channel member count as a stream.
+ Stream get memberCountStream {
_checkInitialized();
-
- return state?.channelStateStream.map((cs) => cs.channel?.memberCount);
+ return state!.channelStateStream.map((cs) => cs.channel?.memberCount);
}
- /// Channel id
+ /// Channel id.
String? get id => state?._channelState.channel?.id ?? _id;
- /// Channel type
+ /// Channel type.
String get type => state?._channelState.channel?.type ?? _type;
- /// Channel cid
+ /// Channel cid.
String? get cid => state?._channelState.channel?.cid ?? _cid;
- /// Channel team
+ /// Channel team.
String? get team {
_checkInitialized();
- return state?._channelState.channel?.team;
+ return state!._channelState.channel?.team;
}
- /// Channel extra data
+ /// Channel extra data.
Map get extraData {
var data = state?._channelState.channel?.extraData;
if (data == null || data.isEmpty) {
@@ -210,23 +300,54 @@ class Channel {
return data;
}
- /// Channel extra data as a stream
- Stream