diff --git a/.github/workflows/pr_title.yml b/.github/workflows/pr_title.yml
index 02aeb03a..eddb2ac1 100644
--- a/.github/workflows/pr_title.yml
+++ b/.github/workflows/pr_title.yml
@@ -21,6 +21,7 @@ jobs:
ui
doc
repo
+ localization
requireScope: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/README.md b/README.md
index 293f60f6..6758b189 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,9 @@ This package provides business logic to fetch common things required for integra
### [stream_chat_flutter](https://github.com/GetStream/stream-chat-flutter/tree/master/packages/stream_chat_flutter)
This library includes both a low-level chat SDK and a set of reusable and customizable UI components.
+### [stream_chat_localizations](https://github.com/GetStream/stream-chat-flutter/tree/master/packages/stream_chat_localizations)
+This library includes a set of localization files for the Flutter UI components.
+
## Flutter Chat Tutorial
The best place to start is the [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/).
diff --git a/docusaurus/docs/Flutter/assets/chat_basics.png b/docusaurus/docs/Flutter/assets/chat_basics.png
index 4eb0aa67..82e27cfa 100644
Binary files a/docusaurus/docs/Flutter/assets/chat_basics.png and b/docusaurus/docs/Flutter/assets/chat_basics.png differ
diff --git a/docusaurus/docs/Flutter/assets/hashtag_example.jpg b/docusaurus/docs/Flutter/assets/hashtag_example.jpg
new file mode 100644
index 00000000..a2bb3ba4
Binary files /dev/null and b/docusaurus/docs/Flutter/assets/hashtag_example.jpg differ
diff --git a/docusaurus/docs/Flutter/assets/localization_support.jpg b/docusaurus/docs/Flutter/assets/localization_support.jpg
new file mode 100644
index 00000000..50d8b761
Binary files /dev/null and b/docusaurus/docs/Flutter/assets/localization_support.jpg differ
diff --git a/docusaurus/docs/Flutter/basics/introduction.mdx b/docusaurus/docs/Flutter/basics/introduction.mdx
index 32e80899..756bd33f 100644
--- a/docusaurus/docs/Flutter/basics/introduction.mdx
+++ b/docusaurus/docs/Flutter/basics/introduction.mdx
@@ -28,6 +28,7 @@ The core package allows more customisation and hence provides business logic but
reusable and customisable UI components.
4. Persistence (stream_chat_persistence) : provides a persistence client for fetching and
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)
diff --git a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx
index 945fd3d7..fb1f7bbe 100644
--- a/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx
+++ b/docusaurus/docs/Flutter/guides/adding_custom_attachments.mdx
@@ -1,6 +1,6 @@
---
id: adding_custom_attachments
-sidebar_position: 2
+sidebar_position: 3
title: Adding Custom Attachments
---
diff --git a/docusaurus/docs/Flutter/guides/adding_localization.mdx b/docusaurus/docs/Flutter/guides/adding_localization.mdx
new file mode 100644
index 00000000..c08f081c
--- /dev/null
+++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx
@@ -0,0 +1,176 @@
+---
+id: adding_localization
+sidebar_position: 2
+title: Adding Localization
+---
+
+Adding Localization To UI Widgets
+
+### Introduction
+
+We have a dedicated package for adding localization to our UI widgets. It's called `stream_chat_localizations` and you can find it [here](https://pub.dev/packages/stream_chat_localizations).
+
+
+
+## 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**).
+
+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.
+
+### Supported languages
+
+At the moment we support the following languages:
+- [English](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart)
+- [Hindi](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart)
+- [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)
+
+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
+
+Add this to your package's `pubspec.yaml` file, use the latest version [](https://pub.dartlang.org/packages/stream_chat_localizations)
+```yaml
+dependencies:
+ stream_chat_localizations: ^latest_version
+```
+
+Then run `flutter packages get`
+
+### Usage
+
+Generally, Flutter and the Stream Chat SDK will use the system locale of the user's device, if that locale is supported (see below). If the locale is not supported we will default to `en` (however it's always possible to [customize that](#changing-the-default-language)).
+Make sure to read more about localization in the [official Flutter docs](https://flutter.dev/docs/development/accessibility-and-localization/internationalization).
+
+```dart
+import 'package:flutter/material.dart';
+import 'package:stream_chat_localizations/stream_chat_localizations.dart';
+
+void main() {
+ WidgetsFlutterBinding.ensureInitialized();
+ runApp(MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ // Add all the supported locales
+ supportedLocales: const [
+ Locale('en'),
+ Locale('hi'),
+ Locale('fr'),
+ Locale('it'),
+ ],
+ // Add GlobalStreamChatLocalizations.delegates
+ localizationsDelegates: GlobalStreamChatLocalizations.delegates,
+ builder: (context, widget) => StreamChat(
+ client: client,
+ child: widget,
+ ),
+ home: StreamChannel(
+ channel: channel,
+ child: const ChannelPage(),
+ ),
+ );
+ }
+}
+```
+
+## Setting a language
+The application language can be changed through system preferences or programmatically.
+
+### System Preferences
+The application locale can be changed by changing the language for your device or emulator within the device's system preferences.
+
+[iOS change language](https://support.apple.com/en-us/HT204031)
+
+[Android change language](https://support.google.com/websearch/answer/3333234?co=GENIE.Platform%3DAndroid&hl=en)
+
+Note that the language needs to be supported in your application to work.
+
+### Programmatically
+You can also set the locale programmatically in your Flutter application without changing the device's language.
+
+```dart
+return MaterialApp(
+ ...
+ locale: const Locale('fr'),
+ ...
+);
+```
+
+There are many ways that this can be set for additional control. For information and examples, see this [Stack Overflow post](https://stackoverflow.com/questions/49441212/flutter-multi-lingual-application-how-to-override-the-locale).
+
+### Adding a new language
+
+To add a new language, create a new class extending `GlobalStreamChatLocalizations` and create a delegate for it, adding it to the `delegates` array.
+
+Check out [this example](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/example/lib/add_new_lang.dart) to see how to add a new language.
+
+### Override existing languages
+
+To override an existing language, create a new class extending that particular language class and create a delegate for it, adding it to the `delegates` array.
+
+Check out [this example](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/example/lib/override_lang.dart) to see how to override an existing language.
+
+### Changing the default language
+
+To change the default language you can use the `MaterialApp.localeListResolutionCallback` property.
+Here is an example of how that would look like:
+
+```dart
+ MaterialApp(
+ theme: ThemeData.light(),
+ darkTheme: ThemeData.dark(),
+ // Add all the supported locales
+ supportedLocales: const [
+ Locale('en'),
+ Locale('hi'),
+ Locale('fr'),
+ Locale('it'),
+ ],
+ // locales are the locales of the device
+ // supportedLocales are the app supported locales
+ localeListResolutionCallback: (locales, supportedLocales) {
+ // We map the supported locales to language codes
+ // note that this is completely optional and this logic can be changed as you like
+ final supportedLanguageCodes =
+ supportedLocales.map((e) => e.languageCode);
+ if (locales != null) {
+ // we iterate over the locales and find the first one that is supported
+ for (final locale in locales) {
+ if (supportedLanguageCodes.contains(locale.languageCode)) {
+ return locale;
+ }
+ }
+ }
+
+ // if we didn't find a supported language, we return the Italian language
+ return const Locale('it');
+ },
+ // Add GlobalStreamChatLocalizations.delegates
+ localizationsDelegates: GlobalStreamChatLocalizations.delegates,
+ ...
+
+```
+
+In this case, we're using Italian as the default language.
+
+### ⚠️ Note on **iOS**
+
+For translation to work on **iOS** you need to add supported locales to
+`ios/Runner/Info.plist` as described [here](https://flutter.dev/docs/development/accessibility-and-localization/internationalization#specifying-supportedlocales).
+
+Example:
+
+```xml
+CFBundleLocalizations
+
+ en
+ nb
+ fr
+ it
+
+```
diff --git a/docusaurus/docs/Flutter/guides/customize_text_messages.mdx b/docusaurus/docs/Flutter/guides/customize_text_messages.mdx
new file mode 100644
index 00000000..a2d37333
--- /dev/null
+++ b/docusaurus/docs/Flutter/guides/customize_text_messages.mdx
@@ -0,0 +1,158 @@
+---
+id: customize_text_messages
+sidebar_position: 6
+title: Customize Text Messages
+---
+
+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 message text in the `MessageListView` / `MessageWidget` in the
+Stream Chat Flutter UI SDK.
+
+:::note
+This guide is specifically for the `MessageListView` but if you intend to display a `MessageWidget`
+separately, follow the same process without the `.copyWith` and use the default constructor instead.
+:::
+
+### Basics of customizing a `MessageWidget`
+
+First, add a `MessageListView` in the appropriate place where you intend to display messages from a
+channel.
+
+```dart
+MessageListView(
+ ...
+)
+```
+
+Now, we use the `messageBuilder` parameter to build a custom message. The builder function also provides
+the default implementation of the `MessageWidget` so that we can change certain aspects of the widget
+without redoing all of the default parameters.
+
+:::note
+In earlier versions of the SDK, some `MessageWidget` parameters were exposed directly through the `MessageListView`,
+however, this quickly becomes hard to maintain as more parameters and customizations are added to the
+`MessageWidget`. Newer version utilise a cleaner interface to change the parameters by supplying a
+default message implementation as aforementioned.
+:::
+
+```dart
+MessageListView(
+ ...
+ messageBuilder: (context, messageDetails, messageList, defaultWidget) {
+ return defaultWidget;
+ },
+)
+```
+
+We use `.copyWith()` to customize the widget:
+
+```dart
+MessageListView(
+ ...
+ messageBuilder: (context, messageDetails, messageList, defaultWidget) {
+ return defaultWidget.copyWith(
+ ...
+ );
+ },
+)
+```
+
+### Customizing text
+
+If you intend to simply change the theme for the text, you need not recreate the whole widget. The
+`MessageWidget` has a `messageTheme` parameter that allows you to pass the theme for most aspects
+of the message.
+
+```dart
+MessageListView(
+ ...
+ messageBuilder: (context, messageDetails, messageList, defaultWidget) {
+ return defaultWidget.copyWith(
+ messageTheme: MessageTheme(
+ ...
+ messageText: TextStyle(),
+ ),
+ );
+ },
+)
+```
+
+If you want to replace the entire text widget in the `MessageWidget`, you can use the `textBuilder`
+parameter which provides a builder for creating a widget to substitute the default text.parameter
+
+```dart
+MessageListView(
+ ...
+ messageBuilder: (context, messageDetails, messageList, defaultWidget) {
+ return defaultWidget.copyWith(
+ textBuilder: (context, message) {
+ return Text(message.text);
+ },
+ );
+ },
+)
+```
+
+### Adding Hashtags
+
+To add elements like hashtags, we can override the `textBuilder` in the MessageWidget:
+
+```dart
+MessageListView(
+ ...
+ messageBuilder: (context, messageDetails, messageList, defaultWidget) {
+ return defaultWidget.copyWith(
+ textBuilder: (context, message) {
+ final text = _replaceHashtags(message.text).replaceAll('\n', '\\\n');
+ final messageTheme = StreamChatTheme.of(context).ownMessageTheme;
+
+ return MarkdownBody(
+ data: text,
+ onTapLink: (
+ String link,
+ String href,
+ String title,
+ ) {
+ // Do something with tapped hashtag
+ },
+ styleSheet: MarkdownStyleSheet.fromTheme(
+ Theme.of(context).copyWith(
+ textTheme: Theme.of(context).textTheme.apply(
+ bodyColor: messageTheme.messageText.color,
+ decoration: messageTheme.messageText.decoration,
+ decorationColor: messageTheme.messageText.decorationColor,
+ decorationStyle: messageTheme.messageText.decorationStyle,
+ fontFamily: messageTheme.messageText.fontFamily,
+ ),
+ ),
+ ).copyWith(
+ a: messageTheme.messageLinks,
+ p: messageTheme.messageText,
+ ),
+ );
+ },
+ );
+ },
+)
+
+String _replaceHashtags(String text) {
+ RegExp exp = new RegExp(r"\B#\w\w+");
+ exp.allMatches(text).forEach((match){
+ text = text.replaceAll(
+ '${match.group(0)}', '[${match.group(0)}](${match.group(0).replaceAll(' ', '')})');
+ });
+ return text;
+}
+```
+
+We can replace the hashtags using RegEx and add links for the MarkdownBody which is done here in the
+`_replaceHashtags()` function.
+Inside the textBuilder, we use the `flutter_markdown` package to build our hashtags as links.
+
+
diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx
index 1dcf969c..89b6dacc 100644
--- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx
+++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx
@@ -1,6 +1,6 @@
---
id: mig_guide_2_0
-sidebar_position: 3
+sidebar_position: 4
title: Migrating to 2.0 (Null-safety)
---
@@ -57,7 +57,7 @@ The changes will be listed by package and a concise changelog will follow with m
* `MessageBuilder` and `ParentMessageBuilder` signature is now
-```
+```dart
typedef MessageBuilder = Widget Function(
BuildContext,
MessageDetails,
diff --git a/melos.yaml b/melos.yaml
index 6232c3fa..b645deb0 100644
--- a/melos.yaml
+++ b/melos.yaml
@@ -13,7 +13,7 @@ scripts:
analyze:
run: |
- melos exec -c 4 --ignore="*example*" -- \
+ melos exec -c 5 --ignore="*example*" -- \
dart analyze --fatal-infos .
description: |
Run `dart analyze` in all packages.
@@ -26,7 +26,7 @@ scripts:
lint:pub:
run: |
- melos exec -c 4 --no-private --ignore="*example*" -- \
+ melos exec -c 5 --no-private --ignore="*example*" -- \
pub publish --dry-run
description: |
Run `pub publish --dry-run` in all packages.
@@ -56,7 +56,7 @@ scripts:
dir-exists: test
test:flutter:
- run: melos exec -c 3 --fail-fast -- "flutter test --coverage"
+ run: melos exec -c 4 --fail-fast -- "flutter test --coverage"
description: Run Flutter tests for a specific package in this project.
select-package:
flutter: true
@@ -64,7 +64,7 @@ scripts:
coverage:ignore-file:
run: |
- melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from-coverage.sh"
+ melos exec -c 5 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from-coverage.sh"
description: Removes all the ignored files from the coverage report.
select-package:
dir-exists: coverage
diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md
index e05599e1..9bfe494f 100644
--- a/packages/stream_chat/CHANGELOG.md
+++ b/packages/stream_chat/CHANGELOG.md
@@ -1,3 +1,30 @@
+## 2.1.1
+
+🐞 Fixed
+
+- Mutes were not working correctly in 2.1.0
+
+## 2.1.0
+
+🛑️ Removed
+
+- The `MessageTranslation` class has been removed. Use the new `i18n` field in the `Message` class instead.
+
+✅ Added
+
+- The `Message` class now has an `i18n` field for translations
+- The `User` class now has a `language` field for the user's language preference.
+
+🔄 Changed
+
+- `client.user` is now deprecated in favor of `client.currentUser`.
+- `client.userStream` is now deprecated in favor of `client.currentUserStream`.
+
+🐞 Fixed
+
+- [#563](https://github.com/GetStream/stream-chat-flutter/issues/563): `Channel.stopWatching()` not working
+- [#575](https://github.com/GetStream/stream-chat-flutter/issues/575): Wrong `OwnUser.*`
+
## 2.0.0
🛑️ Breaking Changes from `1.5.3`
@@ -619,4 +646,4 @@
## 0.0.2
-- first beta version
\ No newline at end of file
+- first beta version
diff --git a/packages/stream_chat/README.md b/packages/stream_chat/README.md
index 1ee52ec5..74afad4c 100644
--- a/packages/stream_chat/README.md
+++ b/packages/stream_chat/README.md
@@ -11,6 +11,7 @@
- [Register](https://getstream.io/chat/trial/) to get an API key for Stream Chat
- [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/)
- [Chat UI Kit](https://getstream.io/chat/ui-kit/)
+- [Chat Client Docs](https://getstream.io/chat/docs/flutter-dart/?language=dart)
### Changelog
diff --git a/packages/stream_chat/example/lib/main.dart b/packages/stream_chat/example/lib/main.dart
index 167f5f6b..46030341 100644
--- a/packages/stream_chat/example/lib/main.dart
+++ b/packages/stream_chat/example/lib/main.dart
@@ -245,5 +245,5 @@ class _MessageViewState extends State {
/// Helper extension for quickly retrieving
/// the current user id from a [StreamChatClient].
extension on StreamChatClient {
- String get uid => state.user!.id;
+ String get uid => state.currentUser!.id;
}
diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart
index 8e298fc9..fb579a56 100644
--- a/packages/stream_chat/lib/src/client/channel.dart
+++ b/packages/stream_chat/lib/src/client/channel.dart
@@ -65,12 +65,12 @@ class Channel {
/// Returns true if the channel is muted
bool get isMuted =>
- _client.state.user?.channelMutes
+ _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.userStream
+ Stream? get isMutedStream => _client.state.currentUserStream
.map((event) =>
event!.channelMutes.any((element) => element.channel.cid == cid) ==
true)
@@ -397,7 +397,7 @@ class Channel {
// ignore: parameter_assignments
message = message.copyWith(
createdAt: message.createdAt,
- user: _client.state.user,
+ user: _client.state.currentUser,
quotedMessage: quotedMessage,
status: MessageSendingStatus.sending,
attachments: message.attachments.map(
@@ -711,7 +711,7 @@ class Channel {
_checkInitialized();
final messageId = message.id;
final now = DateTime.now();
- final user = _client.state.user;
+ final user = _client.state.currentUser;
final latestReactions = [...message.latestReactions ?? []];
if (enforceUnique) {
@@ -768,7 +768,7 @@ class Channel {
Future deleteReaction(
Message message, Reaction reaction) async {
final type = reaction.type;
- final user = _client.state.user;
+ final user = _client.state.currentUser;
final reactionCounts = {...message.reactionCounts ?? {}};
if (reactionCounts.containsKey(type)) {
@@ -1346,7 +1346,7 @@ class ChannelClientState {
void _computeInitialUnread() {
final userRead = channelState.read.firstWhereOrNull(
- (r) => r.user.id == _channel._client.state.user?.id,
+ (r) => r.user.id == _channel._client.state.currentUser?.id,
);
if (userRead != null) {
unreadCount = userRead.unreadMessages;
@@ -1463,7 +1463,7 @@ class ChannelClientState {
void _listenReactionDeleted() {
_subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) {
- final userId = _channel.client.state.user!.id;
+ final userId = _channel.client.state.currentUser!.id;
final message = event.message!.copyWith(
ownReactions: [...event.message!.latestReactions!]
..removeWhere((it) => it.userId != userId),
@@ -1474,7 +1474,7 @@ class ChannelClientState {
void _listenReactions() {
_subscriptions.add(_channel.on(EventType.reactionNew).listen((event) {
- final userId = _channel.client.state.user!.id;
+ final userId = _channel.client.state.currentUser!.id;
final message = event.message!.copyWith(
ownReactions: [...event.message!.latestReactions!]
..removeWhere((it) => it.userId != userId),
@@ -1490,7 +1490,7 @@ class ChannelClientState {
EventType.reactionUpdated,
)
.listen((event) {
- final userId = _channel.client.state.user!.id;
+ final userId = _channel.client.state.currentUser!.id;
final message = event.message!.copyWith(
ownReactions: [...event.message!.latestReactions!]
..removeWhere((it) => it.userId != userId),
@@ -1584,7 +1584,7 @@ class ChannelClientState {
if (userReadIndex != null && userReadIndex != -1) {
final userRead = readList.removeAt(userReadIndex);
- if (userRead.user.id == _channel._client.state.user!.id) {
+ if (userRead.user.id == _channel._client.state.currentUser!.id) {
unreadCount = 0;
}
readList.add(Read(
@@ -1674,11 +1674,12 @@ class ChannelClientState {
int get unreadCount => _unreadCountController.value;
bool _countMessageAsUnread(Message message) {
- final userId = _channel.client.state.user?.id;
- final userIsMuted = _channel.client.state.user?.mutes.firstWhereOrNull(
- (m) => m.user.id == message.user?.id,
- ) !=
- null;
+ final userId = _channel.client.state.currentUser?.id;
+ final userIsMuted =
+ _channel.client.state.currentUser?.mutes.firstWhereOrNull(
+ (m) => m.user.id == message.user?.id,
+ ) !=
+ null;
return message.silent != true &&
message.shadowed != true &&
message.user?.id != userId &&
@@ -1827,7 +1828,7 @@ class ChannelClientState {
(event) {
if (event.user != null) {
final user = event.user!;
- if (user.id != _channel.client.state.user?.id) {
+ if (user.id != _channel.client.state.currentUser?.id) {
_typings[user] = event;
_typingEventsController.add(_typings);
}
@@ -1840,7 +1841,7 @@ class ChannelClientState {
(event) {
if (event.user != null) {
final user = event.user!;
- if (user.id != _channel.client.state.user?.id) {
+ if (user.id != _channel.client.state.currentUser?.id) {
_typings.remove(event.user);
_typingEventsController.add(_typings);
}
diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart
index 2ea20e60..7b5cbb35 100644
--- a/packages/stream_chat/lib/src/client/client.dart
+++ b/packages/stream_chat/lib/src/client/client.dart
@@ -25,12 +25,14 @@ import 'package:stream_chat/src/core/models/member.dart';
import 'package:stream_chat/src/core/models/message.dart';
import 'package:stream_chat/src/core/models/own_user.dart';
import 'package:stream_chat/src/core/models/user.dart';
+import 'package:stream_chat/src/core/platform_detector/platform_detector.dart';
import 'package:stream_chat/src/core/util/utils.dart';
import 'package:stream_chat/src/db/chat_persistence_client.dart';
import 'package:stream_chat/src/event_type.dart';
import 'package:stream_chat/src/location.dart';
import 'package:stream_chat/src/ws/connection_status.dart';
import 'package:stream_chat/src/ws/websocket.dart';
+import 'package:stream_chat/version.dart';
/// Handler function used for logging records. Function requires a single
/// [LogRecord] as the only parameter.
@@ -42,6 +44,10 @@ final _levelEmojiMapper = {
Level.SEVERE: '🚨',
};
+final _userAgent = 'stream-chat-dart-client-'
+ '${CurrentPlatform.name}-'
+ '${PACKAGE_VERSION.split('+')[0]}';
+
/// The official Dart client for Stream Chat,
/// a service for building chat applications.
/// This library can be used on any Dart project and on both mobile and web apps
@@ -80,6 +86,7 @@ class StreamChatClient {
location: location,
connectTimeout: connectTimeout,
receiveTimeout: receiveTimeout,
+ headers: {'X-Stream-Client': _userAgent},
);
_chatApi = chatApi ??
@@ -99,6 +106,7 @@ class StreamChatClient {
tokenManager: _tokenManager,
handler: handleEvent,
logger: detachedLogger('🔌'),
+ queryParameters: {'X-Stream-Client': _userAgent},
);
_retryPolicy = retryPolicy ??
@@ -308,23 +316,21 @@ class StreamChatClient {
);
final ownUser = OwnUser.fromUser(user);
- state.user = ownUser;
+ state.currentUser = ownUser;
- if (!connectWebSocket) {
- return ownUser;
- }
+ if (!connectWebSocket) return ownUser;
try {
if (_originalChatPersistenceClient != null) {
_chatPersistenceClient = _originalChatPersistenceClient;
await _chatPersistenceClient!.connect(ownUser.id);
}
- final res = await openConnection();
- return res;
+ final connectedUser = await openConnection();
+ return state.currentUser = connectedUser;
} catch (e, stk) {
if (e is StreamWebSocketError && e.isRetriable) {
final event = await _chatPersistenceClient?.getConnectionInfo();
- if (event != null) return event.me?.merge(ownUser) ?? ownUser;
+ if (event != null) return ownUser.merge(event.me);
}
logger.severe('error connecting user : ${ownUser.id}', e, stk);
rethrow;
@@ -334,12 +340,12 @@ class StreamChatClient {
/// Creates a new WebSocket connection with the current user.
Future openConnection() async {
assert(
- state.user != null,
+ state.currentUser != null,
'User is not set on client, '
'use `connectUser` or `connectAnonymousUser` instead',
);
- final user = state.user!;
+ final user = state.currentUser!;
logger.info('Opening web-socket connection for ${user.id}');
@@ -363,7 +369,7 @@ class StreamChatClient {
try {
final event = await _ws.connect(user);
- return event.me?.merge(user) ?? user;
+ return user.merge(event.me);
} catch (e, stk) {
logger.severe('error connecting ws', e, stk);
rethrow;
@@ -378,7 +384,7 @@ class StreamChatClient {
void closeConnection() {
if (wsConnectionStatus == ConnectionStatus.disconnected) return;
- logger.info('Closing web-socket connection for ${state.user?.id}');
+ logger.info('Closing web-socket connection for ${state.currentUser?.id}');
_wsConnectionStatus = ConnectionStatus.disconnected;
_connectionStatusSubscription?.cancel();
@@ -388,9 +394,6 @@ class StreamChatClient {
}
void _handleHealthCheckEvent(Event event) {
- final user = event.me;
- if (user != null) state.user = user;
-
final connectionId = event.connectionId;
if (connectionId != null) {
_connectionIdManager.setConnectionId(connectionId);
@@ -1312,7 +1315,7 @@ class StreamChatClient {
/// If [flushChatPersistence] is true the client deletes all offline
/// user's data.
Future disconnectUser({bool flushChatPersistence = false}) async {
- logger.info('Disconnecting user : ${state.user?.id}');
+ logger.info('Disconnecting user : ${state.currentUser?.id}');
// resetting state
state.dispose();
@@ -1355,16 +1358,15 @@ class ClientState {
_subscriptions.addAll([
_client
.on()
- .where((event) => event.me != null)
- .map((e) => e.me)
+ .where((event) =>
+ event.me != null && event.type != EventType.healthCheck)
+ .map((e) => e.me!)
.listen((user) {
- _userController.add(user);
- final totalUnreadCount = user?.totalUnreadCount;
- if (totalUnreadCount != null) {
- _totalUnreadCountController.add(totalUnreadCount);
- }
+ currentUser = currentUser?.merge(user) ?? user;
+ final totalUnreadCount = user.totalUnreadCount;
+ _totalUnreadCountController.add(totalUnreadCount);
- final unreadChannels = user?.unreadChannels;
+ final unreadChannels = user.unreadChannels;
if (unreadChannels != null) {
_unreadChannelsController.add(unreadChannels);
}
@@ -1408,8 +1410,8 @@ class ClientState {
void _listenUserUpdated() {
_subscriptions.add(_client.on(EventType.userUpdated).listen((event) {
- if (event.user!.id == user!.id) {
- user = OwnUser.fromJson(event.user!.toJson());
+ if (event.user!.id == currentUser!.id) {
+ currentUser = OwnUser.fromJson(event.user!.toJson());
}
updateUser(event.user);
}));
@@ -1431,9 +1433,10 @@ class ClientState {
final StreamChatClient _client;
- /// Update user information
- set user(OwnUser? user) {
- _userController.add(user);
+ /// Sets the user currently interacting with the client
+ /// note: this fully overrides the [currentUser]
+ set currentUser(OwnUser? user) {
+ _currentUserController.add(user);
}
/// Update all the [users] with the provided [userList]
@@ -1450,10 +1453,24 @@ class ClientState {
void updateUser(User? user) => updateUsers([user]);
/// The current user
- OwnUser? get user => _userController.valueOrNull;
+ OwnUser? get currentUser => _currentUserController.valueOrNull;
/// The current user as a stream
- Stream get userStream => _userController.stream;
+ Stream get currentUserStream => _currentUserController.stream;
+
+ // coverage:ignore-start
+
+ /// The current user
+ @Deprecated('Use `.currentUser` instead, Will be removed in future releases')
+ OwnUser? get user => _currentUserController.valueOrNull;
+
+ /// The current user as a stream
+ @Deprecated(
+ 'Use `.currentUserStream` instead, Will be removed in future releases',
+ )
+ Stream get userStream => _currentUserController.stream;
+
+ // coverage:ignore-end
/// The current user
Map get users => _usersController.value;
@@ -1485,7 +1502,7 @@ class ClientState {
}
final _channelsController = BehaviorSubject>.seeded({});
- final _userController = BehaviorSubject();
+ final _currentUserController = BehaviorSubject();
final _usersController = BehaviorSubject>.seeded({});
final _unreadChannelsController = BehaviorSubject.seeded(0);
final _totalUnreadCountController = BehaviorSubject.seeded(0);
@@ -1493,7 +1510,7 @@ class ClientState {
/// Call this method to dispose this object
void dispose() {
_subscriptions.forEach((s) => s.cancel());
- _userController.close();
+ _currentUserController.close();
_unreadChannelsController.close();
_totalUnreadCountController.close();
channels.values.forEach((c) => c.dispose());
diff --git a/packages/stream_chat/lib/src/core/api/responses.dart b/packages/stream_chat/lib/src/core/api/responses.dart
index e06a213a..033e91b2 100644
--- a/packages/stream_chat/lib/src/core/api/responses.dart
+++ b/packages/stream_chat/lib/src/core/api/responses.dart
@@ -75,7 +75,7 @@ class QueryChannelsResponse extends _BaseResponse {
@JsonSerializable(createToJson: false)
class TranslateMessageResponse extends _BaseResponse {
/// Translated message
- late TranslatedMessage message;
+ late Message message;
/// Create a new instance from a json
static TranslateMessageResponse fromJson(Map json) =>
diff --git a/packages/stream_chat/lib/src/core/api/responses.g.dart b/packages/stream_chat/lib/src/core/api/responses.g.dart
index deba55f0..15b64a05 100644
--- a/packages/stream_chat/lib/src/core/api/responses.g.dart
+++ b/packages/stream_chat/lib/src/core/api/responses.g.dart
@@ -47,8 +47,7 @@ TranslateMessageResponse _$TranslateMessageResponseFromJson(
Map json) {
return TranslateMessageResponse()
..duration = json['duration'] as String?
- ..message =
- TranslatedMessage.fromJson(json['message'] as Map);
+ ..message = Message.fromJson(json['message'] as Map);
}
QueryMembersResponse _$QueryMembersResponseFromJson(Map json) {
diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client.dart b/packages/stream_chat/lib/src/core/http/stream_http_client.dart
index 406138c3..c19cdb8a 100644
--- a/packages/stream_chat/lib/src/core/http/stream_http_client.dart
+++ b/packages/stream_chat/lib/src/core/http/stream_http_client.dart
@@ -10,9 +10,7 @@ import 'package:stream_chat/src/core/http/interceptor/connection_id_interceptor.
import 'package:stream_chat/src/core/http/interceptor/logging_interceptor.dart';
import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart';
import 'package:stream_chat/src/core/http/token_manager.dart';
-import 'package:stream_chat/src/core/platform_detector/platform_detector.dart';
import 'package:stream_chat/src/location.dart';
-import 'package:stream_chat/version.dart';
part 'stream_http_client_options.dart';
@@ -33,11 +31,14 @@ class StreamHttpClient {
..options.baseUrl = _options.baseUrl
..options.receiveTimeout = _options.receiveTimeout.inMilliseconds
..options.connectTimeout = _options.connectTimeout.inMilliseconds
- ..options.queryParameters = {'api_key': apiKey}
+ ..options.queryParameters = {
+ 'api_key': apiKey,
+ ..._options.queryParameters,
+ }
..options.headers = {
'Content-Type': 'application/json',
- 'X-Stream-Client': _options.userAgent,
'Content-Encoding': 'application/gzip',
+ ..._options.headers,
}
..interceptors.addAll([
if (tokenManager != null) AuthInterceptor(this, tokenManager),
diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart b/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart
index faad46a1..01cdb12e 100644
--- a/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart
+++ b/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart
@@ -10,6 +10,8 @@ class StreamHttpClientOptions {
this.location,
this.connectTimeout = const Duration(seconds: 6),
this.receiveTimeout = const Duration(seconds: 6),
+ this.queryParameters = const {},
+ this.headers = const {},
}) : _baseUrl = baseUrl ?? _defaultBaseURL;
final String _baseUrl;
@@ -32,8 +34,20 @@ class StreamHttpClientOptions {
/// received timeout, default to 6s
final Duration receiveTimeout;
- /// Get the current user agent
- String get userAgent => 'stream-chat-dart-client-'
- '${CurrentPlatform.name}-'
- '${PACKAGE_VERSION.split('+')[0]}';
+ /// Common query parameters.
+ ///
+ /// List values use the default [ListFormat.multiCompatible].
+ ///
+ /// The value can be overridden per parameter by adding a [MultiParam]
+ /// object wrapping the actual List value and the desired format.
+ final Map queryParameters;
+
+ /// Http request headers.
+ /// The keys of initial headers will be converted to lowercase,
+ /// for example 'Content-Type' will be converted to 'content-type'.
+ ///
+ /// The key of Header Map is case-insensitive
+ /// eg: content-type and Content-Type are
+ /// regard as the same key.
+ final Map headers;
}
diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart
index d394dbdd..6ad8721c 100644
--- a/packages/stream_chat/lib/src/core/models/message.dart
+++ b/packages/stream_chat/lib/src/core/models/message.dart
@@ -2,8 +2,8 @@ import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/core/models/attachment.dart';
import 'package:stream_chat/src/core/models/reaction.dart';
-import 'package:stream_chat/src/core/util/serializer.dart';
import 'package:stream_chat/src/core/models/user.dart';
+import 'package:stream_chat/src/core/util/serializer.dart';
import 'package:uuid/uuid.dart';
part 'message.g.dart';
@@ -73,6 +73,7 @@ class Message extends Equatable {
this.extraData = const {},
this.deletedAt,
this.status = MessageSendingStatus.sent,
+ this.i18n,
}) : id = id ?? const Uuid().v4(),
pinExpires = pinExpires?.toUtc(),
createdAt = createdAt ?? DateTime.now(),
@@ -218,6 +219,10 @@ class Message extends Equatable {
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
final DateTime? deletedAt;
+ /// A Map of translations.
+ @JsonKey(includeIfNull: false)
+ final Map? i18n;
+
/// Known top level fields.
/// Useful for [Serializer] methods.
static const topLevelFields = [
@@ -248,6 +253,7 @@ class Message extends Equatable {
'pinned_at',
'pin_expires',
'pinned_by',
+ 'i18n',
];
/// Serialize to json
@@ -285,6 +291,7 @@ class Message extends Equatable {
User? pinnedBy,
Map? extraData,
MessageSendingStatus? status,
+ Map? i18n,
}) {
assert(() {
if (pinExpires is! DateTime &&
@@ -324,6 +331,7 @@ class Message extends Equatable {
pinnedBy: pinnedBy ?? this.pinnedBy,
pinExpires:
pinExpires == _pinExpires ? this.pinExpires : pinExpires as DateTime?,
+ i18n: i18n ?? this.i18n,
);
}
@@ -358,6 +366,7 @@ class Message extends Equatable {
pinnedAt: other.pinnedAt,
pinExpires: other.pinExpires,
pinnedBy: other.pinnedBy,
+ i18n: other.i18n,
);
@override
@@ -390,35 +399,6 @@ class Message extends Equatable {
pinnedBy,
extraData,
status,
+ i18n,
];
}
-
-/// A translated message
-/// It has an additional property called [i18n]
-@JsonSerializable()
-class TranslatedMessage extends Message {
- /// Constructor used for json serialization
- TranslatedMessage(this.i18n) : super();
-
- /// Create a new instance from a json
- factory TranslatedMessage.fromJson(Map json) =>
- _$TranslatedMessageFromJson(
- Serializer.moveToExtraDataFromRoot(json, topLevelFields),
- );
-
- /// A Map of
- final Map? i18n;
-
- /// Known top level fields.
- /// Useful for [Serializer] methods.
- static final topLevelFields = [
- 'i18n',
- ...Message.topLevelFields,
- ];
-
- /// Serialize to json
- @override
- Map toJson() => Serializer.moveFromExtraDataToRoot(
- _$TranslatedMessageToJson(this),
- );
-}
diff --git a/packages/stream_chat/lib/src/core/models/message.g.dart b/packages/stream_chat/lib/src/core/models/message.g.dart
index c49e2a6e..25254e2c 100644
--- a/packages/stream_chat/lib/src/core/models/message.g.dart
+++ b/packages/stream_chat/lib/src/core/models/message.g.dart
@@ -67,6 +67,9 @@ Message _$MessageFromJson(Map json) {
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
+ i18n: (json['i18n'] as Map?)?.map(
+ (k, e) => MapEntry(k, e as String),
+ ),
);
}
@@ -107,18 +110,6 @@ Map _$MessageToJson(Message instance) {
val['pinned_by'] = readonly(instance.pinnedBy);
val['extra_data'] = instance.extraData;
writeNotNull('deleted_at', readonly(instance.deletedAt));
+ writeNotNull('i18n', instance.i18n);
return val;
}
-
-TranslatedMessage _$TranslatedMessageFromJson(Map json) {
- return TranslatedMessage(
- (json['i18n'] as Map?)?.map(
- (k, e) => MapEntry(k, e as String),
- ),
- );
-}
-
-Map _$TranslatedMessageToJson(TranslatedMessage instance) =>
- {
- 'i18n': instance.i18n,
- };
diff --git a/packages/stream_chat/lib/src/core/models/own_user.dart b/packages/stream_chat/lib/src/core/models/own_user.dart
index 679abb47..fb5b1caf 100644
--- a/packages/stream_chat/lib/src/core/models/own_user.dart
+++ b/packages/stream_chat/lib/src/core/models/own_user.dart
@@ -27,6 +27,7 @@ class OwnUser extends User {
Map extraData = const {},
bool banned = false,
List teams = const [],
+ String? language,
}) : super(
id: id,
role: role,
@@ -37,6 +38,7 @@ class OwnUser extends User {
extraData: extraData,
banned: banned,
teams: teams,
+ language: language,
);
/// Create a new instance from a json
@@ -54,6 +56,7 @@ class OwnUser extends User {
banned: user.banned,
extraData: user.extraData,
teams: user.teams,
+ language: user.language,
);
/// Creates a copy of [OwnUser] with specified attributes overridden.
@@ -73,6 +76,7 @@ class OwnUser extends User {
List? mutes,
int? totalUnreadCount,
int? unreadChannels,
+ String? language,
}) =>
OwnUser(
id: id ?? this.id,
@@ -89,15 +93,13 @@ class OwnUser extends User {
mutes: mutes ?? this.mutes,
totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount,
unreadChannels: unreadChannels ?? this.unreadChannels,
+ language: language ?? this.language,
);
/// Returns a new [OwnUser] that is a combination of this ownUser
/// and the given [other] ownUser.
OwnUser merge(OwnUser? other) {
- if (other == null) {
- return this;
- }
-
+ if (other == null) return this;
return copyWith(
banned: other.banned,
channelMutes: other.channelMutes,
@@ -113,6 +115,7 @@ class OwnUser extends User {
totalUnreadCount: other.totalUnreadCount,
unreadChannels: other.unreadChannels,
updatedAt: other.updatedAt,
+ language: other.language,
);
}
diff --git a/packages/stream_chat/lib/src/core/models/own_user.g.dart b/packages/stream_chat/lib/src/core/models/own_user.g.dart
index 465faff2..ca4acdea 100644
--- a/packages/stream_chat/lib/src/core/models/own_user.g.dart
+++ b/packages/stream_chat/lib/src/core/models/own_user.g.dart
@@ -39,5 +39,6 @@ OwnUser _$OwnUserFromJson(Map json) {
teams:
(json['teams'] as List?)?.map((e) => e as String).toList() ??
[],
+ language: json['language'] as String?,
);
}
diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart
index 1360d14e..a7e7a51e 100644
--- a/packages/stream_chat/lib/src/core/models/user.dart
+++ b/packages/stream_chat/lib/src/core/models/user.dart
@@ -18,6 +18,7 @@ class User extends Equatable {
this.extraData = const {},
this.banned = false,
this.teams = const [],
+ this.language,
}) : createdAt = createdAt ?? DateTime.now(),
updatedAt = updatedAt ?? DateTime.now();
@@ -36,6 +37,7 @@ class User extends Equatable {
'online',
'banned',
'teams',
+ 'language',
];
/// User id
@@ -82,8 +84,11 @@ class User extends Equatable {
)
final Map extraData;
- @override
- int get hashCode => id.hashCode;
+ /// The language this user prefers.
+ ///
+ /// Defaults to 'en'.
+ @JsonKey(includeIfNull: false)
+ final String? language;
/// Shortcut for user name
String get name {
@@ -98,11 +103,6 @@ class User extends Equatable {
static List? toIds(List? users) =>
users?.map((u) => u.id).toList();
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- other is User && runtimeType == other.runtimeType && id == other.id;
-
/// Serialize to json
Map toJson() => Serializer.moveFromExtraDataToRoot(
_$UserToJson(this),
@@ -119,6 +119,7 @@ class User extends Equatable {
Map? extraData,
bool? banned,
List? teams,
+ String? language,
}) =>
User(
id: id ?? this.id,
@@ -130,18 +131,9 @@ class User extends Equatable {
extraData: extraData ?? this.extraData,
banned: banned ?? this.banned,
teams: teams ?? this.teams,
+ language: language ?? this.language,
);
@override
- List get props => [
- id,
- role,
- teams,
- createdAt,
- updatedAt,
- lastActive,
- online,
- banned,
- extraData,
- ];
+ List get props => [id];
}
diff --git a/packages/stream_chat/lib/src/core/models/user.g.dart b/packages/stream_chat/lib/src/core/models/user.g.dart
index befcac03..dd3183d5 100644
--- a/packages/stream_chat/lib/src/core/models/user.g.dart
+++ b/packages/stream_chat/lib/src/core/models/user.g.dart
@@ -25,6 +25,7 @@ User _$UserFromJson(Map json) {
teams:
(json['teams'] as List?)?.map((e) => e as String).toList() ??
[],
+ language: json['language'] as String?,
);
}
@@ -47,5 +48,6 @@ Map _$UserToJson(User instance) {
writeNotNull('online', readonly(instance.online));
writeNotNull('banned', readonly(instance.banned));
val['extra_data'] = instance.extraData;
+ writeNotNull('language', instance.language);
return val;
}
diff --git a/packages/stream_chat/lib/src/ws/websocket.dart b/packages/stream_chat/lib/src/ws/websocket.dart
index f9d8a210..b26d811e 100644
--- a/packages/stream_chat/lib/src/ws/websocket.dart
+++ b/packages/stream_chat/lib/src/ws/websocket.dart
@@ -41,11 +41,15 @@ class WebSocket with TimerHelper {
this.reconnectionMonitorInterval = 10,
this.healthCheckInterval = 20,
this.reconnectionMonitorTimeout = 40,
+ this.queryParameters = const {},
}) : _logger = logger;
///
final String apiKey;
+ /// Additional query parameters to be added to the websocket url
+ final Map queryParameters;
+
/// WS base url
final String baseUrl;
@@ -156,6 +160,7 @@ class WebSocket with TimerHelper {
'api_key': apiKey,
'authorization': token.rawValue,
'stream-auth-type': token.authType.raw,
+ ...queryParameters,
};
final scheme = baseUrl.startsWith('https') ? 'wss' : 'ws';
final host = baseUrl.replaceAll(RegExp(r'(^\w+:|^)\/\/'), '');
diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart
index d09a2ccc..3633af13 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.0.0';
+const PACKAGE_VERSION = '2.1.1';
diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml
index 1f145992..12247397 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.0.0
+version: 2.1.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/fixtures/user.json b/packages/stream_chat/test/fixtures/user.json
index 49b972a3..b22c8553 100644
--- a/packages/stream_chat/test/fixtures/user.json
+++ b/packages/stream_chat/test/fixtures/user.json
@@ -1,5 +1,6 @@
{
"id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e",
"role": "test-role",
- "name": "John"
+ "name": "John",
+ "language": "en"
}
\ No newline at end of file
diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart
index 39003fb7..5be55491 100644
--- a/packages/stream_chat/test/src/api/channel_test.dart
+++ b/packages/stream_chat/test/src/api/channel_test.dart
@@ -1610,9 +1610,12 @@ void main() {
const messageId = 'test-message-id';
const language = 'hi'; // Hindi
const translatedMessageText = 'नमस्ते';
- final translatedMessage = TranslatedMessage(const {
- language: translatedMessageText,
- });
+
+ final translatedMessage = Message(
+ i18n: const {
+ language: translatedMessageText,
+ },
+ );
when(() => client.translateMessage(messageId, language)).thenAnswer(
(_) async => TranslateMessageResponse()..message = translatedMessage,
diff --git a/packages/stream_chat/test/src/api/client_test.dart b/packages/stream_chat/test/src/api/client_test.dart
index 423445ec..a1a3fc96 100644
--- a/packages/stream_chat/test/src/api/client_test.dart
+++ b/packages/stream_chat/test/src/api/client_test.dart
@@ -155,7 +155,7 @@ void main() {
group('`.openConnection`', () {
test('should throw if state does not contain user', () async {
- expect(client.state.user, isNull);
+ expect(client.state.currentUser, isNull);
try {
await client.openConnection();
} catch (e) {
@@ -164,7 +164,7 @@ void main() {
});
test('should throw if connection is already in progress', () async {
- expect(client.state.user, isNull);
+ expect(client.state.currentUser, isNull);
try {
await client.connectAnonymousUser();
await client.openConnection();
@@ -179,7 +179,7 @@ void main() {
});
test('should throw if connection is already available', () async {
- expect(client.state.user, isNull);
+ expect(client.state.currentUser, isNull);
try {
await client.connectAnonymousUser();
// waiting 300ms for `wsConnectionStatusStream` to emit
@@ -799,7 +799,7 @@ void main() {
});
test('`.disconnectUser` should reset state and user', () async {
- expect(client.state.user, isNotNull);
+ expect(client.state.currentUser, isNotNull);
expect(client.wsConnectionStatus, ConnectionStatus.connected);
expectLater(
@@ -810,7 +810,7 @@ void main() {
await client.disconnectUser();
- expect(client.state.user, isNull);
+ expect(client.state.currentUser, isNull);
expect(client.wsConnectionStatus, ConnectionStatus.disconnected);
});
});
@@ -2109,9 +2109,11 @@ void main() {
const messageId = 'test-message-id';
const language = 'hi'; // Hindi
const translatedMessageText = 'नमस्ते';
- final translatedMessage = TranslatedMessage(const {
- language: translatedMessageText,
- });
+ final translatedMessage = Message(
+ i18n: const {
+ language: translatedMessageText,
+ },
+ );
when(() => api.message.translateMessage(messageId, language)).thenAnswer(
(_) async => TranslateMessageResponse()..message = translatedMessage,
diff --git a/packages/stream_chat/test/src/core/api/message_api_test.dart b/packages/stream_chat/test/src/core/api/message_api_test.dart
index 1aacb6d2..e89491a2 100644
--- a/packages/stream_chat/test/src/core/api/message_api_test.dart
+++ b/packages/stream_chat/test/src/core/api/message_api_test.dart
@@ -371,9 +371,11 @@ void main() {
final path = '/messages/${message.id}/translate';
const translatedMessageText = 'नमस्ते';
- final translatedMessage = TranslatedMessage(const {
- language: translatedMessageText,
- });
+ final translatedMessage = Message(
+ i18n: const {
+ language: translatedMessageText,
+ },
+ );
when(() => client.post(
path,
diff --git a/packages/stream_chat/test/src/core/http/stream_http_client_options_test.dart b/packages/stream_chat/test/src/core/http/stream_http_client_options_test.dart
index a03434fa..02cfc07a 100644
--- a/packages/stream_chat/test/src/core/http/stream_http_client_options_test.dart
+++ b/packages/stream_chat/test/src/core/http/stream_http_client_options_test.dart
@@ -9,6 +9,8 @@ void main() {
expect(options.baseUrl, 'https://chat-us-east-1.stream-io-api.com');
expect(options.connectTimeout, const Duration(seconds: 6));
expect(options.receiveTimeout, const Duration(seconds: 6));
+ expect(options.queryParameters, const {});
+ expect(options.headers, const {});
});
test('should override all the default set params', () {
@@ -16,11 +18,15 @@ void main() {
baseUrl: 'base-url',
connectTimeout: Duration(seconds: 3),
receiveTimeout: Duration(seconds: 3),
+ headers: {'test': 'test'},
+ queryParameters: {'123': '123'},
);
expect(options.location, isNull);
expect(options.baseUrl, 'base-url');
expect(options.connectTimeout, const Duration(seconds: 3));
expect(options.receiveTimeout, const Duration(seconds: 3));
+ expect(options.headers, {'test': 'test'});
+ expect(options.queryParameters, {'123': '123'});
});
group('should create baseUrl according to provided location', () {
diff --git a/packages/stream_chat/test/src/core/http/stream_http_client_test.dart b/packages/stream_chat/test/src/core/http/stream_http_client_test.dart
index ab4c4984..7860a0d6 100644
--- a/packages/stream_chat/test/src/core/http/stream_http_client_test.dart
+++ b/packages/stream_chat/test/src/core/http/stream_http_client_test.dart
@@ -96,7 +96,7 @@ void main() {
await client.get('path');
} catch (_) {}
- verify(() => logger.info(any())).called(16);
+ verify(() => logger.info(any())).called(greaterThan(0));
});
test('loggingInterceptor should log error', () async {
@@ -108,7 +108,7 @@ void main() {
await client.get('path');
} catch (_) {}
- verify(() => logger.severe(any())).called(8);
+ verify(() => logger.severe(any())).called(greaterThan(0));
});
test('`.lock` should lock the dio client', () async {
diff --git a/packages/stream_chat/test/src/core/models/message_test.dart b/packages/stream_chat/test/src/core/models/message_test.dart
index a5571430..b5e6ddcc 100644
--- a/packages/stream_chat/test/src/core/models/message_test.dart
+++ b/packages/stream_chat/test/src/core/models/message_test.dart
@@ -28,6 +28,7 @@ void main() {
expect(message.pinnedAt, null);
expect(message.pinExpires, null);
expect(message.pinnedBy, null);
+ expect(message.i18n, null);
});
test('should serialize to json correctly', () {
diff --git a/packages/stream_chat/test/src/fakes.dart b/packages/stream_chat/test/src/fakes.dart
index 15d5991c..8d3f6d37 100644
--- a/packages/stream_chat/test/src/fakes.dart
+++ b/packages/stream_chat/test/src/fakes.dart
@@ -89,7 +89,7 @@ class FakeChatApi extends Fake implements StreamChatApi {
class FakeClientState extends Fake implements ClientState {
@override
- OwnUser? get user => OwnUser(id: 'test-user-id');
+ OwnUser? get currentUser => OwnUser(id: 'test-user-id');
@override
int totalUnreadCount = 0;
diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md
index 89e4ac44..de55a3ca 100644
--- a/packages/stream_chat_flutter/CHANGELOG.md
+++ b/packages/stream_chat_flutter/CHANGELOG.md
@@ -1,8 +1,24 @@
-## Upcoming
+## 2.1.1
-✅ Added
+- Updated core dependency
+## 2.1.0
+
+✅ Added
+
+- Added `MessageListView.paginationLimit`
+- `MessageText` renders message translation if available
- Allow the various ListView widgets to be themed via ThemeData classes
+- Added `bottomRowBuilder` and `deletedBottomRowBuilder` that build a widget below a `MessageWidget`
+
+🔄 Changed
+
+- `StreamChat.of(context).user` is now deprecated in favor of `StreamChat.of(context).currentUser`.
+- `StreamChat.of(context).userStream` is now deprecated in favor of `StreamChat.of(context).currentUserStream`.
+
+🐞 Fixed
+
+- Fix floating date divider not having a fixed size
## 2.0.0
diff --git a/packages/stream_chat_flutter/README.md b/packages/stream_chat_flutter/README.md
index 41ea079d..811135a6 100644
--- a/packages/stream_chat_flutter/README.md
+++ b/packages/stream_chat_flutter/README.md
@@ -14,7 +14,8 @@
- [Register](https://getstream.io/chat/trial/) to get an API key for Stream Chat
- [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/)
- [Chat UI Kit](https://getstream.io/chat/ui-kit/)
-
+- [UI Docs](https://getstream.io/chat/docs/sdk/flutter/stream_chat_flutter/introduction/)
+- [Chat Client Docs](https://getstream.io/chat/docs/flutter-dart/?language=dart)
### Changelog
diff --git a/packages/stream_chat_flutter/example/android/app/build.gradle b/packages/stream_chat_flutter/example/android/app/build.gradle
index 9bb36eac..fbd6268e 100644
--- a/packages/stream_chat_flutter/example/android/app/build.gradle
+++ b/packages/stream_chat_flutter/example/android/app/build.gradle
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
- compileSdkVersion 29
+ compileSdkVersion 30
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -41,7 +41,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
minSdkVersion 21
- targetSdkVersion 29
+ targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
diff --git a/packages/stream_chat_flutter/example/android/build.gradle b/packages/stream_chat_flutter/example/android/build.gradle
index 9afae7f8..3e0873de 100644
--- a/packages/stream_chat_flutter/example/android/build.gradle
+++ b/packages/stream_chat_flutter/example/android/build.gradle
@@ -1,12 +1,12 @@
buildscript {
- ext.kotlin_version = '1.3.50'
+ ext.kotlin_version = '1.5.20'
repositories {
google()
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.6.2'
+ classpath 'com.android.tools.build:gradle:4.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
diff --git a/packages/stream_chat_flutter/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/stream_chat_flutter/example/android/gradle/wrapper/gradle-wrapper.properties
index 493072b3..3df6b338 100644
--- a/packages/stream_chat_flutter/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/stream_chat_flutter/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart
index 6f038616..9279e4ff 100644
--- a/packages/stream_chat_flutter/example/lib/main.dart
+++ b/packages/stream_chat_flutter/example/lib/main.dart
@@ -1,11 +1,8 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
-import 'package:stream_chat_persistence/stream_chat_persistence.dart';
-
-/// A chat-persisted StreamChatClient
-final chatPersistentClient = StreamChatPersistenceClient(
- logLevel: Level.INFO,
-);
+import 'package:stream_chat_localizations/stream_chat_localizations.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -15,7 +12,7 @@ void main() async {
final client = StreamChatClient(
'kv7mcsxr24p8',
logLevel: Level.INFO,
- )..chatPersistenceClient = chatPersistentClient;
+ );
/// Set the current user and connect the websocket. In a production
/// scenario, this should be done using a backend to generate a user token
@@ -76,6 +73,13 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) => MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
+ supportedLocales: const [
+ Locale('en'),
+ Locale('hi'),
+ Locale('fr'),
+ Locale('it'),
+ ],
+ localizationsDelegates: GlobalStreamChatLocalizations.delegates,
builder: (context, widget) => StreamChat(
client: client,
child: widget,
diff --git a/packages/stream_chat_flutter/example/lib/split_view.dart b/packages/stream_chat_flutter/example/lib/split_view.dart
index 454071a6..52c0e885 100644
--- a/packages/stream_chat_flutter/example/lib/split_view.dart
+++ b/packages/stream_chat_flutter/example/lib/split_view.dart
@@ -103,7 +103,7 @@ class ChannelListPage extends StatelessWidget {
: null,
filter: Filter.in_(
'members',
- [StreamChat.of(context).user!.id],
+ [StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart
index 79f8674c..5fa9cfdb 100644
--- a/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart
+++ b/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart
@@ -80,7 +80,7 @@ class ChannelListPage extends StatelessWidget {
child: ChannelListView(
filter: Filter.in_(
'members',
- [StreamChat.of(context).user!.id],
+ [StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart
index 78081c85..9658eec4 100644
--- a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart
+++ b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart
@@ -81,7 +81,7 @@ class ChannelListPage extends StatelessWidget {
child: ChannelListView(
filter: Filter.in_(
'members',
- [StreamChat.of(context).user!.id],
+ [StreamChat.of(context).currentUser!.id],
),
channelPreviewBuilder: _channelPreviewBuilder,
// sort: [SortOption('last_message_at')],
diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart
index 07942302..df94cf06 100644
--- a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart
+++ b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart
@@ -66,7 +66,7 @@ class ChannelListPage extends StatelessWidget {
child: ChannelListView(
filter: Filter.in_(
'members',
- [StreamChat.of(context).user!.id],
+ [StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart
index dbbf31a0..b9089d6a 100644
--- a/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart
+++ b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart
@@ -72,7 +72,7 @@ class ChannelListPage extends StatelessWidget {
child: ChannelListView(
filter: Filter.in_(
'members',
- [StreamChat.of(context).user!.id],
+ [StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
@@ -115,7 +115,8 @@ class ChannelPage extends StatelessWidget {
MessageWidget _,
) {
final message = details.message;
- final isCurrentUser = StreamChat.of(context).user!.id == message.user!.id;
+ final isCurrentUser =
+ StreamChat.of(context).currentUser!.id == message.user!.id;
final textAlign = isCurrentUser ? TextAlign.right : TextAlign.left;
final color = isCurrentUser ? Colors.blueGrey : Colors.blue;
diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart
index fcf86b5c..cf94b568 100644
--- a/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart
+++ b/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart
@@ -99,7 +99,7 @@ class ChannelListPage extends StatelessWidget {
child: ChannelListView(
filter: Filter.in_(
'members',
- [StreamChat.of(context).user!.id],
+ [StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml
index d8b29d3f..d3fec032 100644
--- a/packages/stream_chat_flutter/example/pubspec.yaml
+++ b/packages/stream_chat_flutter/example/pubspec.yaml
@@ -33,6 +33,8 @@ dependencies:
# path: ../../stream_chat_flutter_core
stream_chat_flutter:
path: ../
+ stream_chat_localizations:
+ path: ../../stream_chat_localizations
stream_chat_persistence:
path: ../../stream_chat_persistence
diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart
index b1b29c4c..af46515e 100644
--- a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart
+++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/upload_progress_indicator.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Widget to build in progress
typedef InProgressBuilder = Widget Function(BuildContext, int, int);
@@ -226,7 +227,7 @@ class _FailedState extends StatelessWidget {
horizontal: 12,
),
child: Text(
- 'UPLOAD ERROR',
+ context.translations.uploadErrorLabel,
style: theme.textTheme.footnote.copyWith(
color: theme.colorTheme.barsBg,
),
diff --git a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart
index 99c3ec40..f57e1ee6 100644
--- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart
+++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart
@@ -7,9 +7,9 @@ import 'package:stream_chat_flutter/src/upload_progress_indicator.dart';
import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
-// ignore: always_use_package_imports
-import 'attachment_widget.dart';
+import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
/// Widget for displaying file attachments
class FileAttachment extends AttachmentWidget {
@@ -76,7 +76,7 @@ class FileAttachment extends AttachmentWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
- attachment.title ?? 'File',
+ attachment.title ?? context.translations.fileText,
style: StreamChatTheme.of(context).textTheme.bodyBold,
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -286,7 +286,10 @@ class FileAttachment extends AttachmentWidget {
progressIndicatorColor: theme.colorTheme.accentPrimary,
),
success: () => Text(fileSize(size), style: textStyle),
- failed: (_) => Text('UPLOAD ERROR', style: textStyle),
+ failed: (_) => Text(
+ context.translations.uploadErrorLabel,
+ style: textStyle,
+ ),
);
}
}
diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart
index f14abffc..7eb2606e 100644
--- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart
+++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart
@@ -2,8 +2,10 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
+import 'package:stream_chat_flutter/src/visible_footnote.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Widget for showing a GIF attachment
class GiphyAttachment extends AttachmentWidget {
@@ -71,9 +73,9 @@ class GiphyAttachment extends AttachmentWidget {
children: [
StreamSvgIcon.giphyIcon(),
const SizedBox(width: 8),
- const Text(
- 'Giphy',
- style: TextStyle(fontWeight: FontWeight.bold),
+ Text(
+ context.translations.giphyLabel,
+ style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 8),
if (attachment.title != null)
@@ -134,7 +136,7 @@ class GiphyAttachment extends AttachmentWidget {
});
},
child: Text(
- 'Cancel',
+ context.translations.cancelLabel.toLowerCase(),
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
@@ -166,7 +168,7 @@ class GiphyAttachment extends AttachmentWidget {
});
},
child: Text(
- 'Shuffle',
+ context.translations.shuffleLabel,
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
@@ -199,7 +201,7 @@ class GiphyAttachment extends AttachmentWidget {
});
},
child: Text(
- 'Send',
+ context.translations.sendLabel,
style: TextStyle(
color: StreamChatTheme.of(context)
.colorTheme
@@ -216,36 +218,11 @@ class GiphyAttachment extends AttachmentWidget {
),
),
const SizedBox(height: 4),
- Align(
+ const Align(
alignment: Alignment.centerRight,
child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- StreamSvgIcon.eye(
- color: StreamChatTheme.of(context)
- .colorTheme
- .textHighEmphasis
- .withOpacity(0.5),
- size: 16,
- ),
- const SizedBox(
- width: 8,
- ),
- Text(
- 'Only visible to you',
- style: StreamChatTheme.of(context)
- .textTheme
- .footnote
- .copyWith(
- color: StreamChatTheme.of(context)
- .colorTheme
- .textHighEmphasis
- .withOpacity(0.5)),
- ),
- ],
- ),
+ padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
+ child: VisibleFootnote(),
),
),
],
@@ -339,7 +316,7 @@ class GiphyAttachment extends AttachmentWidget {
size: 16,
),
Text(
- 'GIPHY',
+ context.translations.giphyLabel.toUpperCase(),
style: TextStyle(
color:
StreamChatTheme.of(context).colorTheme.barsBg,
diff --git a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart
index ea189b91..b5a3cc87 100644
--- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart
+++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart
@@ -48,7 +48,7 @@ class AttachmentActionsModal extends StatelessWidget {
child: _buildPage(context),
);
- Widget _buildPage(context) {
+ Widget _buildPage(BuildContext context) {
final theme = StreamChatTheme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
@@ -69,7 +69,7 @@ class AttachmentActionsModal extends StatelessWidget {
children: [
_buildButton(
context,
- 'Reply',
+ context.translations.replyLabel,
StreamSvgIcon.iconCurveLineLeftUp(
size: 24,
color: theme.colorTheme.textLowEmphasis,
@@ -80,7 +80,7 @@ class AttachmentActionsModal extends StatelessWidget {
),
_buildButton(
context,
- 'Show in Chat',
+ context.translations.showInChatLabel,
StreamSvgIcon.eye(
size: 24,
color: theme.colorTheme.textHighEmphasis,
@@ -89,8 +89,9 @@ class AttachmentActionsModal extends StatelessWidget {
),
_buildButton(
context,
- // ignore: lines_longer_than_80_chars
- 'Save ${message.attachments[currentIndex].type == 'video' ? 'Video' : 'Image'}',
+ message.attachments[currentIndex].type == 'video'
+ ? context.translations.saveVideoLabel
+ : context.translations.saveImageLabel,
StreamSvgIcon.iconSave(
size: 24,
color: theme.colorTheme.textLowEmphasis,
@@ -138,10 +139,11 @@ class AttachmentActionsModal extends StatelessWidget {
);
},
),
- if (StreamChat.of(context).user?.id == message.user?.id)
+ if (StreamChat.of(context).currentUser?.id ==
+ message.user?.id)
_buildButton(
context,
- 'Delete',
+ context.translations.deleteLabel.capitalize(),
StreamSvgIcon.delete(
size: 24,
color: theme.colorTheme.accentError,
diff --git a/packages/stream_chat_flutter/lib/src/channel_avatar.dart b/packages/stream_chat_flutter/lib/src/channel_avatar.dart
index 4ab6ba31..551fd278 100644
--- a/packages/stream_chat_flutter/lib/src/channel_avatar.dart
+++ b/packages/stream_chat_flutter/lib/src/channel_avatar.dart
@@ -141,7 +141,7 @@ class ChannelAvatar extends StatelessWidget {
return child;
}
- final currentUser = streamChat.user!;
+ final currentUser = streamChat.currentUser!;
final otherMembers = channel.state!.members
.where((it) => it.userId != currentUser.id)
.toList(growable: false);
@@ -149,7 +149,7 @@ class ChannelAvatar extends StatelessWidget {
// our own space, no other members
if (otherMembers.isEmpty) {
return BetterStreamBuilder(
- stream: streamChat.client.state.userStream.map((it) => it!),
+ stream: streamChat.client.state.currentUserStream.map((it) => it!),
initialData: currentUser,
builder: (context, user) => UserAvatar(
borderRadius: borderRadius ?? previewTheme?.borderRadius,
diff --git a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart
index 368bfbd8..a06da0a8 100644
--- a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart
+++ b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/channel_info.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Bottom Sheet with options
class ChannelBottomSheet extends StatefulWidget {
@@ -27,8 +28,8 @@ class _ChannelBottomSheetState extends State {
final members = channel.state?.members ?? [];
- final userAsMember =
- members.firstWhere((e) => e.user?.id == _streamChatState.user?.id);
+ final userAsMember = members
+ .firstWhere((e) => e.user?.id == _streamChatState.currentUser?.id);
final isOwner = userAsMember.role == 'owner';
return Material(
@@ -149,7 +150,7 @@ class _ChannelBottomSheetState extends State {
color: _streamChatThemeData.colorTheme.textLowEmphasis,
),
),
- title: 'View Info',
+ title: context.translations.viewInfoLabel,
onTap: widget.onViewInfoTap,
),
if (!channel.isDistinct)
@@ -160,7 +161,7 @@ class _ChannelBottomSheetState extends State {
color: _streamChatThemeData.colorTheme.textLowEmphasis,
),
),
- title: 'Leave Group',
+ title: context.translations.leaveGroupLabel,
onTap: () async {
setState(() {
_showActions = false;
@@ -179,7 +180,7 @@ class _ChannelBottomSheetState extends State {
color: _streamChatThemeData.colorTheme.accentError,
),
),
- title: 'Delete Conversation',
+ title: context.translations.deleteConversationLabel,
titleColor: _streamChatThemeData.colorTheme.accentError,
onTap: () async {
setState(() {
@@ -198,7 +199,7 @@ class _ChannelBottomSheetState extends State {
color: _streamChatThemeData.colorTheme.textLowEmphasis,
),
),
- title: 'Cancel',
+ title: context.translations.cancelLabel,
onTap: () {
Navigator.pop(context);
},
@@ -219,10 +220,10 @@ class _ChannelBottomSheetState extends State {
Future _showDeleteDialog() async {
final res = await showConfirmationDialog(
context,
- title: 'Delete Conversation',
- okText: 'DELETE',
- question: 'Are you sure you want to delete this conversation?',
- cancelText: 'CANCEL',
+ title: context.translations.deleteConversationLabel,
+ okText: context.translations.deleteLabel,
+ question: context.translations.deleteConversationQuestion,
+ cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.delete(
color: _streamChatThemeData.colorTheme.accentError,
),
@@ -237,17 +238,17 @@ class _ChannelBottomSheetState extends State {
Future _showLeaveDialog() async {
final res = await showConfirmationDialog(
context,
- title: 'Leave conversation',
- okText: 'LEAVE',
- question: 'Are you sure you want to leave this conversation?',
- cancelText: 'CANCEL',
+ title: context.translations.leaveConversationLabel,
+ okText: context.translations.leaveLabel,
+ question: context.translations.leaveConversationQuestion,
+ cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.userRemove(
color: _streamChatThemeData.colorTheme.accentError,
),
);
if (res == true) {
final channel = _streamChannelState.channel;
- final user = _streamChatState.user;
+ final user = _streamChatState.currentUser;
if (user != null) {
await channel.removeMembers([user.id]);
}
diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart
index 8f586516..5f6275a8 100644
--- a/packages/stream_chat_flutter/lib/src/channel_header.dart
+++ b/packages/stream_chat_flutter/lib/src/channel_header.dart
@@ -6,6 +6,7 @@ import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// 
/// 
@@ -121,14 +122,14 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
switch (status) {
case ConnectionStatus.connected:
- statusString = 'Connected';
+ statusString = context.translations.connectedLabel;
showStatus = false;
break;
case ConnectionStatus.connecting:
- statusString = 'Reconnecting...';
+ statusString = context.translations.reconnectingLabel;
break;
case ConnectionStatus.disconnected:
- statusString = 'Disconnected';
+ statusString = context.translations.disconnectedLabel;
break;
}
diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart
index 3be26ec8..67a59782 100644
--- a/packages/stream_chat_flutter/lib/src/channel_info.dart
+++ b/packages/stream_chat_flutter/lib/src/channel_info.dart
@@ -2,6 +2,7 @@ import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Widget which shows channel info
class ChannelInfo extends StatelessWidget {
@@ -55,10 +56,13 @@ class ChannelInfo extends StatelessWidget {
) {
Widget? alternativeWidget;
- if (channel.memberCount != null && channel.memberCount! > 2) {
- var text = '${channel.memberCount} Members';
+ final memberCount = channel.memberCount;
+ if (memberCount != null && memberCount > 2) {
+ var text = context.translations.membersCountText(memberCount);
final watcherCount = channel.state?.watcherCount ?? 0;
- if (watcherCount > 0) text += ' $watcherCount Online';
+ if (watcherCount > 0) {
+ text += ' ${context.translations.watchersCountText(watcherCount)}';
+ }
alternativeWidget = Text(
text,
style: StreamChatTheme.of(context)
@@ -67,7 +71,7 @@ class ChannelInfo extends StatelessWidget {
.subtitle,
);
} else {
- final userId = StreamChat.of(context).user?.id;
+ final userId = StreamChat.of(context).currentUser?.id;
final otherMember = members?.firstWhereOrNull(
(element) => element.userId != userId,
);
@@ -75,12 +79,13 @@ class ChannelInfo extends StatelessWidget {
if (otherMember != null) {
if (otherMember.user?.online == true) {
alternativeWidget = Text(
- 'Online',
+ context.translations.userOnlineText,
style: textStyle,
);
} else {
alternativeWidget = Text(
- 'Last seen ${Jiffy(otherMember.user?.lastActive).fromNow()}',
+ '${context.translations.userLastOnlineText} '
+ '${Jiffy(otherMember.user?.lastActive).fromNow()}',
style: textStyle,
);
}
@@ -111,7 +116,7 @@ class ChannelInfo extends StatelessWidget {
),
const SizedBox(width: 10),
Text(
- 'Searching for Network',
+ context.translations.searchingForNetworkText,
style: textStyle,
),
],
@@ -125,7 +130,7 @@ class ChannelInfo extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
- 'Offline...',
+ context.translations.offlineLabel,
style: textStyle,
),
TextButton(
@@ -141,7 +146,7 @@ class ChannelInfo extends StatelessWidget {
..closeConnection()
..openConnection(),
child: Text(
- 'Try Again',
+ context.translations.tryAgainLabel,
style: textStyle?.copyWith(
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
),
diff --git a/packages/stream_chat_flutter/lib/src/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel_list_header.dart
index 0db31f80..835f51a2 100644
--- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart
+++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart
@@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Widget builder for title
typedef TitleBuilder = Widget Function(
@@ -95,7 +96,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) {
final _client = client ?? StreamChat.of(context).client;
- final user = _client.state.user;
+ final user = _client.state.currentUser;
return ConnectionStatusBuilder(
statusBuilder: (context, status) {
var statusString = '';
@@ -103,21 +104,20 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
switch (status) {
case ConnectionStatus.connected:
- statusString = 'Connected';
+ statusString = context.translations.connectedLabel;
showStatus = false;
break;
case ConnectionStatus.connecting:
- statusString = 'Reconnecting...';
+ statusString = context.translations.reconnectingLabel;
break;
case ConnectionStatus.disconnected:
- statusString = 'Disconnected';
+ statusString = context.translations.disconnectedLabel;
break;
}
final chatThemeData = StreamChatTheme.of(context);
return InfoTile(
- // ignore: avoid_bool_literals_in_conditional_expressions
- showMessage: showConnectionStateTile ? showStatus : false,
+ showMessage: showConnectionStateTile && showStatus,
message: statusString,
child: AppBar(
textTheme: Theme.of(context).textTheme,
@@ -207,7 +207,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
Widget _buildConnectedTitleState(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return Text(
- 'Stream Chat',
+ context.translations.streamChatLabel,
style: chatThemeData.textTheme.headlineBold.copyWith(
color: chatThemeData.colorTheme.textHighEmphasis,
),
@@ -226,7 +226,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
),
const SizedBox(width: 10),
Text(
- 'Searching for Network',
+ context.translations.searchingForNetworkText,
style: StreamChatTheme.of(context)
.channelListHeaderTheme
.title
@@ -247,7 +247,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
- 'Offline...',
+ context.translations.offlineLabel,
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
@@ -258,7 +258,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
..closeConnection()
..openConnection(),
child: Text(
- 'Try Again',
+ context.translations.tryAgainLabel,
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart
index e3340ec7..42c7420b 100644
--- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart
+++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart
@@ -7,6 +7,7 @@ import 'package:stream_chat_flutter/src/channel_bottom_sheet.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Callback called when tapping on a channel
typedef ChannelTapCallback = void Function(Channel, Widget?);
@@ -301,7 +302,7 @@ class _ChannelListViewState extends State {
Padding(
padding: const EdgeInsets.all(8),
child: Text(
- 'Let’s start chatting!',
+ context.translations.letsStartChattingLabel,
style: chatThemeData.textTheme.headline,
),
),
@@ -311,7 +312,7 @@ class _ChannelListViewState extends State {
horizontal: 52,
),
child: Text(
- 'How about sending your first message to a friend?',
+ context.translations.sendingFirstMessageLabel,
textAlign: TextAlign.center,
style: chatThemeData.textTheme.body.copyWith(
color: chatThemeData.colorTheme.textLowEmphasis,
@@ -330,7 +331,7 @@ class _ChannelListViewState extends State {
child: TextButton(
onPressed: widget.onStartChatPressed,
child: Text(
- 'Start a chat',
+ context.translations.startAChatLabel,
style: chatThemeData.textTheme.bodyBold.copyWith(
color: chatThemeData.colorTheme.accentPrimary,
),
@@ -466,9 +467,9 @@ class _ChannelListViewState extends State {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text.rich(
- const TextSpan(
+ TextSpan(
children: [
- WidgetSpan(
+ const WidgetSpan(
child: Padding(
padding: EdgeInsets.only(
right: 2,
@@ -476,14 +477,14 @@ class _ChannelListViewState extends State {
child: Icon(Icons.error_outline),
),
),
- TextSpan(text: 'Error loading channels'),
+ TextSpan(text: context.translations.loadingChannelsError),
],
),
style: Theme.of(context).textTheme.headline6,
),
TextButton(
onPressed: () => _channelListController.loadData!(),
- child: const Text('Retry'),
+ child: Text(context.translations.retryLabel),
),
],
),
@@ -552,7 +553,7 @@ class _ChannelListViewState extends State {
'owner',
].contains(channel.state!.members
.firstWhereOrNull(
- (m) => m.userId == channel.client.state.user?.id)
+ (m) => m.userId == channel.client.state.currentUser?.id)
?.role))
IconSlideAction(
color: backgroundColor,
@@ -561,17 +562,16 @@ class _ChannelListViewState extends State {
),
onTap: widget.onDeletePressed != null
? () {
- widget.onDeletePressed!(channel);
+ widget.onDeletePressed?.call(channel);
}
: () async {
final res = await showConfirmationDialog(
context,
- title: 'Delete Conversation',
- okText: 'DELETE',
+ title: context.translations.deleteConversationLabel,
question:
- // ignore: lines_longer_than_80_chars
- 'Are you sure you want to delete this conversation?',
- cancelText: 'CANCEL',
+ context.translations.deleteConversationQuestion,
+ okText: context.translations.deleteLabel,
+ cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.delete(
color: chatThemeData.colorTheme.accentError,
),
@@ -668,7 +668,7 @@ class _ChannelListViewState extends State {
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
- 'Error loading channels',
+ context.translations.loadingChannelsError,
style: theme.textTheme.body.copyWith(
color: Colors.white,
),
diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart
index 4819ecc7..4ae4f935 100644
--- a/packages/stream_chat_flutter/lib/src/channel_name.dart
+++ b/packages/stream_chat_flutter/lib/src/channel_name.dart
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// It shows the current [Channel] name using a [Text] widget.
///
@@ -44,10 +45,12 @@ class ChannelName extends StatelessWidget {
) =>
LayoutBuilder(
builder: (context, constraints) {
- var title = 'No title';
- if (extraData['name'] == null) {
- final otherMembers =
- members?.where((member) => member.userId != client.user!.id);
+ var title = context.translations.noTitleText;
+ if (extraData['name'] != null) {
+ title = extraData['name'];
+ } else {
+ final otherMembers = members
+ ?.where((member) => member.userId != client.currentUser!.id);
if (otherMembers?.length == 1) {
if (otherMembers!.first.user != null) {
title = otherMembers.first.user!.name;
@@ -71,8 +74,6 @@ class ChannelName extends StatelessWidget {
title = '${currentMembers.map((e) => e.user?.name).join(', ')} '
'${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
}
- } else {
- title = extraData['name'];
}
return Text(
diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart
index bf615248..7fc59c7f 100644
--- a/packages/stream_chat_flutter/lib/src/channel_preview.dart
+++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart
@@ -6,6 +6,7 @@ import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// 
/// 
@@ -101,7 +102,7 @@ class ChannelPreview extends StatelessWidget {
if (members?.isEmpty == true ||
members?.any((Member e) =>
e.user!.id ==
- channel.client.state.user?.id) !=
+ channel.client.state.currentUser?.id) !=
true) {
return const SizedBox();
}
@@ -124,7 +125,7 @@ class ChannelPreview extends StatelessWidget {
(m) => !m.isDeleted && m.shadowed != true,
);
if (lastMessage?.user?.id ==
- streamChatState.user?.id) {
+ streamChatState.currentUser?.id) {
return Padding(
padding: const EdgeInsets.only(right: 4),
child: SendingIndicator(
@@ -133,7 +134,8 @@ class ChannelPreview extends StatelessWidget {
isMessageRead: channel.state!.read
?.where((element) =>
element.user.id !=
- channel.client.state.user!.id)
+ channel
+ .client.state.currentUser!.id)
.where((element) => element.lastRead
.isAfter(lastMessage.createdAt))
.isNotEmpty ==
@@ -172,7 +174,7 @@ class ChannelPreview extends StatelessWidget {
startOfDay
.subtract(const Duration(days: 1))
.millisecondsSinceEpoch) {
- stringDate = 'Yesterday';
+ stringDate = context.translations.yesterdayLabel;
} else if (startOfDay.difference(lastMessageAt).inDays < 7) {
stringDate = Jiffy(lastMessageAt.toLocal()).EEEE;
} else {
@@ -197,7 +199,7 @@ class ChannelPreview extends StatelessWidget {
size: 16,
),
Text(
- ' Channel is muted',
+ ' ${context.translations.channelIsMutedText}',
style: chatThemeData.channelPreviewTheme.subtitle,
),
],
diff --git a/packages/stream_chat_flutter/lib/src/date_divider.dart b/packages/stream_chat_flutter/lib/src/date_divider.dart
index 8fff2ade..3d85299c 100644
--- a/packages/stream_chat_flutter/lib/src/date_divider.dart
+++ b/packages/stream_chat_flutter/lib/src/date_divider.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// It shows a date divider depending on the date difference
class DateDivider extends StatelessWidget {
@@ -24,10 +25,10 @@ class DateDivider extends StatelessWidget {
String dayInfo;
if (Jiffy(createdAt).isSame(now, Units.DAY)) {
- dayInfo = 'Today';
+ dayInfo = context.translations.todayLabel;
} else if (Jiffy(createdAt)
.isSame(now.subtract(const Duration(days: 1)), Units.DAY)) {
- dayInfo = 'Yesterday';
+ dayInfo = context.translations.yesterdayLabel;
} else if (Jiffy(createdAt).isAfter(
now.subtract(const Duration(days: 7)),
Units.DAY,
diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart
index 011b0c72..0a6c337a 100644
--- a/packages/stream_chat_flutter/lib/src/deleted_message.dart
+++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Widget to display deleted message
class DeletedMessage extends StatelessWidget {
@@ -49,7 +50,7 @@ class DeletedMessage extends StatelessWidget {
horizontal: 16,
),
child: Text(
- 'Message deleted',
+ context.translations.messageDeletedLabel,
style: messageTheme.messageText?.copyWith(
fontStyle: FontStyle.italic,
color: messageTheme.createdAt?.color,
diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart
index 5161c8e8..b975941e 100644
--- a/packages/stream_chat_flutter/lib/src/extension.dart
+++ b/packages/stream_chat_flutter/lib/src/extension.dart
@@ -2,6 +2,7 @@ import 'package:characters/characters.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/emoji/emoji.dart';
+import 'package:stream_chat_flutter/src/localization/translations.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
final _emojiChars = Emoji.chars();
@@ -9,7 +10,8 @@ final _emojiChars = Emoji.chars();
/// String extension
extension StringExtension on String {
/// Returns the capitalized string
- String capitalize() => '${this[0].toUpperCase()}${substring(1)}';
+ String capitalize() =>
+ '${this[0].toUpperCase()}${substring(1).toLowerCase()}';
/// Returns whether the string contains only emoji's or not.
///
@@ -103,6 +105,11 @@ extension BuildContextX on BuildContext {
// ignore: public_member_api_docs
double get textScaleFactor =>
MediaQuery.maybeOf(this)?.textScaleFactor ?? 1.0;
+
+ /// Retrieves current translations according to locale
+ /// Defaults to [DefaultTranslations]
+ Translations get translations =>
+ StreamChatLocalizations.of(this) ?? DefaultTranslations.instance;
}
/// Extension on [BorderRadius]
diff --git a/packages/stream_chat_flutter/lib/src/full_screen_media.dart b/packages/stream_chat_flutter/lib/src/full_screen_media.dart
index 0f6101b9..5e498a32 100644
--- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart
+++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart
@@ -4,12 +4,12 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
-import 'package:jiffy/jiffy.dart';
import 'package:photo_view/photo_view.dart';
import 'package:stream_chat_flutter/src/gallery_footer.dart';
import 'package:stream_chat_flutter/src/gallery_header.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_player/video_player.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Return action for coming back from pages
enum ReturnActionType {
@@ -182,9 +182,10 @@ class _FullScreenMediaState extends State
children: [
GalleryHeader(
userName: widget.userName,
- sentAt:
- // ignore: lines_longer_than_80_chars
- 'Sent ${getDay(widget.message.createdAt.toLocal())} at ${Jiffy(widget.message.createdAt.toLocal()).format('HH:mm')}',
+ sentAt: context.translations.sentAtText(
+ date: widget.message.createdAt,
+ time: widget.message.createdAt,
+ ),
onBackPressed: () {
Navigator.of(context).pop();
},
@@ -197,7 +198,7 @@ class _FullScreenMediaState extends State
);
},
),
- if (widget.message.type != 'ephemeral')
+ if (!widget.message.isEphemeral)
GalleryFooter(
currentPage: _currentPage,
totalPages: widget.mediaAttachments.length,
@@ -222,22 +223,6 @@ class _FullScreenMediaState extends State
),
);
- String getDay(DateTime dateTime) {
- final now = DateTime.now();
-
- if (DateTime(dateTime.year, dateTime.month, dateTime.day) ==
- DateTime(now.year, now.month, now.day)) {
- return 'today';
- } else if (DateTime(now.year, now.month, now.day)
- .difference(dateTime)
- .inHours <
- 24) {
- return 'yesterday';
- } else {
- return 'on ${Jiffy(dateTime).MMMd}';
- }
- }
-
@override
void dispose() async {
for (final package in videoPackages.values) {
diff --git a/packages/stream_chat_flutter/lib/src/gallery_footer.dart b/packages/stream_chat_flutter/lib/src/gallery_footer.dart
index c26c8dbc..03d575ae 100644
--- a/packages/stream_chat_flutter/lib/src/gallery_footer.dart
+++ b/packages/stream_chat_flutter/lib/src/gallery_footer.dart
@@ -11,6 +11,7 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Footer widget for media display
class GalleryFooter extends StatefulWidget implements PreferredSizeWidget {
@@ -135,7 +136,9 @@ class _GalleryFooterState extends State {
mainAxisSize: MainAxisSize.min,
children: [
Text(
- '${widget.currentPage + 1} of ${widget.totalPages}',
+ '${widget.currentPage + 1} '
+ '${context.translations.ofText} '
+ '${widget.totalPages}',
style: galleryFooterThemeData.titleTextStyle,
),
],
@@ -191,7 +194,7 @@ class _GalleryFooterState extends State {
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
- 'Photos',
+ context.translations.photosLabel,
style:
galleryFooterThemeData.bottomSheetPhotosTextStyle,
),
diff --git a/packages/stream_chat_flutter/lib/src/gallery_header.dart b/packages/stream_chat_flutter/lib/src/gallery_header.dart
index 6c4ecfee..e0852800 100644
--- a/packages/stream_chat_flutter/lib/src/gallery_header.dart
+++ b/packages/stream_chat_flutter/lib/src/gallery_header.dart
@@ -67,7 +67,7 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
: const SizedBox(),
backgroundColor: galleryHeaderThemeData.backgroundColor,
actions: [
- if (message.type != 'ephemeral')
+ if (!message.isEphemeral)
IconButton(
icon: StreamSvgIcon.iconMenuPoint(
color: galleryHeaderThemeData.iconMenuPointColor,
@@ -78,7 +78,7 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
),
],
centerTitle: true,
- title: message.type != 'ephemeral'
+ title: !message.isEphemeral
? InkWell(
onTap: onTitleTap,
child: SizedBox(
diff --git a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart
new file mode 100644
index 00000000..3a6aba10
--- /dev/null
+++ b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart
@@ -0,0 +1,258 @@
+import 'dart:math';
+import 'dart:ui';
+import 'dart:ui' as ui;
+
+import 'package:flutter/material.dart';
+
+/// Fallback user avatar with a polygon gradient overlayed with text
+class GradientAvatar extends StatefulWidget {
+ /// Constructor for [GradientAvatar]
+ const GradientAvatar({
+ Key? key,
+ required this.name,
+ required this.userId,
+ }) : super(key: key);
+
+ /// Name of user to shorten and display
+ final String name;
+
+ /// ID of user to be used for key
+ final String userId;
+
+ @override
+ _GradientAvatarState createState() => _GradientAvatarState();
+}
+
+class _GradientAvatarState extends State {
+ @override
+ Widget build(BuildContext context) => Center(
+ child: RepaintBoundary(
+ child: CustomPaint(
+ painter: DemoPainter(
+ widget.userId,
+ getShortenedName(widget.name),
+ DefaultTextStyle.of(context).style.fontFamily ?? 'Roboto',
+ ),
+ child: const SizedBox.expand(),
+ ),
+ ),
+ );
+
+ String getShortenedName(String name) {
+ var parts = name.split(' ')..removeWhere((e) => e == '');
+
+ if (parts.length > 2) {
+ parts = parts.take(2).toList();
+ }
+
+ var result = '';
+
+ for (var i = 0; i < parts.length; i++) {
+ result = result + parts[i][0].toUpperCase();
+ }
+
+ return result;
+ }
+}
+
+/// Painter for bg polygon gradient
+class DemoPainter extends CustomPainter {
+ /// Constructor for [DemoPainter]
+ DemoPainter(
+ this.userId,
+ this.username,
+ this.fontFamily,
+ );
+
+ /// Init grid row count
+ static const int rowCount = 5;
+
+ /// Init grid column count
+ static const int columnCount = 5;
+
+ /// User ID used for key
+ String userId;
+
+ /// User name to display
+ String username;
+
+ /// Font family to use
+ String fontFamily;
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ final rowUnit = size.width / columnCount;
+ final columnUnit = size.height / rowCount;
+ final rand = Random(userId.length);
+
+ final squares = [];
+ final points = {};
+ final gradient = colorGradients[rand.nextInt(colorGradients.length)];
+
+ for (var i = 0; i < rowCount; i++) {
+ for (var j = 0; j < columnCount; j++) {
+ final off1 = Offset(rowUnit * j, columnUnit * i);
+ final off2 = Offset(rowUnit * (j + 1), columnUnit * i);
+ final off3 = Offset(rowUnit * (j + 1), columnUnit * (i + 1));
+ final off4 = Offset(rowUnit * j, columnUnit * (i + 1));
+
+ points.addAll([off1, off2, off3, off4]);
+
+ final pointsList = points.toList();
+
+ final p1 = pointsList.indexOf(off1);
+ final p2 = pointsList.indexOf(off2);
+ final p3 = pointsList.indexOf(off3);
+ final p4 = pointsList.indexOf(off4);
+
+ squares.add(
+ Offset4(p1, p2, p3, p4, i, j, rowCount, columnCount, gradient));
+ }
+ }
+
+ final list = transformPoints(points, size);
+ squares.forEach((e) => e.draw(canvas, list));
+
+ final smallerSide = size.width > size.height ? size.width : size.height;
+
+ final textSize = smallerSide / 3;
+
+ final dxShift = (username.length == 2 ? 1.45 : 0.9) * textSize / 2;
+ final dyShift = (username.length == 2 ? 1.0 : 1.65) * textSize / 2;
+
+ final fontSize = username.length == 2 ? textSize : textSize * 1.5;
+
+ TextPainter(
+ text: TextSpan(
+ text: username,
+ style: TextStyle(
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontWeight: FontWeight.w500,
+ color: Colors.white.withOpacity(0.7),
+ ),
+ ),
+ textAlign: TextAlign.center,
+ textDirection: TextDirection.ltr)
+ ..layout(maxWidth: size.width)
+ ..paint(
+ canvas,
+ Offset(
+ (size.width / 2) - dxShift,
+ (size.height / 2) - dyShift,
+ ),
+ );
+ }
+
+ @override
+ bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
+
+ /// Transforms initial grid into a polygon grid
+ List transformPoints(Set points, Size size) {
+ final transformedList = [];
+ final orgList = points.toList();
+ final rand = Random(userId.length);
+
+ for (var i = 0; i < points.length; i++) {
+ final orgDx = orgList[i].dx;
+ final orgDy = orgList[i].dy;
+
+ if (orgDx == 0 ||
+ orgDy == 0 ||
+ orgDx == size.width ||
+ orgDy == size.height) {
+ transformedList.add(Offset(orgDx, orgDy));
+ continue;
+ }
+
+ final sign1 = rand.nextInt(2) == 1 ? 1 : -1;
+ final sign2 = rand.nextInt(2) == 1 ? 1 : -1;
+
+ final dx = 0.6 * sign1 * rand.nextInt(size.width ~/ columnCount);
+ final dy = 0.6 * sign2 * rand.nextInt(size.height ~/ rowCount);
+
+ transformedList.add(Offset(orgDx + dx, orgDy + dy));
+ }
+
+ return transformedList;
+ }
+}
+
+/// Class for storing and drawing four points of a polygon
+class Offset4 {
+ /// Constructor for [Offset4]
+ Offset4(
+ this.p1,
+ this.p2,
+ this.p3,
+ this.p4,
+ this.row,
+ this.column,
+ this.rowSize,
+ this.colSize,
+ this.gradient,
+ );
+
+ /// Point 1
+ int p1;
+
+ /// Point 2
+ int p2;
+
+ /// Point 3
+ int p3;
+
+ /// Point 4
+ int p4;
+
+ /// Position of polygon on grid
+ int row;
+
+ /// Position of polygon on grid
+ int column;
+
+ /// Max row size
+ int rowSize;
+
+ /// Max col size
+ int colSize;
+
+ /// Gradient to be applied to polygon
+ List gradient;
+
+ /// Draw the polygon on canvas
+ void draw(Canvas canvas, List points) {
+ final paint = Paint()
+ ..color = Color.fromARGB(255, Random().nextInt(255),
+ Random().nextInt(255), Random().nextInt(255))
+ ..shader = ui.Gradient.linear(
+ points[p1],
+ points[p3],
+ gradient,
+ );
+
+ final backgroundPath = Path()
+ ..moveTo(points[p1].dx, points[p1].dy)
+ ..lineTo(points[p2].dx, points[p2].dy)
+ ..lineTo(points[p3].dx, points[p3].dy)
+ ..lineTo(points[p4].dx, points[p4].dy)
+ ..lineTo(points[p1].dx, points[p1].dy)
+ ..close();
+
+ canvas.drawPath(backgroundPath, paint);
+ }
+}
+
+/// Gradient list for polygons
+const colorGradients = [
+ [Color(0xffffafbd), Color(0xffffc3a0)],
+ [Color(0xff2193b0), Color(0xff6dd5ed)],
+ [Color(0xffcc2b5e), Color(0xff753a88)],
+ [Color(0xffee9ca7), Color(0xffffdde1)],
+ [Color(0xff42275a), Color(0xff734b6d)],
+ [Color(0xffde6262), Color(0xffffb88c)],
+ [Color(0xff56ab2f), Color(0xffa8e063)],
+ [Color(0xff614385), Color(0xff516395)],
+ [Color(0xffeacda3), Color(0xffd6ae7b)],
+ [Color(0xff02aab0), Color(0xff00cdac)],
+];
diff --git a/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart b/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart
new file mode 100644
index 00000000..e9f8cf4b
--- /dev/null
+++ b/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart
@@ -0,0 +1,36 @@
+import 'package:flutter/widgets.dart';
+
+import 'package:stream_chat_flutter/src/localization/translations.dart'
+ show Translations;
+
+/// Defines the localized resource values used by the StreamChatFlutter widgets.
+///
+/// See also:
+///
+/// * [GlobalStreamChatLocalizations], which provides stream chat localizations
+/// for many languages.
+abstract class StreamChatLocalizations implements Translations {
+ /// The `StreamChatLocalizations` from the closest [Localizations] instance
+ /// that encloses the given context.
+ ///
+ /// If no [StreamChatLocalizations] are available in the given `context`, this
+ /// method returns null.
+ ///
+ /// This method is just a convenient shorthand for:
+ /// `Localizations.of(
+ /// context,
+ /// StreamChatLocalizations
+ /// )`.
+ ///
+ /// References to the localized resources defined by this class are typically
+ /// written in terms of this method. For example:
+ ///
+ /// ```dart
+ /// tooltip: StreamChatLocalizations.of(context).streamChatLabel,
+ /// ```
+ static StreamChatLocalizations? of(BuildContext context) =>
+ Localizations.of(
+ context,
+ StreamChatLocalizations,
+ );
+}
diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart
new file mode 100644
index 00000000..d6e5add0
--- /dev/null
+++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart
@@ -0,0 +1,667 @@
+import 'package:jiffy/jiffy.dart';
+import 'package:stream_chat_flutter/src/connection_status_builder.dart';
+import 'package:stream_chat_flutter/src/message_input.dart';
+import 'package:stream_chat_flutter/src/message_list_view.dart';
+import 'package:stream_chat_flutter/src/message_search_list_view.dart';
+import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'
+ show User;
+
+/// Translation strings for the stream chat widgets
+abstract class Translations {
+ /// The error shown when [launchURL] fails
+ String get launchUrlError;
+
+ /// The error shown when loading users fails
+ String get loadingUsersError;
+
+ /// The label for "retry" button
+ String get retryLabel;
+
+ /// The label for showing no users
+ String get noUsersLabel;
+
+ /// The text for showing user is online
+ String get userOnlineText;
+
+ /// The text for showing the last online of the user
+ String get userLastOnlineText;
+
+ /// The text shown when [users] starts typing
+ String userTypingText(Iterable users);
+
+ /// The label for "thread reply"
+ String get threadReplyLabel;
+
+ /// The text for showing if the message is only visible to you
+ String get onlyVisibleToYouText;
+
+ /// The text for showing the thread reply count
+ String threadReplyCountText(int count);
+
+ /// The text for showing the attachments upload progress
+ String attachmentsUploadProgressText({
+ required int remaining,
+ required int total,
+ });
+
+ /// The text for showing who pinned the message
+ String pinnedByUserText({
+ required User pinnedBy,
+ required User currentUser,
+ });
+
+ /// The text for showing there are empty messages
+ String get emptyMessagesText;
+
+ /// The text for showing generic error
+ String get genericErrorText;
+
+ /// The error shown when loading messages fails
+ String get loadingMessagesError;
+
+ /// The text for showing the result count in [MessageSearchListView]
+ String resultCountText(int count);
+
+ /// The text for showing the message is deleted
+ String get messageDeletedText;
+
+ /// The label for message deleted
+ String get messageDeletedLabel;
+
+ /// The label for message reactions
+ String get messageReactionsLabel;
+
+ /// The text for showing there are no chats
+ String get emptyChatMessagesText;
+
+ /// The text for showing the thread separator in case [MessageListView]
+ /// contains a parent message
+ String threadSeparatorText(int replyCount);
+
+ /// The label for "connected" in [ConnectionStatusBuilder]
+ String get connectedLabel;
+
+ /// The label for "disconnected" in [ConnectionStatusBuilder]
+ String get disconnectedLabel;
+
+ /// The label for "reconnecting" in [ConnectionStatusBuilder]
+ String get reconnectingLabel;
+
+ /// The label for also send as direct message "checkbox"" in [MessageInput]
+ String get alsoSendAsDirectMessageLabel;
+
+ /// The label for search Gif
+ String get searchGifLabel;
+
+ /// The label for add a comment or send in case of
+ /// attachments inside [MessageInput]
+ String get addACommentOrSendLabel;
+
+ /// The label for write a message in [MessageInput]
+ String get writeAMessageLabel;
+
+ /// The label for instant commands in [MessageInput]
+ String get instantCommandsLabel;
+
+ /// The error shown in case the fi"le is too large even after compression
+ /// while uploading via [MessageInput]
+ String fileTooLargeAfterCompressionError(double limitInMB);
+
+ /// The error shown in case the file is too large
+ /// while uploading via [MessageInput]
+ String fileTooLargeError(double limitInMB);
+
+ /// The text for showing the query while searching for emojis
+ String emojiMatchingQueryText(String query);
+
+ /// The label for "add a file"
+ String get addAFileLabel;
+
+ /// The label for "upload a photo"
+ String get uploadAPhotoLabel;
+
+ /// The label for "upload a video"
+ String get uploadAVideoLabel;
+
+ /// The label for "photo from camera"
+ String get photoFromCameraLabel;
+
+ /// The label for "video from camera"
+ String get videoFromCameraLabel;
+
+ /// The label for "upload a file"
+ String get uploadAFileLabel;
+
+ /// The error shown when something went wrong
+ String get somethingWentWrongError;
+
+ /// The label for "OK"
+ String get okLabel;
+
+ /// The label for "add more files"
+ String get addMoreFilesLabel;
+
+ /// The message shown for asking photo and video access permission
+ String get enablePhotoAndVideoAccessMessage;
+
+ /// The message shown for asking gallery access permission
+ String get allowGalleryAccessMessage;
+
+ /// The label for "flag message"
+ String get flagMessageLabel;
+
+ /// The question asked while showing flag message dialog
+ String get flagMessageQuestion;
+
+ /// The label for "Flag"
+ String get flagLabel;
+
+ /// The label for "Cancel"
+ String get cancelLabel;
+
+ /// The label for successful message flag
+ String get flagMessageSuccessfulLabel;
+
+ /// The text for showing the message if successfully flagged
+ String get flagMessageSuccessfulText;
+
+ /// The label for "delete message"
+ String get deleteMessageLabel;
+
+ /// The question asked while showing delete message dialog
+ String get deleteMessageQuestion;
+
+ /// The label for "Delete"
+ String get deleteLabel;
+
+ /// The text for showing the operation could not be completed
+ String get operationCouldNotBeCompletedText;
+
+ /// The label for "Reply"
+ String get replyLabel;
+
+ /// The text for showing pin/un-pin functionality in [MessageWidget]
+ /// based on [pinned]
+ String togglePinUnpinText({required bool pinned});
+
+ /// The text for showing delete/retry-delete based on [isDeleteFailed]
+ String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed});
+
+ /// The label for "copy message"
+ String get copyMessageLabel;
+
+ /// The label for "edit message"
+ String get editMessageLabel;
+
+ /// The text for showing resend/resend-edited message
+ /// based on [isUpdateFailed]
+ String toggleResendOrResendEditedMessage({required bool isUpdateFailed});
+
+ /// The label for "Photos"
+ String get photosLabel;
+
+ /// The text for showing on which [date] and [time] the message was sent
+ String sentAtText({required DateTime date, required DateTime time});
+
+ /// The label for "Today"
+ String get todayLabel;
+
+ /// The label for "Yesterday"
+ String get yesterdayLabel;
+
+ /// The text for showing the channel is muted
+ String get channelIsMutedText;
+
+ /// The text for showing there is no title
+ String get noTitleText;
+
+ /// The label for "let's start chatting"
+ String get letsStartChattingLabel;
+
+ /// The label for sending the first message
+ String get sendingFirstMessageLabel;
+
+ /// The label for "start a chat"
+ String get startAChatLabel;
+
+ /// The error shown when loading channel fails
+ String get loadingChannelsError;
+
+ /// The label for "Delete conversation"
+ String get deleteConversationLabel;
+
+ /// The question asked while showing delete conversation dialog
+ String get deleteConversationQuestion;
+
+ /// The label for "Stream Chat"
+ String get streamChatLabel;
+
+ /// The text for showing searching for network
+ String get searchingForNetworkText;
+
+ /// The label for "Offline"
+ String get offlineLabel;
+
+ /// The label for "Try again"
+ String get tryAgainLabel;
+
+ /// The text for showing the members count based on [count]
+ String membersCountText(int count);
+
+ /// The text for showing the watchers count based on [count]
+ String watchersCountText(int count);
+
+ /// The label for "View Info"
+ String get viewInfoLabel;
+
+ /// The label for "Leave Group"
+ String get leaveGroupLabel;
+
+ /// The label for "Leave"
+ String get leaveLabel;
+
+ /// The label for "Leave conversation"
+ String get leaveConversationLabel;
+
+ /// The question asked while showing leave conversation dialog
+ String get leaveConversationQuestion;
+
+ /// The label for "Show in chat"
+ String get showInChatLabel;
+
+ /// The label for "Save Image"
+ String get saveImageLabel;
+
+ /// The label for "Save Video"
+ String get saveVideoLabel;
+
+ /// The label for "Upload Error"
+ String get uploadErrorLabel;
+
+ /// The label for "Giphy"
+ String get giphyLabel;
+
+ /// The label for "Shuffle"
+ String get shuffleLabel;
+
+ /// The label for "Send"
+ String get sendLabel;
+
+ /// The label for "With"
+ String get withText;
+
+ /// The text shown for "In"
+ String get inText;
+
+ /// The text shown for "You"
+ String get youText;
+
+ /// The text shown for "Of"
+ String get ofText;
+
+ /// The text shown for "File"
+ String get fileText;
+
+ /// The label for "Reply to message"
+ String get replyToMessageLabel;
+}
+
+/// Default implementation of Translation strings for the stream chat widgets
+class DefaultTranslations implements Translations {
+ const DefaultTranslations._();
+
+ /// Singleton instance of [DefaultTranslations]
+ static const instance = DefaultTranslations._();
+
+ @override
+ String get launchUrlError => 'Cannot launch the url';
+
+ @override
+ String get loadingUsersError => 'Error loading users';
+
+ @override
+ String get noUsersLabel => 'There are no users currently';
+
+ @override
+ String get retryLabel => 'Retry';
+
+ @override
+ String get userLastOnlineText => 'Last online';
+
+ @override
+ String get userOnlineText => 'Online';
+
+ @override
+ String userTypingText(Iterable users) {
+ if (users.isEmpty) return '';
+ final first = users.first;
+ if (users.length == 1) {
+ return '${first.name} is typing';
+ }
+ return '${first.name} and ${users.length - 1} more are typing';
+ }
+
+ @override
+ String get threadReplyLabel => 'Thread Reply';
+
+ @override
+ String get onlyVisibleToYouText => 'Only visible to you';
+
+ @override
+ String threadReplyCountText(int count) => '$count Thread Replies';
+
+ @override
+ String attachmentsUploadProgressText({
+ required int remaining,
+ required int total,
+ }) =>
+ 'Uploading $remaining/$total ...';
+
+ @override
+ String pinnedByUserText({
+ required User pinnedBy,
+ required User currentUser,
+ }) {
+ final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
+ if (pinnedByCurrentUser) return 'Pinned by You';
+ return 'Pinned by ${pinnedBy.name}';
+ }
+
+ @override
+ String get emptyMessagesText => 'There are no messages currently';
+
+ @override
+ String get genericErrorText => 'Something went wrong';
+
+ @override
+ String get loadingMessagesError => 'Error loading messages';
+
+ @override
+ String resultCountText(int count) => '$count results';
+
+ @override
+ String get messageDeletedText => 'This message is deleted.';
+
+ @override
+ String get messageDeletedLabel => 'Message deleted';
+
+ @override
+ String get messageReactionsLabel => 'Message Reactions';
+
+ @override
+ String get emptyChatMessagesText => 'No chats here yet...';
+
+ @override
+ String threadSeparatorText(int replyCount) {
+ if (replyCount == 1) return '1 Reply';
+ return '$replyCount Replies';
+ }
+
+ @override
+ String get connectedLabel => 'Connected';
+
+ @override
+ String get disconnectedLabel => 'Disconnected';
+
+ @override
+ String get reconnectingLabel => 'Reconnecting...';
+
+ @override
+ String get alsoSendAsDirectMessageLabel => 'Also send as direct message';
+
+ @override
+ String get addACommentOrSendLabel => 'Add a comment or send';
+
+ @override
+ String get searchGifLabel => 'Search GIFs';
+
+ @override
+ String get writeAMessageLabel => 'Write a message';
+
+ @override
+ String get instantCommandsLabel => 'Instant Commands';
+
+ @override
+ String fileTooLargeAfterCompressionError(double limitInMB) =>
+ 'The file is too large to upload. '
+ 'The file size limit is $limitInMB MB. '
+ 'We tried compressing it, but it was not enough.';
+
+ @override
+ String fileTooLargeError(double limitInMB) =>
+ 'The file is too large to upload. The file size limit is $limitInMB MB.';
+
+ @override
+ String emojiMatchingQueryText(String query) => 'Emoji matching "$query"';
+
+ @override
+ String get addAFileLabel => 'Add a file';
+
+ @override
+ String get photoFromCameraLabel => 'Photo from camera';
+
+ @override
+ String get uploadAFileLabel => 'Upload a file';
+
+ @override
+ String get uploadAPhotoLabel => 'Upload a photo';
+
+ @override
+ String get uploadAVideoLabel => 'Upload a video';
+
+ @override
+ String get videoFromCameraLabel => 'Video from camera';
+
+ @override
+ String get okLabel => 'OK';
+
+ @override
+ String get somethingWentWrongError => 'Something went wrong';
+
+ @override
+ String get addMoreFilesLabel => 'Add more files';
+
+ @override
+ String get enablePhotoAndVideoAccessMessage =>
+ 'Please enable access to your photos'
+ '\nand videos so you can share them with friends.';
+
+ @override
+ String get allowGalleryAccessMessage => 'Allow access to your gallery';
+
+ @override
+ String get flagMessageLabel => 'Flag Message';
+
+ @override
+ String get flagMessageQuestion =>
+ 'Do you want to send a copy of this message to a'
+ '\nmoderator for further investigation?';
+
+ @override
+ String get flagLabel => 'FLAG';
+
+ @override
+ String get cancelLabel => 'CANCEL';
+
+ @override
+ String get flagMessageSuccessfulLabel => 'Message flagged';
+
+ @override
+ String get flagMessageSuccessfulText =>
+ 'The message has been reported to a moderator.';
+
+ @override
+ String get deleteLabel => 'DELETE';
+
+ @override
+ String get deleteMessageLabel => 'Delete Message';
+
+ @override
+ String get deleteMessageQuestion =>
+ 'Are you sure you want to permanently delete this\nmessage?';
+
+ @override
+ String get operationCouldNotBeCompletedText =>
+ 'The operation couldn\'t be completed.';
+
+ @override
+ String get replyLabel => 'Reply';
+
+ @override
+ String togglePinUnpinText({required bool pinned}) {
+ if (pinned) return 'Unpin from Conversation';
+ return 'Pin to Conversation';
+ }
+
+ @override
+ String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
+ if (isDeleteFailed) return 'Retry Deleting Message';
+ return 'Delete Message';
+ }
+
+ @override
+ String get copyMessageLabel => 'Copy Message';
+
+ @override
+ String get editMessageLabel => 'Edit Message';
+
+ @override
+ String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
+ if (isUpdateFailed) return 'Resend Edited Message';
+ return 'Resend';
+ }
+
+ @override
+ String get photosLabel => 'Photos';
+
+ String _getDay(DateTime dateTime) {
+ final now = DateTime.now();
+ final today = DateTime(now.year, now.month, now.day);
+ final yesterday = DateTime(now.year, now.month, now.day - 1);
+
+ final date = DateTime(dateTime.year, dateTime.month, dateTime.day);
+
+ if (date == today) {
+ return 'today';
+ } else if (date == yesterday) {
+ return 'yesterday';
+ } else {
+ return 'on ${Jiffy(date).MMMd}';
+ }
+ }
+
+ @override
+ String sentAtText({required DateTime date, required DateTime time}) =>
+ 'Sent ${_getDay(date)} at ${Jiffy(time.toLocal()).format('HH:mm')}';
+
+ @override
+ String get todayLabel => 'Today';
+
+ @override
+ String get yesterdayLabel => 'Yesterday';
+
+ @override
+ String get channelIsMutedText => 'Channel is muted';
+
+ @override
+ String get noTitleText => 'No title';
+
+ @override
+ String get letsStartChattingLabel => 'Let’s start chatting!';
+
+ @override
+ String get sendingFirstMessageLabel =>
+ 'How about sending your first message to a friend?';
+
+ @override
+ String get startAChatLabel => 'Start a chat';
+
+ @override
+ String get loadingChannelsError => 'Error loading channels';
+
+ @override
+ String get deleteConversationLabel => 'Delete Conversation';
+
+ @override
+ String get deleteConversationQuestion =>
+ 'Are you sure you want to delete this conversation?';
+
+ @override
+ String get streamChatLabel => 'Stream Chat';
+
+ @override
+ String get searchingForNetworkText => 'Searching for Network';
+
+ @override
+ String get offlineLabel => 'Offline...';
+
+ @override
+ String get tryAgainLabel => 'Try Again';
+
+ @override
+ String membersCountText(int count) {
+ if (count == 1) return '1 Member';
+ return '$count Members';
+ }
+
+ @override
+ String watchersCountText(int count) {
+ if (count == 1) return '1 Online';
+ return '$count Online';
+ }
+
+ @override
+ String get viewInfoLabel => 'View Info';
+
+ @override
+ String get leaveGroupLabel => 'Leave Group';
+
+ @override
+ String get leaveLabel => 'LEAVE';
+
+ @override
+ String get leaveConversationLabel => 'Leave conversation';
+
+ @override
+ String get leaveConversationQuestion =>
+ 'Are you sure you want to leave this conversation?';
+
+ @override
+ String get showInChatLabel => 'Show in Chat';
+
+ @override
+ String get saveImageLabel => 'Save Image';
+
+ @override
+ String get saveVideoLabel => 'Save Video';
+
+ @override
+ String get uploadErrorLabel => 'UPLOAD ERROR';
+
+ @override
+ String get giphyLabel => 'Giphy';
+
+ @override
+ String get shuffleLabel => 'Shuffle';
+
+ @override
+ String get sendLabel => 'Send';
+
+ @override
+ String get withText => 'with';
+
+ @override
+ String get inText => 'in';
+
+ @override
+ String get youText => 'You';
+
+ @override
+ String get ofText => 'of';
+
+ @override
+ String get fileText => 'File';
+
+ @override
+ String get replyToMessageLabel => 'Reply to Message';
+}
diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart
index 3e653187..d392c5c5 100644
--- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart
+++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart
@@ -101,7 +101,7 @@ class _MessageActionsModalState extends State {
Widget _showMessageOptionsModal() {
final mediaQueryData = MediaQuery.of(context);
final size = mediaQueryData.size;
- final user = StreamChat.of(context).user;
+ final user = StreamChat.of(context).currentUser;
final roughMaxSize = 2 * size.width / 3;
var messageTextLength = widget.message.text!.length;
@@ -268,16 +268,14 @@ class _MessageActionsModalState extends State {
final streamChatThemeData = StreamChatTheme.of(context);
final answer = await showConfirmationDialog(
context,
- title: 'Flag Message',
+ title: context.translations.flagMessageLabel,
icon: StreamSvgIcon.flag(
color: streamChatThemeData.colorTheme.accentError,
size: 24,
),
- question:
- // ignore: lines_longer_than_80_chars
- 'Do you want to send a copy of this message to a\nmoderator for further investigation?',
- okText: 'FLAG',
- cancelText: 'CANCEL',
+ question: context.translations.flagMessageQuestion,
+ okText: context.translations.flagLabel,
+ cancelText: context.translations.cancelLabel,
);
final theme = streamChatThemeData;
@@ -290,9 +288,9 @@ class _MessageActionsModalState extends State {
color: theme.colorTheme.accentError,
size: 24,
),
- details: 'The message has been reported to a moderator.',
- title: 'Message flagged',
- okText: 'OK',
+ details: context.translations.flagMessageSuccessfulText,
+ title: context.translations.flagMessageSuccessfulLabel,
+ okText: context.translations.okLabel,
);
} catch (err) {
if (err is StreamChatNetworkError &&
@@ -303,9 +301,9 @@ class _MessageActionsModalState extends State {
color: theme.colorTheme.accentError,
size: 24,
),
- details: 'The message has been reported to a moderator.',
- title: 'Message flagged',
- okText: 'OK',
+ details: context.translations.flagMessageSuccessfulText,
+ title: context.translations.flagMessageSuccessfulLabel,
+ okText: context.translations.okLabel,
);
} else {
_showErrorAlert();
@@ -335,14 +333,14 @@ class _MessageActionsModalState extends State {
});
final answer = await showConfirmationDialog(
context,
- title: 'Delete message',
+ title: context.translations.deleteMessageLabel,
icon: StreamSvgIcon.flag(
color: StreamChatTheme.of(context).colorTheme.accentError,
size: 24,
),
- question: 'Are you sure you want to permanently delete this\nmessage?',
- okText: 'DELETE',
- cancelText: 'CANCEL',
+ question: context.translations.deleteMessageQuestion,
+ okText: context.translations.deleteLabel,
+ cancelText: context.translations.cancelLabel,
);
if (answer == true) {
@@ -366,9 +364,9 @@ class _MessageActionsModalState extends State {
color: StreamChatTheme.of(context).colorTheme.accentError,
size: 24,
),
- details: 'The operation couldn\'t be completed.',
- title: 'Something went wrong',
- okText: 'OK',
+ details: context.translations.operationCouldNotBeCompletedText,
+ title: context.translations.somethingWentWrongError,
+ okText: context.translations.okLabel,
);
}
@@ -390,7 +388,7 @@ class _MessageActionsModalState extends State {
),
const SizedBox(width: 16),
Text(
- 'Reply',
+ context.translations.replyLabel,
style: streamChatThemeData.textTheme.body,
),
],
@@ -412,7 +410,7 @@ class _MessageActionsModalState extends State {
),
const SizedBox(width: 16),
Text(
- 'Flag Message',
+ context.translations.flagMessageLabel,
style: streamChatThemeData.textTheme.body,
),
],
@@ -435,7 +433,9 @@ class _MessageActionsModalState extends State {
),
const SizedBox(width: 16),
Text(
- '${widget.message.pinned ? 'Unpin from' : 'Pin to'} Conversation',
+ context.translations.togglePinUnpinText(
+ pinned: widget.message.pinned,
+ ),
style: streamChatThemeData.textTheme.body,
),
],
@@ -458,7 +458,9 @@ class _MessageActionsModalState extends State {
),
const SizedBox(width: 16),
Text(
- isDeleteFailed ? 'Retry Deleting Message' : 'Delete Message',
+ context.translations.toggleDeleteRetryDeleteMessageText(
+ isDeleteFailed: isDeleteFailed,
+ ),
style: StreamChatTheme.of(context)
.textTheme
.body
@@ -487,7 +489,7 @@ class _MessageActionsModalState extends State {
),
const SizedBox(width: 16),
Text(
- 'Copy Message',
+ context.translations.copyMessageLabel,
style: streamChatThemeData.textTheme.body,
),
],
@@ -512,7 +514,7 @@ class _MessageActionsModalState extends State {
),
const SizedBox(width: 16),
Text(
- 'Edit Message',
+ context.translations.editMessageLabel,
style: streamChatThemeData.textTheme.body,
),
],
@@ -544,7 +546,9 @@ class _MessageActionsModalState extends State {
),
const SizedBox(width: 16),
Text(
- isUpdateFailed ? 'Resend Edited Message' : 'Resend',
+ context.translations.toggleResendOrResendEditedMessage(
+ isUpdateFailed: isUpdateFailed,
+ ),
style: streamChatThemeData.textTheme.body,
),
],
@@ -588,9 +592,9 @@ class _MessageActionsModalState extends State {
color: streamChatThemeData.colorTheme.disabled,
),
),
- const Text(
- 'Edit Message',
- style: TextStyle(fontWeight: FontWeight.bold),
+ Text(
+ context.translations.editMessageLabel,
+ style: const TextStyle(fontWeight: FontWeight.bold),
),
IconButton(
visualDensity: VisualDensity.compact,
@@ -636,7 +640,7 @@ class _MessageActionsModalState extends State {
),
const SizedBox(width: 16),
Text(
- 'Thread Reply',
+ context.translations.threadReplyLabel,
style: streamChatThemeData.textTheme.body,
),
],
diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart
index c3c2e30a..f4599340 100644
--- a/packages/stream_chat_flutter/lib/src/message_input.dart
+++ b/packages/stream_chat_flutter/lib/src/message_input.dart
@@ -380,9 +380,9 @@ class MessageInputState extends State {
color: _streamChatTheme.colorTheme.disabled,
),
),
- const Text(
- 'Reply to Message',
- style: TextStyle(fontWeight: FontWeight.bold),
+ Text(
+ context.translations.replyToMessageLabel,
+ style: const TextStyle(fontWeight: FontWeight.bold),
),
IconButton(
visualDensity: VisualDensity.compact,
@@ -484,7 +484,7 @@ class MessageInputState extends State {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text(
- 'Also send as direct message',
+ context.translations.alsoSendAsDirectMessageLabel,
style: _streamChatTheme.textTheme.footnote.copyWith(
color: _streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(0.5),
@@ -640,7 +640,7 @@ class MessageInputState extends State {
style: _streamChatTheme.messageInputTheme.inputTextStyle,
autofocus: widget.autofocus,
textAlignVertical: TextAlignVertical.center,
- decoration: _getInputDecoration(),
+ decoration: _getInputDecoration(context),
textCapitalization: TextCapitalization.sentences,
),
)
@@ -652,11 +652,11 @@ class MessageInputState extends State {
);
}
- InputDecoration _getInputDecoration() {
+ InputDecoration _getInputDecoration(BuildContext context) {
final passedDecoration = _streamChatTheme.messageInputTheme.inputDecoration;
return InputDecoration(
isDense: true,
- hintText: _getHint(),
+ hintText: _getHint(context),
hintStyle: _streamChatTheme.messageInputTheme.inputTextStyle!.copyWith(
color: _streamChatTheme.colorTheme.textLowEmphasis,
),
@@ -805,14 +805,14 @@ class MessageInputState extends State {
);
}
- String _getHint() {
+ String _getHint(BuildContext context) {
if (_commandEnabled && _chosenCommand!.name == 'giphy') {
- return 'Search GIFs';
+ return context.translations.searchGifLabel;
}
if (_attachments.isNotEmpty) {
- return 'Add a comment or send';
+ return context.translations.addACommentOrSendLabel;
}
- return 'Write a message';
+ return context.translations.writeAMessageLabel;
}
void _checkEmoji(String s, BuildContext context) {
@@ -935,7 +935,7 @@ class MessageInputState extends State {
),
),
Text(
- 'Instant Commands',
+ context.translations.instantCommandsLabel,
style: TextStyle(
color: _streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(.5),
@@ -1198,8 +1198,9 @@ class MessageInputState extends State {
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
_showErrorAlert(
- // ignore: lines_longer_than_80_chars
- 'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.',
+ context.translations.fileTooLargeAfterCompressionError(
+ widget.maxAttachmentSize / (1024 * 1024),
+ ),
);
return;
}
@@ -1210,9 +1211,9 @@ class MessageInputState extends State {
path: mediaInfo.path,
);
} else {
- _showErrorAlert(
- 'The file is too large to upload. The file size limit is 20MB.',
- );
+ _showErrorAlert(context.translations.fileTooLargeError(
+ widget.maxAttachmentSize / (1024 * 1024),
+ ));
return;
}
}
@@ -1480,7 +1481,9 @@ class MessageInputState extends State {
),
Flexible(
child: Text(
- 'Emoji matching "$query"',
+ context.translations.emojiMatchingQueryText(
+ query,
+ ),
style: TextStyle(
color: _streamChatTheme
.colorTheme.textHighEmphasis
@@ -1830,17 +1833,17 @@ class MessageInputState extends State {
builder: (_) => Column(
mainAxisSize: MainAxisSize.min,
children: [
- const ListTile(
+ ListTile(
title: Text(
- 'Add a file',
- style: TextStyle(
+ context.translations.addAFileLabel,
+ style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
ListTile(
leading: const Icon(Icons.image),
- title: const Text('Upload a photo'),
+ title: Text(context.translations.uploadAPhotoLabel),
onTap: () {
pickFile(DefaultAttachmentTypes.image);
Navigator.pop(context);
@@ -1848,7 +1851,7 @@ class MessageInputState extends State {
),
ListTile(
leading: const Icon(Icons.video_library),
- title: const Text('Upload a video'),
+ title: Text(context.translations.uploadAVideoLabel),
onTap: () {
pickFile(DefaultAttachmentTypes.video);
Navigator.pop(context);
@@ -1857,7 +1860,7 @@ class MessageInputState extends State {
if (!kIsWeb)
ListTile(
leading: const Icon(Icons.camera_alt),
- title: const Text('Photo from camera'),
+ title: Text(context.translations.photoFromCameraLabel),
onTap: () {
pickFile(DefaultAttachmentTypes.image, true);
Navigator.pop(context);
@@ -1866,7 +1869,7 @@ class MessageInputState extends State {
if (!kIsWeb)
ListTile(
leading: const Icon(Icons.videocam),
- title: const Text('Video from camera'),
+ title: Text(context.translations.videoFromCameraLabel),
onTap: () {
pickFile(DefaultAttachmentTypes.video, true);
Navigator.pop(context);
@@ -1874,7 +1877,7 @@ class MessageInputState extends State {
),
ListTile(
leading: const Icon(Icons.insert_drive_file),
- title: const Text('Upload a file'),
+ title: Text(context.translations.uploadAFileLabel),
onTap: () {
pickFile(DefaultAttachmentTypes.file);
Navigator.pop(context);
@@ -1977,8 +1980,9 @@ class MessageInputState extends State {
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
_showErrorAlert(
- // ignore: lines_longer_than_80_chars
- 'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.',
+ context.translations.fileTooLargeAfterCompressionError(
+ widget.maxAttachmentSize / (1024 * 1024),
+ ),
);
return;
}
@@ -1989,9 +1993,9 @@ class MessageInputState extends State {
path: mediaInfo.path,
);
} else {
- _showErrorAlert(
- 'The file is too large to upload. The file size limit is 20MB.',
- );
+ _showErrorAlert(context.translations.fileTooLargeError(
+ widget.maxAttachmentSize / (1024 * 1024),
+ ));
return;
}
}
@@ -2172,7 +2176,7 @@ class MessageInputState extends State {
height: 26,
),
Text(
- 'Something went wrong',
+ context.translations.somethingWentWrongError,
style: _streamChatTheme.textTheme.headlineBold,
),
const SizedBox(
@@ -2201,7 +2205,7 @@ class MessageInputState extends State {
Navigator.of(context).pop();
},
child: Text(
- 'OK',
+ context.translations.okLabel,
style: _streamChatTheme.textTheme.bodyBold.copyWith(
color: _streamChatTheme.colorTheme.accentPrimary),
),
@@ -2312,10 +2316,10 @@ class _PickerWidget extends StatefulWidget {
final StreamChatThemeData streamChatTheme;
@override
- __PickerWidgetState createState() => __PickerWidgetState();
+ _PickerWidgetState createState() => _PickerWidgetState();
}
-class __PickerWidgetState extends State<_PickerWidget> {
+class _PickerWidgetState extends State<_PickerWidget> {
Future? requestPermission;
@override
@@ -2347,7 +2351,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
color: widget.streamChatTheme.colorTheme.inputBg,
alignment: Alignment.center,
child: Text(
- 'Add more files',
+ context.translations.addMoreFilesLabel,
style: TextStyle(
color: widget.streamChatTheme.colorTheme.accentPrimary,
fontWeight: FontWeight.bold,
@@ -2379,8 +2383,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
color: widget.streamChatTheme.colorTheme.disabled,
),
Text(
- // ignore: lines_longer_than_80_chars
- 'Please enable access to your photos \nand videos so you can share them with friends.',
+ context.translations.enablePhotoAndVideoAccessMessage,
style: widget.streamChatTheme.textTheme.body.copyWith(
color:
widget.streamChatTheme.colorTheme.textLowEmphasis),
@@ -2389,7 +2392,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
const SizedBox(height: 6),
Center(
child: Text(
- 'Allow access to your gallery',
+ context.translations.allowGalleryAccessMessage,
style: widget.streamChatTheme.textTheme.bodyBold.copyWith(
color: widget.streamChatTheme.colorTheme.accentPrimary,
),
diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart
index 8a236cbd..f72ebfe1 100644
--- a/packages/stream_chat_flutter/lib/src/message_list_view.dart
+++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart
@@ -166,11 +166,23 @@ class MessageListView extends StatefulWidget {
this.showFloatingDateDivider = true,
this.threadSeparatorBuilder,
this.messageListController,
+ this.reverse = true,
+ this.paginationLimit = 20,
}) : super(key: key);
/// Function used to build a custom message widget
final MessageBuilder? messageBuilder;
+ /// Whether the view scrolls in the reading direction.
+ ///
+ /// Defaults to true.
+ ///
+ /// See [ScrollView.reverse].
+ final bool reverse;
+
+ /// Limit used during pagination
+ final int paginationLimit;
+
/// Function used to build a custom system message widget
final SystemMessageBuilder? systemMessageBuilder;
@@ -323,11 +335,13 @@ class _MessageListViewState extends State {
bool _inBetweenList = false;
late final _defaultController = MessageListController();
+
MessageListController get _messageListController =>
widget.messageListController ?? _defaultController;
@override
Widget build(BuildContext context) => MessageListCore(
+ paginationLimit: widget.paginationLimit,
messageFilter: widget.messageFilter,
loadingBuilder: widget.loadingBuilder ??
(context) => const Center(
@@ -336,7 +350,7 @@ class _MessageListViewState extends State {
emptyBuilder: widget.emptyBuilder ??
(context) => Center(
child: Text(
- 'No chats here yet...',
+ context.translations.emptyChatMessagesText,
style: _streamTheme.textTheme.footnote.copyWith(
color: _streamTheme.colorTheme.textHighEmphasis
.withOpacity(.5)),
@@ -349,7 +363,7 @@ class _MessageListViewState extends State {
errorBuilder: widget.errorBuilder ??
(BuildContext context, Object error) => Center(
child: Text(
- 'Something went wrong',
+ context.translations.genericErrorText,
style: _streamTheme.textTheme.footnote.copyWith(
color: _streamTheme.colorTheme.textHighEmphasis
.withOpacity(.5)),
@@ -395,14 +409,14 @@ class _MessageListViewState extends State {
var showStatus = true;
switch (status) {
case ConnectionStatus.connected:
- statusString = 'Connected';
+ statusString = context.translations.connectedLabel;
showStatus = false;
break;
case ConnectionStatus.connecting:
- statusString = 'Reconnecting...';
+ statusString = context.translations.reconnectingLabel;
break;
case ConnectionStatus.disconnected:
- statusString = 'Disconnected';
+ statusString = context.translations.disconnectedLabel;
break;
}
@@ -445,7 +459,7 @@ class _MessageListViewState extends State {
initialAlignment: initialAlignment ?? 0,
physics: widget.scrollPhysics,
itemScrollController: _scrollController,
- reverse: true,
+ reverse: widget.reverse,
addAutomaticKeepAlives: false,
itemCount: itemCount,
@@ -604,7 +618,7 @@ class _MessageListViewState extends State {
return widget.threadSeparatorBuilder!.call(context);
}
- final replyCount = widget.parentMessage!.replyCount;
+ final replyCount = widget.parentMessage!.replyCount!;
return DecoratedBox(
decoration: BoxDecoration(
gradient: _streamTheme.colorTheme.bgGradient,
@@ -612,7 +626,7 @@ class _MessageListViewState extends State {
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
- '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}',
+ context.translations.threadSeparatorText(replyCount),
textAlign: TextAlign.center,
style: _streamTheme.channelTheme.channelHeaderTheme.subtitle,
),
@@ -621,7 +635,8 @@ class _MessageListViewState extends State {
}
Positioned _buildFloatingDateDivider(int itemCount) => Positioned(
- top: 20,
+ top: widget.reverse ? 20 : null,
+ bottom: widget.reverse ? null : 20,
left: 0,
right: 0,
child: BetterStreamBuilder>(
@@ -655,7 +670,9 @@ class _MessageListViewState extends State {
);
Future _paginateData(
- StreamChannelState? channel, QueryDirection direction) =>
+ StreamChannelState? channel,
+ QueryDirection direction,
+ ) =>
_messageListController.paginateData!(direction: direction);
int? _getTopElementIndex(Iterable values) {
@@ -687,7 +704,8 @@ class _MessageListViewState extends State {
final unreadCount = snapshot.data!.item2;
final showUnreadCount = unreadCount > 0 &&
streamChannel!.channel.state!.members.any((e) =>
- e.userId == streamChannel!.channel.client.state.user!.id);
+ e.userId ==
+ streamChannel!.channel.client.state.currentUser!.id);
return Positioned(
bottom: 8,
right: 8,
@@ -715,9 +733,13 @@ class _MessageListViewState extends State {
);
}
},
- child: StreamSvgIcon.down(
- color: _streamTheme.colorTheme.textHighEmphasis,
- ),
+ child: widget.reverse
+ ? StreamSvgIcon.down(
+ color: _streamTheme.colorTheme.textHighEmphasis,
+ )
+ : StreamSvgIcon.up(
+ color: _streamTheme.colorTheme.textHighEmphasis,
+ ),
),
if (showUnreadCount)
Positioned(
@@ -789,9 +811,10 @@ class _MessageListViewState extends State {
Widget buildParentMessage(
Message message,
) {
- final isMyMessage = message.user!.id == StreamChat.of(context).user!.id;
+ final isMyMessage =
+ message.user!.id == StreamChat.of(context).currentUser!.id;
final isOnlyEmoji = message.text!.isOnlyEmoji;
- final currentUser = StreamChat.of(context).user;
+ final currentUser = StreamChat.of(context).currentUser;
final members = StreamChannel.of(context).channel.state?.members ?? [];
final currentUserMember =
members.firstWhereOrNull((e) => e.user!.id == currentUser!.id);
@@ -876,7 +899,7 @@ class _MessageListViewState extends State {
);
}
- final userId = StreamChat.of(context).user!.id;
+ final userId = StreamChat.of(context).currentUser!.id;
final isMyMessage = message.user!.id == userId;
final nextMessage = index - 1 >= 0 ? messages[index - 1] : null;
final isNextUserSame =
@@ -939,7 +962,7 @@ class _MessageListViewState extends State {
? BorderSide.none
: null;
- final currentUser = StreamChat.of(context).user;
+ final currentUser = StreamChat.of(context).currentUser;
final members = StreamChannel.of(context).channel.state?.members ?? [];
final currentUserMember =
members.firstWhereOrNull((e) => e.user!.id == currentUser!.id);
@@ -1145,7 +1168,7 @@ class _MessageListViewState extends State {
_topPaginationActive = false;
}
if (event.message!.user!.id ==
- streamChannel!.channel.client.state.user!.id) {
+ streamChannel!.channel.client.state.currentUser!.id) {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_scrollController?.jumpTo(
index: 0,
@@ -1227,8 +1250,8 @@ class _LoadingIndicator extends StatelessWidget {
initialData: false,
errorBuilder: (context, error) => Container(
color: streamTheme.colorTheme.accentError.withOpacity(.2),
- child: const Center(
- child: Text('Error loading messages'),
+ child: Center(
+ child: Text(context.translations.loadingMessagesError),
),
),
builder: (context, data) {
diff --git a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart
index 3d9a907e..b7e37bad 100644
--- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart
+++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart
@@ -7,6 +7,7 @@ import 'package:stream_chat_flutter/src/stream_chat.dart';
import 'package:stream_chat_flutter/src/user_avatar.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Modal widget for displaying message reactions
class MessageReactionsModal extends StatelessWidget {
@@ -42,7 +43,7 @@ class MessageReactionsModal extends StatelessWidget {
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
- final user = StreamChat.of(context).user;
+ final user = StreamChat.of(context).currentUser;
final roughMaxSize = 2 * size.width / 3;
var messageTextLength = message.text!.length;
@@ -154,7 +155,7 @@ class MessageReactionsModal extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Text(
- 'Message Reactions',
+ context.translations.messageReactionsLabel,
style: chatThemeData.textTheme.headlineBold,
),
const SizedBox(height: 16),
diff --git a/packages/stream_chat_flutter/lib/src/message_search_item.dart b/packages/stream_chat_flutter/lib/src/message_search_item.dart
index 1f8a31ea..31c53d5a 100644
--- a/packages/stream_chat_flutter/lib/src/message_search_item.dart
+++ b/packages/stream_chat_flutter/lib/src/message_search_item.dart
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// It shows the current [Message] preview.
///
@@ -49,12 +50,14 @@ class MessageSearchItem extends StatelessWidget {
title: Row(
children: [
Text(
- user.id == StreamChat.of(context).user?.id ? 'You' : user.name,
+ user.id == StreamChat.of(context).currentUser?.id
+ ? context.translations.youText
+ : user.name,
style: chatThemeData.channelPreviewTheme.title,
),
if (channelName != null) ...[
Text(
- ' in ',
+ ' ${context.translations.inText} ',
style: chatThemeData.channelPreviewTheme.title?.copyWith(
fontWeight: FontWeight.normal,
),
@@ -98,7 +101,7 @@ class MessageSearchItem extends StatelessWidget {
Widget _buildSubtitle(BuildContext context, Message message) {
var text = message.text;
if (message.isDeleted) {
- text = 'This message was deleted.';
+ text = context.translations.messageDeletedText;
} else if (message.attachments.isNotEmpty) {
final parts = [
...message.attachments.map((e) {
diff --git a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart
index 13f5ea55..1eae16cd 100644
--- a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart
+++ b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart
@@ -3,6 +3,7 @@ import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/message_search_item.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Callback called when tapping on a user
typedef MessageSearchItemTapCallback = void Function(GetMessageResponse);
@@ -140,6 +141,7 @@ class MessageSearchListView extends StatefulWidget {
class _MessageSearchListViewState extends State {
late final _defaultController = MessageSearchListController();
+
MessageSearchListController get _messageSearchListController =>
widget.messageSearchListController ?? _defaultController;
@@ -161,8 +163,8 @@ class _MessageSearchListViewState extends State {
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
- child: const Center(
- child: Text('There are no messages currently'),
+ child: Center(
+ child: Text(context.translations.emptyMessagesText),
),
),
),
@@ -176,7 +178,7 @@ class _MessageSearchListViewState extends State {
showMessage: widget.showErrorTile,
tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter,
- message: 'An error occurred.',
+ message: context.translations.genericErrorText,
child: Container(),
);
},
@@ -240,10 +242,10 @@ class _MessageSearchListViewState extends State {
.colorTheme
.accentError
.withOpacity(.2),
- child: const Padding(
- padding: EdgeInsets.symmetric(vertical: 16),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(
- child: Text('Error loading messages'),
+ child: Text(context.translations.loadingMessagesError),
),
),
);
@@ -306,7 +308,7 @@ class _MessageSearchListViewState extends State {
horizontal: 8,
),
child: Text(
- '${items.length} results',
+ context.translations.resultCountText(items.length),
style: TextStyle(
color: chatThemeData.colorTheme.textLowEmphasis,
),
diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart
index 6d006085..345b0096 100644
--- a/packages/stream_chat_flutter/lib/src/message_text.dart
+++ b/packages/stream_chat_flutter/lib/src/message_text.dart
@@ -29,60 +29,66 @@ class MessageText extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\n\n');
+ final streamChat = StreamChat.of(context);
+ assert(streamChat.currentUser != null, '');
+ return BetterStreamBuilder(
+ stream: streamChat.currentUserStream.map((it) => it!.language ?? 'en'),
+ initialData: streamChat.currentUser!.language ?? 'en',
+ builder: (context, language) {
+ final translatedText =
+ message.i18n?['${language}_text'] ?? message.text;
+ final messageText =
+ _replaceMentions(translatedText ?? '').replaceAll('\n', '\n\n');
+ final themeData = Theme.of(context);
+ return MarkdownBody(
+ data: messageText,
+ onTapLink: (
+ String link,
+ String? href,
+ String title,
+ ) {
+ if (link.startsWith('@')) {
+ final mentionedUser = message.mentionedUsers.firstWhereOrNull(
+ (u) => '@${u.name}' == link,
+ );
- final themeData = Theme.of(context);
- return MarkdownBody(
- data: text,
- onTapLink: (
- String link,
- String? href,
- String title,
- ) {
- if (link.startsWith('@')) {
- final mentionedUser = message.mentionedUsers.firstWhereOrNull(
- (u) => '@${u.name}' == link,
- );
- if (mentionedUser == null) {
- return;
- }
+ if (mentionedUser == null) return;
- if (onMentionTap != null) {
- onMentionTap!(mentionedUser);
- } else {
- print('tap on ${mentionedUser.name}');
- }
- } else {
- if (onLinkTap != null) {
- onLinkTap!(link);
- } else {
- launchURL(context, link);
- }
- }
- },
- styleSheet: MarkdownStyleSheet.fromTheme(
- themeData.copyWith(
- textTheme: themeData.textTheme.apply(
- bodyColor: messageTheme.messageText?.color,
- decoration: messageTheme.messageText?.decoration,
- decorationColor: messageTheme.messageText?.decorationColor,
- decorationStyle: messageTheme.messageText?.decorationStyle,
- fontFamily: messageTheme.messageText?.fontFamily,
+ onMentionTap?.call(mentionedUser);
+ } else {
+ if (onLinkTap != null) {
+ onLinkTap!(link);
+ } else {
+ launchURL(context, link);
+ }
+ }
+ },
+ styleSheet: MarkdownStyleSheet.fromTheme(
+ themeData.copyWith(
+ textTheme: themeData.textTheme.apply(
+ bodyColor: messageTheme.messageText?.color,
+ decoration: messageTheme.messageText?.decoration,
+ decorationColor: messageTheme.messageText?.decorationColor,
+ decorationStyle: messageTheme.messageText?.decorationStyle,
+ fontFamily: messageTheme.messageText?.fontFamily,
+ ),
+ ),
+ ).copyWith(
+ a: messageTheme.messageLinks,
+ p: messageTheme.messageText,
),
- ),
- ).copyWith(
- a: messageTheme.messageLinks,
- p: messageTheme.messageText,
- ),
+ );
+ },
);
}
String _replaceMentions(String text) {
- message.mentionedUsers.map((u) => u.name).toSet().forEach((userName) {
- // ignore: parameter_assignments
- text = text.replaceAll(
+ var messageTextToRender = text;
+ for (final user in message.mentionedUsers.toSet()) {
+ final userName = user.name;
+ messageTextToRender = messageTextToRender.replaceAll(
'@$userName', '[@$userName](@${userName.replaceAll(' ', '')})');
- });
- return text;
+ }
+ return messageTextToRender;
}
}
diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart
index c9dfd9d4..6982d0a0 100644
--- a/packages/stream_chat_flutter/lib/src/message_widget.dart
+++ b/packages/stream_chat_flutter/lib/src/message_widget.dart
@@ -92,6 +92,8 @@ class MessageWidget extends StatefulWidget {
this.userAvatarBuilder,
this.editMessageInputBuilder,
this.textBuilder,
+ this.bottomRowBuilder,
+ this.deletedBottomRowBuilder,
this.onReturnAction,
Map? customAttachmentBuilders,
this.readList,
@@ -275,6 +277,12 @@ class MessageWidget extends StatefulWidget {
/// Function called on long press
final void Function(BuildContext, Message)? onMessageActions;
+ /// Widget builder for building a bottom row below the message
+ final Widget Function(BuildContext, Message)? bottomRowBuilder;
+
+ /// Widget builder for building a bottom row below a deleted message
+ final Widget Function(BuildContext, Message)? deletedBottomRowBuilder;
+
/// Widget builder for building user avatar
final Widget Function(BuildContext, User)? userAvatarBuilder;
@@ -410,6 +418,8 @@ class MessageWidget extends StatefulWidget {
Widget Function(BuildContext, Message)? editMessageInputBuilder,
Widget Function(BuildContext, Message)? textBuilder,
Widget Function(BuildContext, Message)? usernameBuilder,
+ Widget Function(BuildContext, Message)? bottomRowBuilder,
+ Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
void Function(BuildContext, Message)? onMessageActions,
Message? message,
MessageTheme? messageTheme,
@@ -463,6 +473,9 @@ class MessageWidget extends StatefulWidget {
editMessageInputBuilder ?? this.editMessageInputBuilder,
textBuilder: textBuilder ?? this.textBuilder,
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
+ bottomRowBuilder: bottomRowBuilder ?? this.bottomRowBuilder,
+ deletedBottomRowBuilder:
+ deletedBottomRowBuilder ?? this.deletedBottomRowBuilder,
onMessageActions: onMessageActions ?? this.onMessageActions,
message: message ?? this.message,
messageTheme: messageTheme ?? this.messageTheme,
@@ -782,7 +795,11 @@ class _MessageWidgetState extends State
bottom:
isPinned && widget.showPinHighlight ? 6.0 : 0.0,
),
- child: _bottomRow,
+ child: widget.bottomRowBuilder?.call(
+ context,
+ widget.message,
+ ) ??
+ _bottomRow,
),
if (isFailedState)
Positioned(
@@ -810,7 +827,7 @@ class _MessageWidgetState extends State
}
Widget _buildQuotedMessage() {
- final isMyMessage = widget.message.user?.id == _streamChat.user?.id;
+ final isMyMessage = widget.message.user?.id == _streamChat.currentUser?.id;
final onTap = widget.message.quotedMessage?.isDeleted != true &&
widget.onQuotedMessageTap != null
? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId)
@@ -830,22 +847,11 @@ class _MessageWidgetState extends State
Widget get _bottomRow {
if (isDeleted) {
- final chatThemeData = _streamChatTheme;
- return Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- StreamSvgIcon.eye(
- color: chatThemeData.colorTheme.textLowEmphasis,
- size: 16,
- ),
- const SizedBox(width: 8),
- Text(
- 'Only visible to you',
- style: chatThemeData.textTheme.footnote
- .copyWith(color: chatThemeData.colorTheme.textLowEmphasis),
- ),
- ],
- );
+ return widget.deletedBottomRowBuilder?.call(
+ context,
+ widget.message,
+ ) ??
+ const Offstage();
}
final children = [];
@@ -854,9 +860,9 @@ class _MessageWidgetState extends State
final showThreadParticipants = threadParticipants?.isNotEmpty == true;
final replyCount = widget.message.replyCount;
- var msg = 'Thread Reply';
+ var msg = context.translations.threadReplyLabel;
if (showThreadReplyIndicator && replyCount! > 1) {
- msg = '$replyCount Thread Replies';
+ msg = context.translations.threadReplyCountText(replyCount);
}
// ignore: prefer_function_declarations_over_variables
@@ -993,7 +999,7 @@ class _MessageWidgetState extends State
Widget _buildReactionIndicator(
BuildContext context,
) {
- final ownId = _streamChat.user!.id;
+ final ownId = _streamChat.currentUser!.id;
final reactionsMap = {};
widget.message.latestReactions?.forEach((element) {
if (!reactionsMap.containsKey(element.type) ||
@@ -1054,10 +1060,10 @@ class _MessageWidgetState extends State
showReactionPickerIndicator: widget.showReactions &&
(widget.message.status == MessageSendingStatus.sent),
showPinHighlight: false,
- showUserAvatar:
- widget.message.user!.id == channel.client.state.user!.id
- ? DisplayWidget.gone
- : DisplayWidget.show,
+ showUserAvatar: widget.message.user!.id ==
+ channel.client.state.currentUser!.id
+ ? DisplayWidget.gone
+ : DisplayWidget.show,
),
onCopyTap: (message) =>
Clipboard.setData(ClipboardData(text: message.text)),
@@ -1118,7 +1124,7 @@ class _MessageWidgetState extends State
(widget.message.status == MessageSendingStatus.sent),
showPinHighlight: false,
showUserAvatar:
- widget.message.user!.id == channel.client.state.user!.id
+ widget.message.user!.id == channel.client.state.currentUser!.id
? DisplayWidget.gone
: DisplayWidget.show,
),
@@ -1201,7 +1207,10 @@ class _MessageWidgetState extends State
);
}
return Text(
- 'Uploading $uploadRemaining/$totalAttachments ...',
+ context.translations.attachmentsUploadProgressText(
+ remaining: uploadRemaining,
+ total: totalAttachments,
+ ),
style: style,
);
}
@@ -1275,8 +1284,8 @@ class _MessageWidgetState extends State
}
Widget _buildPinnedMessage(Message message) {
- final pinnedBy = message.pinnedBy;
- final pinnedByMe = _streamChat.user!.id == pinnedBy!.id;
+ final pinnedBy = message.pinnedBy!;
+ final currentUser = _streamChat.currentUser!;
return Padding(
padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8),
@@ -1290,7 +1299,10 @@ class _MessageWidgetState extends State
width: 4,
),
Text(
- 'Pinned by ${pinnedByMe ? 'You' : pinnedBy.name}',
+ context.translations.pinnedByUserText(
+ pinnedBy: pinnedBy,
+ currentUser: currentUser,
+ ),
style: TextStyle(
color: _streamChatTheme.colorTheme.textLowEmphasis,
fontSize: 13,
diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart
index 9e279344..ecc2b27a 100644
--- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart
+++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart
@@ -123,7 +123,7 @@ class ReactionBubble extends StatelessWidget {
);
final chatThemeData = StreamChatTheme.of(context);
- final userId = StreamChat.of(context).user?.id;
+ final userId = StreamChat.of(context).currentUser?.id;
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4,
diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart
index 66add133..f185d288 100644
--- a/packages/stream_chat_flutter/lib/src/stream_chat.dart
+++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart
@@ -1,5 +1,4 @@
import 'dart:async';
-import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -126,21 +125,34 @@ class StreamChatState extends State {
return defaultTheme.merge(themeData);
}
+ // coverage:ignore-start
+
/// The current user
- User? get user => widget.client.state.user;
+ @Deprecated('Use `.currentUser` instead, Will be removed in future releases')
+ User? get user => widget.client.state.currentUser;
/// The current user as a stream
- Stream get userStream => widget.client.state.userStream;
+ @Deprecated(
+ 'Use `.currentUserStream` instead, Will be removed in future releases',
+ )
+ Stream get userStream => widget.client.state.currentUserStream;
- @override
- void initState() {
- super.initState();
- }
+ // coverage:ignore-end
+
+ /// The current user
+ User? get currentUser => widget.client.state.currentUser;
+
+ /// The current user as a stream
+ Stream get currentUserStream => widget.client.state.currentUserStream;
@override
void didChangeDependencies() {
- final locale = ui.window.locale;
- Jiffy.locale(locale.languageCode);
+ final currentLocale = Localizations.localeOf(context);
+ final languageCode = currentLocale.languageCode;
+ final availableLocales = Jiffy.getAllAvailableLocales();
+ if (availableLocales.contains(languageCode)) {
+ Jiffy.locale(languageCode);
+ }
super.didChangeDependencies();
}
}
diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart
index 94ba50e4..9d2df266 100644
--- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart
+++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart
@@ -1,12 +1,11 @@
-import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/channel_header.dart';
import 'package:stream_chat_flutter/src/channel_preview.dart';
import 'package:stream_chat_flutter/src/extension.dart';
+import 'package:stream_chat_flutter/src/gradient_avatar.dart';
import 'package:stream_chat_flutter/src/message_input.dart';
import 'package:stream_chat_flutter/src/reaction_icon.dart';
-import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -310,10 +309,9 @@ class StreamChatThemeData {
colorTheme: colorTheme,
primaryIconTheme: iconTheme,
defaultUserImage: (context, user) => Center(
- child: CachedNetworkImage(
- filterQuality: FilterQuality.high,
- imageUrl: getRandomPicUrl(user),
- fit: BoxFit.cover,
+ child: GradientAvatar(
+ name: user.name,
+ userId: user.id,
),
),
channelPreviewTheme: channelPreviewTheme,
diff --git a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart
index fb5017e1..73c33131 100644
--- a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart
+++ b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart
@@ -37,6 +37,18 @@ class StreamSvgIcon extends StatelessWidget {
height: size,
);
+ /// [StreamSvgIcon] type
+ factory StreamSvgIcon.up({
+ double? size,
+ Color? color,
+ }) =>
+ StreamSvgIcon(
+ assetName: 'Icon_up.svg',
+ color: color,
+ width: size,
+ height: size,
+ );
+
/// [StreamSvgIcon] type
factory StreamSvgIcon.attach({
double? size,
diff --git a/packages/stream_chat_flutter/lib/src/system_message.dart b/packages/stream_chat_flutter/lib/src/system_message.dart
index 11c80300..297810b3 100644
--- a/packages/stream_chat_flutter/lib/src/system_message.dart
+++ b/packages/stream_chat_flutter/lib/src/system_message.dart
@@ -13,8 +13,8 @@ class SystemMessage extends StatelessWidget {
/// This message
final Message message;
- // ignore: lines_longer_than_80_chars
- /// The function called when tapping on the message when the message is not failed
+ /// The function called when tapping on the message
+ /// when the message is not failed
final void Function(Message)? onMessageTap;
@override
diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart
index bfde9a4c..a5eb62db 100644
--- a/packages/stream_chat_flutter/lib/src/thread_header.dart
+++ b/packages/stream_chat_flutter/lib/src/thread_header.dart
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// 
/// 
@@ -111,7 +112,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
- 'with ',
+ '${context.translations.withText} ',
style: chatThemeData.channelTheme.channelHeaderTheme.subtitle,
),
Flexible(
@@ -149,7 +150,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
children: [
title ??
Text(
- 'Thread Reply',
+ context.translations.threadReplyLabel,
style: chatThemeData.channelTheme.channelHeaderTheme.title,
),
const SizedBox(height: 2),
diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart
index 4c0ad19f..48752f07 100644
--- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart
+++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Widget to show the current list of typing users
class TypingIndicator extends StatelessWidget {
@@ -63,8 +64,7 @@ class TypingIndicator extends StatelessWidget {
height: 4,
),
Text(
- // ignore: lines_longer_than_80_chars
- ' ${data.elementAt(0).name}${data.length == 1 ? '' : ' and ${data.length - 1} more'} ${data.length == 1 ? 'is' : 'are'} typing',
+ context.translations.userTypingText(data),
maxLines: 1,
style: style,
),
diff --git a/packages/stream_chat_flutter/lib/src/user_item.dart b/packages/stream_chat_flutter/lib/src/user_item.dart
index a13b544e..a55693aa 100644
--- a/packages/stream_chat_flutter/lib/src/user_item.dart
+++ b/packages/stream_chat_flutter/lib/src/user_item.dart
@@ -4,6 +4,7 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/user_list_view.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
///
/// It shows the current [User] preview.
@@ -86,12 +87,13 @@ class UserItem extends StatelessWidget {
);
}
- Widget _buildLastActive(context) {
+ Widget _buildLastActive(BuildContext context) {
final chatTheme = StreamChatTheme.of(context);
return Text(
user.online == true
- ? 'Online'
- : 'Last online ${Jiffy(user.lastActive).fromNow()}',
+ ? context.translations.userOnlineText
+ : '${context.translations.userLastOnlineText} '
+ '${Jiffy(user.lastActive).fromNow()}',
style: chatTheme.textTheme.footnote.copyWith(
color: chatTheme.colorTheme.textHighEmphasis.withOpacity(.5)),
);
diff --git a/packages/stream_chat_flutter/lib/src/user_list_view.dart b/packages/stream_chat_flutter/lib/src/user_list_view.dart
index 39d4453d..9481e831 100644
--- a/packages/stream_chat_flutter/lib/src/user_list_view.dart
+++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Callback called when tapping on a user
typedef UserTapCallback = void Function(User, Widget?);
@@ -155,6 +156,7 @@ class _UserListViewState extends State
bool get _isListView => widget.crossAxisCount == 1;
late final _defaultController = UserListController();
+
UserListController get _userListController =>
widget.userListController ?? _defaultController;
@@ -220,9 +222,9 @@ class _UserListViewState extends State
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text.rich(
- const TextSpan(
+ TextSpan(
children: [
- WidgetSpan(
+ const WidgetSpan(
child: Padding(
padding: EdgeInsets.only(
right: 2,
@@ -230,14 +232,14 @@ class _UserListViewState extends State
child: Icon(Icons.error_outline),
),
),
- TextSpan(text: 'Error loading users'),
+ TextSpan(text: context.translations.loadingUsersError),
],
),
style: Theme.of(context).textTheme.headline6,
),
TextButton(
onPressed: () => _userListController.loadData!(),
- child: const Text('Retry'),
+ child: Text(context.translations.retryLabel),
),
],
),
@@ -250,8 +252,8 @@ class _UserListViewState extends State
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
- child: const Center(
- child: Text('There are no users currently'),
+ child: Center(
+ child: Text(context.translations.noUsersLabel),
),
),
),
@@ -400,10 +402,10 @@ class _UserListViewState extends State
.colorTheme
.accentError
.withOpacity(.2),
- child: const Padding(
- padding: EdgeInsets.symmetric(vertical: 16),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(
- child: Text('Error loading users'),
+ child: Text(context.translations.loadingUsersError),
),
),
);
diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart
index aee46800..314a788a 100644
--- a/packages/stream_chat_flutter/lib/src/utils.dart
+++ b/packages/stream_chat_flutter/lib/src/utils.dart
@@ -4,17 +4,15 @@ import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:url_launcher/url_launcher.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
/// Launch URL
Future launchURL(BuildContext context, String? url) async {
if (url != null && await canLaunch(url)) {
await launch(url);
} else {
- // ignore: deprecated_member_use
- Scaffold.of(context).showSnackBar(
- const SnackBar(
- content: Text('Cannot launch the url'),
- ),
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(context.translations.launchUrlError)),
);
}
}
diff --git a/packages/stream_chat_flutter/lib/src/visible_footnote.dart b/packages/stream_chat_flutter/lib/src/visible_footnote.dart
new file mode 100644
index 00000000..8d16ce41
--- /dev/null
+++ b/packages/stream_chat_flutter/lib/src/visible_footnote.dart
@@ -0,0 +1,30 @@
+import 'package:flutter/material.dart';
+import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
+import 'package:stream_chat_flutter/stream_chat_flutter.dart';
+import 'package:stream_chat_flutter/src/extension.dart';
+
+/// Widget for displaying a footnote
+class VisibleFootnote extends StatelessWidget {
+ /// Constructor for creating a [VisibleFootnote]
+ const VisibleFootnote({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final chatThemeData = StreamChatTheme.of(context);
+ return Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ StreamSvgIcon.eye(
+ color: chatThemeData.colorTheme.textLowEmphasis,
+ size: 16,
+ ),
+ const SizedBox(width: 8),
+ Text(
+ context.translations.onlyVisibleToYouText,
+ style: chatThemeData.textTheme.footnote
+ .copyWith(color: chatThemeData.colorTheme.textLowEmphasis),
+ ),
+ ],
+ );
+ }
+}
diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart
index 39aabb85..373a61d2 100644
--- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart
+++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart
@@ -1,3 +1,4 @@
+export 'package:jiffy/jiffy.dart';
export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
export 'src/attachment/attachment.dart';
@@ -14,7 +15,10 @@ export 'src/deleted_message.dart';
export 'src/full_screen_media.dart';
export 'src/gallery_footer.dart';
export 'src/gallery_header.dart';
+export 'src/gradient_avatar.dart';
export 'src/info_tile.dart';
+export 'src/localization/stream_chat_localizations.dart';
+export 'src/localization/translations.dart' show DefaultTranslations;
export 'src/mention_tile.dart';
export 'src/message_action.dart';
export 'src/message_input.dart';
@@ -41,3 +45,4 @@ export 'src/user_item.dart';
export 'src/user_list_view.dart';
export 'src/user_list_view.dart';
export 'src/utils.dart';
+export 'src/visible_footnote.dart';
diff --git a/packages/stream_chat_flutter/lib/svgs/Icon_up.svg b/packages/stream_chat_flutter/lib/svgs/Icon_up.svg
new file mode 100644
index 00000000..60e6888f
--- /dev/null
+++ b/packages/stream_chat_flutter/lib/svgs/Icon_up.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml
index 6dd29200..f47fa9f5 100644
--- a/packages/stream_chat_flutter/pubspec.yaml
+++ b/packages/stream_chat_flutter/pubspec.yaml
@@ -1,12 +1,13 @@
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.0.0
+version: 2.1.1
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
environment:
sdk: '>=2.12.0 <3.0.0'
+ flutter: ">=1.17.0"
dependencies:
cached_network_image: ^3.0.0
@@ -30,13 +31,13 @@ dependencies:
lottie: ^1.0.1
meta: ^1.3.0
path_provider: ^2.0.1
- photo_manager: ^1.1.6
+ photo_manager: ^1.2.6+1
photo_view: ^0.12.0
rxdart: ^0.27.0
scrollable_positioned_list: ^0.2.0-nullsafety.0
share_plus: ^2.0.3
shimmer: ^2.0.0
- stream_chat_flutter_core: ^2.0.0
+ stream_chat_flutter_core: ^2.1.1
substring_highlight: ^1.0.26
synchronized: ^3.0.0
url_launcher: ^6.0.3
@@ -58,5 +59,6 @@ dev_dependencies:
sdk: flutter
golden_toolkit: ^0.9.0
mocktail: ^0.1.2
+ path: ^1.8.0
pedantic: ^1.11.0
diff --git a/packages/stream_chat_flutter/test/flutter_test_config.dart b/packages/stream_chat_flutter/test/flutter_test_config.dart
index c09db700..bfda23d0 100644
--- a/packages/stream_chat_flutter/test/flutter_test_config.dart
+++ b/packages/stream_chat_flutter/test/flutter_test_config.dart
@@ -1,9 +1,35 @@
import 'dart:async';
+import 'dart:typed_data';
+import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
Future testExecutable(FutureOr Function() testMain) async {
await loadAppFonts();
+ goldenFileComparator =
+ CustomGoldenFileComparator(Uri.parse('test/src/goldens'));
return testMain();
}
+
+class CustomGoldenFileComparator extends LocalFileComparator {
+ CustomGoldenFileComparator(Uri testFile) : super(testFile);
+
+ @override
+ Future compare(Uint8List imageBytes, Uri golden) async {
+ final result = await GoldenFileComparator.compareLists(
+ imageBytes,
+ await getGoldenBytes(golden),
+ );
+
+ if (!result.passed && result.diffPercent > 0.05) {
+ final error = await generateFailureOutput(result, golden, basedir);
+ throw FlutterError(error);
+ }
+ return true;
+ }
+
+ @override
+ Future update(Uri golden, Uint8List imageBytes) =>
+ super.update(golden, imageBytes);
+}
diff --git a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart
index 3f210742..c8832f6f 100644
--- a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart
+++ b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart
@@ -35,7 +35,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -77,7 +77,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id2'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id2'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -119,7 +119,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -160,7 +160,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -207,7 +207,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -254,7 +254,7 @@ void main() {
when(() => mockChannel.updateMessage(any()))
.thenAnswer((_) async => UpdateMessageResponse());
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final message = Message(
text: 'test',
@@ -308,7 +308,7 @@ void main() {
when(() => mockChannel.updateMessage(any()))
.thenAnswer((_) async => UpdateMessageResponse());
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final message = Message(
text: 'test',
@@ -357,7 +357,7 @@ void main() {
when(() => mockChannel.deleteMessage(any()))
.thenAnswer((_) async => EmptyResponse());
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final message = Message(
user: User(
@@ -399,7 +399,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final imageDownloader = MockAttachmentDownloader();
@@ -454,7 +454,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final fileDownloader = MockAttachmentDownloader();
diff --git a/packages/stream_chat_flutter/test/src/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel_header_test.dart
index 211a7132..f7157871 100644
--- a/packages/stream_chat_flutter/test/src/channel_header_test.dart
+++ b/packages/stream_chat_flutter/test/src/channel_header_test.dart
@@ -18,8 +18,9 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(user);
- when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
+ when(() => clientState.currentUser).thenReturn(user);
+ when(() => clientState.currentUserStream)
+ .thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -82,8 +83,9 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(user);
- when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
+ when(() => clientState.currentUser).thenReturn(user);
+ when(() => clientState.currentUserStream)
+ .thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -149,8 +151,9 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(user);
- when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
+ when(() => clientState.currentUser).thenReturn(user);
+ when(() => clientState.currentUserStream)
+ .thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -217,8 +220,9 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(user);
- when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
+ when(() => clientState.currentUser).thenReturn(user);
+ when(() => clientState.currentUserStream)
+ .thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -293,8 +297,9 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(user);
- when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
+ when(() => clientState.currentUser).thenReturn(user);
+ when(() => clientState.currentUserStream)
+ .thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -360,8 +365,9 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(user);
- when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
+ when(() => clientState.currentUser).thenReturn(user);
+ when(() => clientState.currentUserStream)
+ .thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
diff --git a/packages/stream_chat_flutter/test/src/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel_image_test.dart
index 7c620774..c9293e79 100644
--- a/packages/stream_chat_flutter/test/src/channel_image_test.dart
+++ b/packages/stream_chat_flutter/test/src/channel_image_test.dart
@@ -17,7 +17,7 @@ void main() {
final channelState = MockChannelState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
@@ -56,7 +56,7 @@ void main() {
final channelState = MockChannelState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
@@ -135,7 +135,7 @@ void main() {
final currentUser = OwnUser(id: 'user-id');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(currentUser);
+ when(() => clientState.currentUser).thenReturn(currentUser);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
@@ -207,7 +207,7 @@ void main() {
final channelState = MockChannelState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
diff --git a/packages/stream_chat_flutter/test/src/channel_list_header_test.dart b/packages/stream_chat_flutter/test/src/channel_list_header_test.dart
index f2f054d4..a2783067 100644
--- a/packages/stream_chat_flutter/test/src/channel_list_header_test.dart
+++ b/packages/stream_chat_flutter/test/src/channel_list_header_test.dart
@@ -13,7 +13,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.connected));
@@ -30,7 +30,7 @@ void main() {
await tester.pumpAndSettle();
final userAvatar = tester.widget(find.byType(UserAvatar));
- expect(userAvatar.user, clientState.user);
+ expect(userAvatar.user, clientState.currentUser);
expect(find.byType(StreamNeumorphicButton), findsOneWidget);
expect(find.text('Stream Chat'), findsOneWidget);
},
@@ -43,7 +43,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.disconnected));
@@ -72,7 +72,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.connecting));
@@ -101,7 +101,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.connecting));
@@ -139,7 +139,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.connecting));
@@ -173,7 +173,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.connecting));
diff --git a/packages/stream_chat_flutter/test/src/channel_name_test.dart b/packages/stream_chat_flutter/test/src/channel_name_test.dart
index c8d3df9b..fc868eb0 100644
--- a/packages/stream_chat_flutter/test/src/channel_name_test.dart
+++ b/packages/stream_chat_flutter/test/src/channel_name_test.dart
@@ -16,7 +16,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
diff --git a/packages/stream_chat_flutter/test/src/channel_preview_test.dart b/packages/stream_chat_flutter/test/src/channel_preview_test.dart
index 6add0c60..7d0c549e 100644
--- a/packages/stream_chat_flutter/test/src/channel_preview_test.dart
+++ b/packages/stream_chat_flutter/test/src/channel_preview_test.dart
@@ -18,8 +18,9 @@ void main() {
when(() => channel.cid).thenReturn('cid');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(user);
- when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
+ when(() => clientState.currentUser).thenReturn(user);
+ when(() => clientState.currentUserStream)
+ .thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
diff --git a/packages/stream_chat_flutter/test/src/date_divider_test.dart b/packages/stream_chat_flutter/test/src/date_divider_test.dart
index eb5e9b70..f84d4dce 100644
--- a/packages/stream_chat_flutter/test/src/date_divider_test.dart
+++ b/packages/stream_chat_flutter/test/src/date_divider_test.dart
@@ -13,7 +13,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
diff --git a/packages/stream_chat_flutter/test/src/default_translations_test.dart b/packages/stream_chat_flutter/test/src/default_translations_test.dart
new file mode 100644
index 00000000..e78c0115
--- /dev/null
+++ b/packages/stream_chat_flutter/test/src/default_translations_test.dart
@@ -0,0 +1,174 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:stream_chat_flutter/stream_chat_flutter.dart';
+
+void main() {
+ test('Default translations should exist', () {
+ const translations = DefaultTranslations.instance;
+ expect(translations.launchUrlError, isNotNull);
+ expect(translations.loadingUsersError, isNotNull);
+ expect(translations.noUsersLabel, isNotNull);
+ expect(translations.retryLabel, isNotNull);
+ expect(translations.userLastOnlineText, isNotNull);
+ expect(translations.userOnlineText, isNotNull);
+ expect(translations.userOnlineText, isNotNull);
+ // no users
+ expect(translations.userTypingText([]), isNotNull);
+ // single user
+ expect(translations.userTypingText([User(id: 'test-id')]), isNotNull);
+ // multiple users
+ expect(
+ translations.userTypingText([
+ User(id: 'test-id-1'),
+ User(id: 'test-id-2'),
+ ]),
+ isNotNull,
+ );
+ expect(translations.threadReplyLabel, isNotNull);
+ expect(translations.onlyVisibleToYouText, isNotNull);
+ expect(translations.threadReplyCountText(3), isNotNull);
+ expect(
+ translations.attachmentsUploadProgressText(remaining: 3, total: 10),
+ isNotNull,
+ );
+ expect(
+ translations.pinnedByUserText(
+ pinnedBy: User(id: 'pinned-by-user-id'),
+ currentUser: OwnUser(id: 'current-user-id'),
+ ),
+ isNotNull,
+ );
+ expect(translations.emptyMessagesText, isNotNull);
+ expect(translations.genericErrorText, isNotNull);
+ expect(translations.loadingMessagesError, isNotNull);
+ expect(translations.resultCountText(3), isNotNull);
+ expect(translations.messageDeletedText, isNotNull);
+ expect(translations.messageDeletedLabel, isNotNull);
+ expect(translations.messageReactionsLabel, isNotNull);
+ expect(translations.emptyChatMessagesText, isNotNull);
+ expect(translations.threadSeparatorText(3), isNotNull);
+ expect(translations.connectedLabel, isNotNull);
+ expect(translations.disconnectedLabel, isNotNull);
+ expect(translations.reconnectingLabel, isNotNull);
+ expect(translations.alsoSendAsDirectMessageLabel, isNotNull);
+ expect(translations.addACommentOrSendLabel, isNotNull);
+ expect(translations.searchGifLabel, isNotNull);
+ expect(translations.writeAMessageLabel, isNotNull);
+ expect(translations.instantCommandsLabel, isNotNull);
+ expect(translations.fileTooLargeAfterCompressionError(33), isNotNull);
+ expect(translations.fileTooLargeError(33), isNotNull);
+ expect(translations.emojiMatchingQueryText('sahil'), isNotNull);
+ expect(translations.addAFileLabel, isNotNull);
+ expect(translations.photoFromCameraLabel, isNotNull);
+ expect(translations.uploadAFileLabel, isNotNull);
+ expect(translations.uploadAPhotoLabel, isNotNull);
+ expect(translations.uploadAVideoLabel, isNotNull);
+ expect(translations.videoFromCameraLabel, isNotNull);
+ expect(translations.okLabel, isNotNull);
+ expect(translations.somethingWentWrongError, isNotNull);
+ expect(translations.addMoreFilesLabel, isNotNull);
+ expect(translations.enablePhotoAndVideoAccessMessage, isNotNull);
+ expect(translations.allowGalleryAccessMessage, isNotNull);
+ expect(translations.flagMessageLabel, isNotNull);
+ expect(translations.flagMessageQuestion, isNotNull);
+ expect(translations.flagLabel, isNotNull);
+ expect(translations.cancelLabel, isNotNull);
+ expect(translations.flagMessageSuccessfulLabel, isNotNull);
+ expect(translations.flagMessageSuccessfulText, isNotNull);
+ expect(translations.deleteLabel, isNotNull);
+ expect(translations.deleteMessageLabel, isNotNull);
+ expect(translations.deleteMessageQuestion, isNotNull);
+ expect(translations.operationCouldNotBeCompletedText, isNotNull);
+ expect(translations.replyLabel, isNotNull);
+ // pinned
+ expect(translations.togglePinUnpinText(pinned: true), isNotNull);
+ // un-pinned
+ expect(translations.togglePinUnpinText(pinned: false), isNotNull);
+ // delete-failed
+ expect(
+ translations.toggleDeleteRetryDeleteMessageText(isDeleteFailed: true),
+ isNotNull,
+ );
+ // first-delete
+ expect(
+ translations.toggleDeleteRetryDeleteMessageText(isDeleteFailed: false),
+ isNotNull,
+ );
+ expect(translations.copyMessageLabel, isNotNull);
+ expect(translations.editMessageLabel, isNotNull);
+ // resend-failed
+ expect(
+ translations.toggleResendOrResendEditedMessage(isUpdateFailed: true),
+ isNotNull,
+ );
+ // first resend
+ expect(
+ translations.toggleResendOrResendEditedMessage(isUpdateFailed: false),
+ isNotNull,
+ );
+ expect(translations.photosLabel, isNotNull);
+ // today
+ expect(
+ translations.sentAtText(
+ date: DateTime.now(),
+ time: DateTime.now(),
+ ),
+ isNotNull,
+ );
+ // yesterday
+ expect(
+ translations.sentAtText(
+ date: DateTime.now().subtract(const Duration(days: 1)),
+ time: DateTime.now(),
+ ),
+ isNotNull,
+ );
+ // any other day
+ expect(
+ translations.sentAtText(
+ date: DateTime.now().subtract(const Duration(days: 3)),
+ time: DateTime.now(),
+ ),
+ isNotNull,
+ );
+ expect(translations.todayLabel, isNotNull);
+ expect(translations.yesterdayLabel, isNotNull);
+ expect(translations.channelIsMutedText, isNotNull);
+ expect(translations.noTitleText, isNotNull);
+ expect(translations.letsStartChattingLabel, isNotNull);
+ expect(translations.sendingFirstMessageLabel, isNotNull);
+ expect(translations.startAChatLabel, isNotNull);
+ expect(translations.loadingChannelsError, isNotNull);
+ expect(translations.deleteConversationLabel, isNotNull);
+ expect(translations.deleteConversationQuestion, isNotNull);
+ expect(translations.streamChatLabel, isNotNull);
+ expect(translations.searchingForNetworkText, isNotNull);
+ expect(translations.offlineLabel, isNotNull);
+ expect(translations.tryAgainLabel, isNotNull);
+ // 1 member
+ expect(translations.membersCountText(1), isNotNull);
+ // 3 members
+ expect(translations.membersCountText(3), isNotNull);
+ // 1 member
+ expect(translations.watchersCountText(1), isNotNull);
+ // 3 members
+ expect(translations.watchersCountText(3), isNotNull);
+ expect(translations.viewInfoLabel, isNotNull);
+ expect(translations.leaveGroupLabel, isNotNull);
+ expect(translations.leaveLabel, isNotNull);
+ expect(translations.leaveConversationLabel, isNotNull);
+ expect(translations.leaveConversationQuestion, isNotNull);
+ expect(translations.showInChatLabel, isNotNull);
+ expect(translations.saveImageLabel, isNotNull);
+ expect(translations.saveVideoLabel, isNotNull);
+ expect(translations.uploadErrorLabel, isNotNull);
+ expect(translations.giphyLabel, isNotNull);
+ expect(translations.shuffleLabel, isNotNull);
+ expect(translations.sendLabel, isNotNull);
+ expect(translations.withText, isNotNull);
+ expect(translations.inText, isNotNull);
+ expect(translations.youText, isNotNull);
+ expect(translations.ofText, isNotNull);
+ expect(translations.fileText, isNotNull);
+ expect(translations.replyToMessageLabel, isNotNull);
+ });
+}
diff --git a/packages/stream_chat_flutter/test/src/deleted_message_test.dart b/packages/stream_chat_flutter/test/src/deleted_message_test.dart
index 0e30d2b3..ac953ee9 100644
--- a/packages/stream_chat_flutter/test/src/deleted_message_test.dart
+++ b/packages/stream_chat_flutter/test/src/deleted_message_test.dart
@@ -14,7 +14,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
@@ -46,7 +46,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -99,7 +99,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -152,7 +152,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
diff --git a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart
index 03f6ea65..2d271090 100644
--- a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart
+++ b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart
@@ -17,7 +17,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
diff --git a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_0.png b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_0.png
new file mode 100644
index 00000000..bf15f6af
Binary files /dev/null and b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_0.png differ
diff --git a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_1.png b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_1.png
new file mode 100644
index 00000000..bd466f79
Binary files /dev/null and b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_1.png differ
diff --git a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_2.png b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_2.png
new file mode 100644
index 00000000..d7d671c4
Binary files /dev/null and b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_2.png differ
diff --git a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_3.png b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_3.png
new file mode 100644
index 00000000..5381c0f0
Binary files /dev/null and b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_3.png differ
diff --git a/packages/stream_chat_flutter/test/src/goldens/message_text.png b/packages/stream_chat_flutter/test/src/goldens/message_text.png
index ecd93721..dda158b2 100644
Binary files a/packages/stream_chat_flutter/test/src/goldens/message_text.png and b/packages/stream_chat_flutter/test/src/goldens/message_text.png differ
diff --git a/packages/stream_chat_flutter/test/src/gradient_avatar_test.dart b/packages/stream_chat_flutter/test/src/gradient_avatar_test.dart
new file mode 100644
index 00000000..6f7ebe2e
--- /dev/null
+++ b/packages/stream_chat_flutter/test/src/gradient_avatar_test.dart
@@ -0,0 +1,127 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:golden_toolkit/golden_toolkit.dart';
+import 'package:mocktail/mocktail.dart';
+import 'package:stream_chat_flutter/src/gradient_avatar.dart';
+import 'package:stream_chat_flutter/stream_chat_flutter.dart';
+
+import 'mocks.dart';
+
+void main() {
+ testWidgets(
+ 'control test',
+ (WidgetTester tester) async {
+ final client = MockClient();
+ final clientState = MockClientState();
+
+ when(() => client.state).thenReturn(clientState);
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
+
+ await tester.pumpWidget(
+ MaterialApp(
+ home: StreamChat(
+ client: client,
+ child: const Scaffold(
+ body: Center(
+ child: SizedBox(
+ width: 100,
+ height: 100,
+ child: GradientAvatar(name: 'demo user', userId: 'demo123'),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ expect(find.byType(GradientAvatar), findsOneWidget);
+ },
+ );
+
+ testGoldens(
+ 'golden test for the name "demo user"',
+ (WidgetTester tester) async {
+ await tester.pumpWidget(
+ const MaterialApp(
+ home: Scaffold(
+ body: Center(
+ child: SizedBox(
+ width: 100,
+ height: 100,
+ child: GradientAvatar(name: 'demo user', userId: 'demo123'),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ await screenMatchesGolden(tester, 'gradient_avatar_0');
+ },
+ );
+
+ testGoldens(
+ 'golden test for the name "demo"',
+ (WidgetTester tester) async {
+ await tester.pumpWidget(
+ const MaterialApp(
+ home: Scaffold(
+ body: Center(
+ child: SizedBox(
+ width: 100,
+ height: 100,
+ child: GradientAvatar(name: 'demo', userId: 'demo1'),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ await screenMatchesGolden(tester, 'gradient_avatar_1');
+ },
+ );
+
+ testGoldens(
+ 'control special character test',
+ (WidgetTester tester) async {
+ await tester.pumpWidget(
+ const MaterialApp(
+ home: Scaffold(
+ body: Center(
+ child: SizedBox(
+ width: 100,
+ height: 100,
+ child: GradientAvatar(
+ name: 'd123@/d de:\$as',
+ userId: 'demo123',
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ await screenMatchesGolden(tester, 'gradient_avatar_2');
+ },
+ );
+
+ testGoldens(
+ 'control special character test 2',
+ (WidgetTester tester) async {
+ await tester.pumpWidget(
+ const MaterialApp(
+ home: Scaffold(
+ body: Center(
+ child: SizedBox(
+ width: 100,
+ height: 100,
+ child: GradientAvatar(name: '123@/d \$as', userId: 'demo123'),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ await screenMatchesGolden(tester, 'gradient_avatar_3');
+ },
+ );
+}
diff --git a/packages/stream_chat_flutter/test/src/image_footer_test.dart b/packages/stream_chat_flutter/test/src/image_footer_test.dart
index 0fe3349a..434a78d4 100644
--- a/packages/stream_chat_flutter/test/src/image_footer_test.dart
+++ b/packages/stream_chat_flutter/test/src/image_footer_test.dart
@@ -16,7 +16,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
diff --git a/packages/stream_chat_flutter/test/src/info_tile_test.dart b/packages/stream_chat_flutter/test/src/info_tile_test.dart
index 02387edc..f175ca9a 100644
--- a/packages/stream_chat_flutter/test/src/info_tile_test.dart
+++ b/packages/stream_chat_flutter/test/src/info_tile_test.dart
@@ -14,7 +14,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
@@ -44,7 +44,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
diff --git a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart
index 953434de..38518b19 100644
--- a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart
+++ b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart
@@ -20,7 +20,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -66,7 +66,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -117,7 +117,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -172,7 +172,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -218,7 +218,7 @@ void main() {
final clientState = MockClientState();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -265,7 +265,7 @@ void main() {
final channel = MockChannel();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -314,7 +314,7 @@ void main() {
final channel = MockChannel();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -364,7 +364,7 @@ void main() {
final channel = MockChannel();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -414,7 +414,7 @@ void main() {
final channel = MockChannel();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.sendMessage(any()))
.thenAnswer((_) async => SendMessageResponse());
@@ -464,7 +464,7 @@ void main() {
final channel = MockChannel();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.updateMessage(any()))
.thenAnswer((_) async => UpdateMessageResponse());
@@ -514,7 +514,7 @@ void main() {
final channel = MockChannel();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -568,7 +568,7 @@ void main() {
final channel = MockChannel();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => client.flagMessage(any()))
.thenThrow(StreamChatNetworkError(ChatErrorCode.internalSystemError));
@@ -624,7 +624,7 @@ void main() {
final channel = MockChannel();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => client.flagMessage(any()))
.thenThrow(StreamChatNetworkError(ChatErrorCode.inputError));
@@ -680,7 +680,7 @@ void main() {
final channel = MockChannel();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -717,7 +717,7 @@ void main() {
await tester.tap(find.text('Delete Message'));
await tester.pumpAndSettle();
- expect(find.text('Delete message'), findsOneWidget);
+ expect(find.text('Delete Message'), findsOneWidget);
await tester.tap(find.text('DELETE'));
await tester.pumpAndSettle();
@@ -734,7 +734,7 @@ void main() {
final channel = MockChannel();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.deleteMessage(any()))
.thenThrow(StreamChatNetworkError(ChatErrorCode.internalSystemError));
@@ -773,7 +773,7 @@ void main() {
await tester.tap(find.text('Delete Message'));
await tester.pumpAndSettle();
- expect(find.text('Delete message'), findsOneWidget);
+ expect(find.text('Delete Message'), findsOneWidget);
await tester.tap(find.text('DELETE'));
await tester.pumpAndSettle();
diff --git a/packages/stream_chat_flutter/test/src/message_input_test.dart b/packages/stream_chat_flutter/test/src/message_input_test.dart
index f07f518d..679fb7f3 100644
--- a/packages/stream_chat_flutter/test/src/message_input_test.dart
+++ b/packages/stream_chat_flutter/test/src/message_input_test.dart
@@ -16,7 +16,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
diff --git a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart
index 9452e7e6..3732ca0d 100644
--- a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart
+++ b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart
@@ -16,7 +16,7 @@ void main() {
final themeData = ThemeData();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final streamTheme = StreamChatThemeData.fromTheme(themeData);
@@ -61,7 +61,7 @@ void main() {
final themeData = ThemeData();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final streamTheme = StreamChatThemeData.fromTheme(themeData);
diff --git a/packages/stream_chat_flutter/test/src/message_text_test.dart b/packages/stream_chat_flutter/test/src/message_text_test.dart
index f64e2bf1..5bfe0a34 100644
--- a/packages/stream_chat_flutter/test/src/message_text_test.dart
+++ b/packages/stream_chat_flutter/test/src/message_text_test.dart
@@ -8,10 +8,33 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
import 'simple_frame.dart';
+void expectTextStrings(Iterable widgets, List strings) {
+ var currentString = 0;
+ for (final widget in widgets) {
+ if (widget is RichText) {
+ final span = widget.text as TextSpan;
+ final text = _extractTextFromTextSpan(span);
+ expect(text, equals(strings[currentString]));
+ currentString += 1;
+ }
+ }
+}
+
+String _extractTextFromTextSpan(TextSpan span) {
+ var text = span.text ?? '';
+ if (span.children != null) {
+ for (final child in span.children! as Iterable) {
+ text += _extractTextFromTextSpan(child);
+ }
+ }
+ return text;
+}
+
void main() {
testWidgets(
'it should show correct message text',
(WidgetTester tester) async {
+ final currentUser = OwnUser(id: 'user-id');
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
@@ -21,7 +44,9 @@ void main() {
final streamTheme = StreamChatThemeData.fromTheme(themeData);
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(currentUser);
+ when(() => clientState.currentUserStream)
+ .thenAnswer((_) => Stream.value(currentUser));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -54,9 +79,107 @@ void main() {
},
);
+ group('Message with i18n field', () {
+ final client = MockClient();
+ final clientState = MockClientState();
+ final channel = MockChannel();
+ final channelState = MockChannelState();
+ const messageTheme = MessageTheme();
+
+ final currentUser = OwnUser(
+ id: 'sahil',
+ language: 'hi',
+ );
+
+ setUp(() {
+ when(() => client.state).thenReturn(clientState);
+ when(() => clientState.currentUser).thenReturn(currentUser);
+ when(() => clientState.currentUserStream)
+ .thenAnswer((_) => Stream.value(currentUser));
+
+ when(() => channel.state).thenReturn(channelState);
+ when(() => channel.client).thenReturn(client);
+ when(() => channel.isMuted).thenReturn(false);
+ when(() => channel.isMutedStream).thenAnswer((_) => Stream.value(false));
+ });
+
+ testWidgets(
+ 'should show correct translated message text as per user language',
+ (WidgetTester tester) async {
+ final message = Message(
+ text: 'Hello',
+ i18n: const {
+ 'en_text': 'Hello',
+ 'hi_text': 'नमस्ते',
+ 'language': 'en',
+ },
+ );
+
+ await tester.pumpWidget(
+ MaterialApp(
+ home: StreamChat(
+ client: client,
+ child: StreamChannel(
+ channel: channel,
+ child: Scaffold(
+ body: MessageText(
+ message: message,
+ messageTheme: messageTheme,
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ expect(find.byType(MarkdownBody), findsOneWidget);
+
+ final widgets = tester.allWidgets;
+ expectTextStrings(widgets, ['नमस्ते']);
+ },
+ );
+
+ testWidgets(
+ '''should show default text if i18n does not contain translations as per user language''',
+ (WidgetTester tester) async {
+ final message = Message(
+ text: 'Hello',
+ i18n: const {
+ 'en_text': 'Hello',
+ 'fr_text': 'Bonjour',
+ 'language': 'en',
+ },
+ );
+
+ await tester.pumpWidget(
+ MaterialApp(
+ home: StreamChat(
+ client: client,
+ child: StreamChannel(
+ channel: channel,
+ child: Scaffold(
+ body: MessageText(
+ message: message,
+ messageTheme: messageTheme,
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ expect(find.byType(MarkdownBody), findsOneWidget);
+
+ final widgets = tester.allWidgets;
+ expectTextStrings(widgets, ['Hello']);
+ },
+ );
+ });
+
testGoldens(
'control test',
(WidgetTester tester) async {
+ final currentUser = OwnUser(id: 'user-id');
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
@@ -66,7 +189,9 @@ void main() {
final streamTheme = StreamChatThemeData.fromTheme(themeData);
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(currentUser);
+ when(() => clientState.currentUserStream)
+ .thenAnswer((_) => Stream.value(currentUser));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -80,7 +205,7 @@ void main() {
});
const messageText = '''
- a message.
+a message.
with multiple lines
and a list:
- a. okasd
@@ -90,14 +215,18 @@ cool.''';
await tester.pumpWidgetBuilder(
materialAppWrapper()(SimpleFrame(
- child: StreamChannel(
- channel: channel,
- child: Scaffold(
- body: MessageText(
- message: Message(
- text: messageText,
+ child: StreamChat(
+ client: client,
+ connectivityStream: Stream.value(ConnectivityResult.wifi),
+ child: StreamChannel(
+ channel: channel,
+ child: Scaffold(
+ body: MessageText(
+ message: Message(
+ text: messageText,
+ ),
+ messageTheme: streamTheme.otherMessageTheme,
),
- messageTheme: streamTheme.otherMessageTheme,
),
),
),
diff --git a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart
index 1e3c9f5e..6e823b43 100644
--- a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart
+++ b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart
@@ -6,32 +6,8 @@ import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
-import 'simple_frame.dart';
void main() {
- testGoldens(
- 'it should show no reactions',
- (WidgetTester tester) async {
- await tester.pumpWidgetBuilder(
- SimpleFrame(
- child: StreamChatTheme(
- data: StreamChatThemeData(),
- child: const SizedBox(
- child: ReactionBubble(
- reactions: [],
- borderColor: Colors.black,
- backgroundColor: Colors.white,
- maskColor: Colors.white,
- ),
- ),
- ),
- ),
- surfaceSize: const Size(100, 100),
- );
- await screenMatchesGolden(tester, 'reaction_bubble_0');
- },
- );
-
testGoldens(
'it should show a like - light theme',
(WidgetTester tester) async {
@@ -40,7 +16,7 @@ void main() {
final themeData = ThemeData.light();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
final theme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidgetBuilder(
@@ -77,7 +53,7 @@ void main() {
final theme = StreamChatThemeData.fromTheme(themeData);
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidgetBuilder(
StreamChat(
@@ -114,7 +90,7 @@ void main() {
final theme = StreamChatThemeData.fromTheme(themeData);
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidgetBuilder(
StreamChat(
@@ -159,7 +135,7 @@ void main() {
final theme = StreamChatThemeData.fromTheme(themeData);
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidgetBuilder(
StreamChat(
@@ -203,7 +179,7 @@ void main() {
final themeData = ThemeData();
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidgetBuilder(
StreamChat(
diff --git a/packages/stream_chat_flutter/test/src/system_message_test.dart b/packages/stream_chat_flutter/test/src/system_message_test.dart
index 2e221d57..24eccdf6 100644
--- a/packages/stream_chat_flutter/test/src/system_message_test.dart
+++ b/packages/stream_chat_flutter/test/src/system_message_test.dart
@@ -17,7 +17,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -68,7 +68,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -120,7 +120,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
diff --git a/packages/stream_chat_flutter/test/src/thread_header_test.dart b/packages/stream_chat_flutter/test/src/thread_header_test.dart
index 2b649be3..1bc4c865 100644
--- a/packages/stream_chat_flutter/test/src/thread_header_test.dart
+++ b/packages/stream_chat_flutter/test/src/thread_header_test.dart
@@ -16,7 +16,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -81,7 +81,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
diff --git a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart
index 6c8a3351..8d790266 100644
--- a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart
+++ b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart
@@ -16,7 +16,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
diff --git a/packages/stream_chat_flutter/test/src/unread_indicator_test.dart b/packages/stream_chat_flutter/test/src/unread_indicator_test.dart
index 96a6c678..00359089 100644
--- a/packages/stream_chat_flutter/test/src/unread_indicator_test.dart
+++ b/packages/stream_chat_flutter/test/src/unread_indicator_test.dart
@@ -16,7 +16,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -58,7 +58,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => clientState.channels).thenReturn({
channel.cid!: channel,
});
@@ -98,7 +98,7 @@ void main() {
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
- when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
+ when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => clientState.channels).thenReturn({
channel.cid!: channel,
});
diff --git a/packages/stream_chat_flutter/test/utils/golden.dart b/packages/stream_chat_flutter/test/utils/golden.dart
new file mode 100644
index 00000000..4fc0aff3
--- /dev/null
+++ b/packages/stream_chat_flutter/test/utils/golden.dart
@@ -0,0 +1,58 @@
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:golden_toolkit/golden_toolkit.dart';
+import 'package:golden_toolkit/src/testing_tools.dart';
+import 'package:path/path.dart' as path;
+
+const double _kGoldenDiffTolerance = 0.05;
+
+/// Wrapper function for golden tests.
+Future customExpectGoldenMatches(
+ WidgetTester tester,
+ String name, {
+ bool? autoHeight,
+ Finder? finder,
+ CustomPump? customPump,
+ @Deprecated('''
+This method level parameter will be removed in an upcoming release. This can be configured globally. If you have concerns, please file an issue with your use case.''') bool? skip,
+}) {
+ final goldenPath = path.join('test/src/goldens');
+ print('goldenPath: $goldenPath');
+ goldenFileComparator = CustomGoldenFileComparator(Uri.parse(goldenPath));
+
+ return compareWithGolden(
+ tester,
+ name,
+ autoHeight: autoHeight,
+ finder: finder,
+ customPump: customPump,
+ skip: skip,
+ // This value is actually ignored. We are forced to pass it because the
+ // downstream API is structured poorly. This should be refactored.
+ device: Device.phone,
+ fileNameFactory: (String name, Device device) =>
+ GoldenToolkit.configuration.fileNameFactory(name),
+ );
+}
+
+class CustomGoldenFileComparator extends LocalFileComparator {
+ CustomGoldenFileComparator(Uri testFile) : super(testFile);
+
+ @override
+ Future compare(Uint8List imageBytes, Uri golden) async {
+ print('golden.toString(): ${golden.toString()}');
+ final result = await GoldenFileComparator.compareLists(
+ imageBytes,
+ await getGoldenBytes(golden),
+ );
+
+ if (!result.passed && result.diffPercent > _kGoldenDiffTolerance) {
+ final error = await generateFailureOutput(result, golden, basedir);
+ throw FlutterError(error);
+ }
+ return result.passed;
+ }
+}
diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md
index 2f7abfe2..ef24e401 100644
--- a/packages/stream_chat_flutter_core/CHANGELOG.md
+++ b/packages/stream_chat_flutter_core/CHANGELOG.md
@@ -1,3 +1,19 @@
+## 2.1.1
+
+- Updated llc dependency
+
+## 2.1.0
+
+🛑️ Breaking Changes from `2.0.0`
+- Changed default message filter of `MessageListCore`
+
+✅ Added
+- Added `MessageListCore.paginationLimit`
+
+🔄 Changed
+- `StreamChatCore.of(context).user` is now deprecated in favor of `StreamChatCore.of(context).currentUser`.
+- `StreamChatCore.of(context).userStream` is now deprecated in favor of `StreamChatCore.of(context).currentUserStream`.
+
## 2.0.0
🛑️ Breaking Changes from `1.5.3`
diff --git a/packages/stream_chat_flutter_core/README.md b/packages/stream_chat_flutter_core/README.md
index 4aaf27d9..462498f6 100644
--- a/packages/stream_chat_flutter_core/README.md
+++ b/packages/stream_chat_flutter_core/README.md
@@ -12,6 +12,7 @@
- [Register](https://getstream.io/chat/trial/) to get an API key for Stream Chat
- [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/)
- [Chat UI Kit](https://getstream.io/chat/ui-kit/)
+- [Core Docs](https://getstream.io/chat/docs/sdk/flutter/stream_chat_flutter_core/introduction/)
### Changelog
diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart
index 4989f38f..0a7b0bce 100644
--- a/packages/stream_chat_flutter_core/example/lib/main.dart
+++ b/packages/stream_chat_flutter_core/example/lib/main.dart
@@ -87,7 +87,7 @@ class HomeScreen extends StatelessWidget {
filter: Filter.and([
Filter.equal('type', 'messaging'),
Filter.in_('members', [
- StreamChatCore.of(context).user!.id,
+ StreamChatCore.of(context).currentUser!.id,
])
]),
emptyBuilder: (BuildContext context) => const Center(
@@ -336,7 +336,7 @@ class _MessageScreenState extends State {
/// below, we add two simple extensions to the [StreamChatClient] and [Channel].
extension on StreamChatClient {
/// Fetches the current user id.
- String get uid => state.user!.id;
+ String get uid => state.currentUser!.id;
}
extension on Channel {
diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart
index dc90cc91..d3d69dfb 100644
--- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart
+++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart
@@ -71,6 +71,7 @@ class MessageListCore extends StatefulWidget {
this.parentMessage,
this.messageListController,
this.messageFilter,
+ this.paginationLimit = 20,
}) : super(key: key);
/// A [MessageListController] allows pagination.
@@ -86,6 +87,9 @@ class MessageListCore extends StatefulWidget {
/// Function used to build an empty widget
final WidgetBuilder emptyBuilder;
+ /// Limit used to paginate messages
+ final int paginationLimit;
+
/// Callback triggered when an error occurs while performing the given
/// request.
///
@@ -112,7 +116,7 @@ class MessageListCoreState extends State {
bool get _isThreadConversation => widget.parentMessage != null;
- OwnUser? get _currentUser => _streamChannel!.channel.client.state.user;
+ OwnUser? get _currentUser => _streamChannel!.channel.client.state.currentUser;
var _messages = [];
@@ -130,8 +134,7 @@ class MessageListCoreState extends State {
bool defaultFilter(Message m) {
final isMyMessage = m.user?.id == _currentUser?.id;
- final isDeletedOrShadowed = m.isDeleted == true || m.shadowed == true;
- if (isDeletedOrShadowed && !isMyMessage) return false;
+ if (m.shadowed && !isMyMessage) return false;
return true;
}
@@ -163,13 +166,20 @@ class MessageListCoreState extends State {
/// Fetches more messages with updated pagination and updates the widget.
///
/// Optionally pass the fetch direction, defaults to [QueryDirection.top]
+ /// Optionally pass a limit, defaults to 20
Future paginateData({
QueryDirection direction = QueryDirection.top,
}) {
if (!_isThreadConversation) {
- return _streamChannel!.queryMessages(direction: direction);
+ return _streamChannel!.queryMessages(
+ direction: direction,
+ limit: widget.paginationLimit,
+ );
} else {
- return _streamChannel!.getReplies(widget.parentMessage!.id);
+ return _streamChannel!.getReplies(
+ widget.parentMessage!.id,
+ limit: widget.paginationLimit,
+ );
}
}
@@ -179,7 +189,10 @@ class MessageListCoreState extends State {
if (newStreamChannel != _streamChannel) {
if (_streamChannel == null /*only first time*/ && _isThreadConversation) {
- newStreamChannel.getReplies(widget.parentMessage!.id);
+ newStreamChannel.getReplies(
+ widget.parentMessage!.id,
+ limit: widget.paginationLimit,
+ );
}
_streamChannel = newStreamChannel;
}
@@ -197,7 +210,10 @@ class MessageListCoreState extends State {
if (widget.parentMessage?.id != widget.parentMessage?.id) {
if (_isThreadConversation) {
- _streamChannel!.getReplies(widget.parentMessage!.id);
+ _streamChannel!.getReplies(
+ widget.parentMessage!.id,
+ limit: widget.paginationLimit,
+ );
}
}
}
diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart
index 1dcf9c3c..fb7b79f2 100644
--- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart
+++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart
@@ -147,9 +147,18 @@ class StreamChannelState extends State {
}
/// Calls [channel.query] updating [queryMessage] stream
- Future queryMessages({QueryDirection? direction = QueryDirection.top}) {
- if (direction == QueryDirection.top) return _queryTopMessages();
- return _queryBottomMessages();
+ Future queryMessages({
+ QueryDirection? direction = QueryDirection.top,
+ int limit = 20,
+ }) {
+ if (direction == QueryDirection.top) {
+ return _queryTopMessages(
+ limit: limit,
+ );
+ }
+ return _queryBottomMessages(
+ limit: limit,
+ );
}
/// Calls [channel.getReplies] updating [queryMessage] stream
diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart
index 6e39d5e3..3d07b3c7 100644
--- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart
+++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart
@@ -97,11 +97,25 @@ class StreamChatCoreState extends State
@override
Widget build(BuildContext context) => widget.child;
+ // coverage:ignore-start
+
/// The current user
- User? get user => client.state.user;
+ @Deprecated('Use `.currentUser` instead, Will be removed in future releases')
+ User? get user => client.state.currentUser;
/// The current user as a stream
- Stream get userStream => client.state.userStream;
+ @Deprecated(
+ 'Use `.currentUserStream` instead, Will be removed in future releases',
+ )
+ Stream get userStream => client.state.currentUserStream;
+
+ // coverage:ignore-end
+
+ /// The current user
+ User? get currentUser => client.state.currentUser;
+
+ /// The current user as a stream
+ Stream get currentUserStream => client.state.currentUserStream;
StreamSubscription? _connectivitySubscription;
@@ -126,7 +140,7 @@ class StreamChatCoreState extends State
if (!_isInForeground) return;
if (_isConnectionAvailable) {
if (client.wsConnectionStatus == ConnectionStatus.disconnected &&
- user != null) {
+ currentUser != null) {
client.openConnection();
}
} else {
@@ -163,7 +177,7 @@ class StreamChatCoreState extends State
AppLifecycleState.resumed,
AppLifecycleState.inactive,
].contains(state);
- if (user != null) {
+ if (currentUser != null) {
if (_isInForeground) {
_onForeground();
} else {
diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml
index cc59f9fe..91fd4fe1 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.0.0
+version: 2.1.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.0.0
+ stream_chat: ^2.1.1
dev_dependencies:
fake_async: ^1.2.0
diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart
index b038411b..e61d0abb 100644
--- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart
+++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart
@@ -151,7 +151,9 @@ void main() {
(tester) async {
const messageListCoreKey = Key('messageListCore');
final controller = MessageListController();
+ const paginationLimit = 10;
final messageListCore = MessageListCore(
+ paginationLimit: paginationLimit,
key: messageListCoreKey,
messageListBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => const Offstage(),
@@ -165,10 +167,6 @@ void main() {
final mockChannel = MockChannel();
when(() => mockChannel.state.isUpToDate).thenReturn(true);
- // when(() => mockChannel.query(
- // messagesPagination: any(named: 'messagesPagination'),
- // preferOffline: any(named: 'preferOffline'),
- // )).thenAnswer((_) => mockChannel.state);
final messages = _generateMessages();
when(() => mockChannel.state.messages).thenReturn(messages);
when(() => mockChannel.state.messagesStream)
@@ -191,7 +189,10 @@ void main() {
await coreState.paginateData();
verify(() => mockChannel.query(
- messagesPagination: any(named: 'messagesPagination'),
+ messagesPagination: any(
+ named: 'messagesPagination',
+ that: wrapMatcher((it) => it.limit == paginationLimit),
+ ),
preferOffline: any(named: 'preferOffline'),
)).called(1);
},
diff --git a/packages/stream_chat_flutter_core/test/mocks.dart b/packages/stream_chat_flutter_core/test/mocks.dart
index 73227e7c..7013d408 100644
--- a/packages/stream_chat_flutter_core/test/mocks.dart
+++ b/packages/stream_chat_flutter_core/test/mocks.dart
@@ -18,10 +18,10 @@ class MockClient extends Mock implements StreamChatClient {
}
class MockClientState extends Mock implements ClientState {
- OwnUser? _user;
+ OwnUser? _currentUser;
@override
- OwnUser get user => _user ??= OwnUser(
+ OwnUser get currentUser => _currentUser ??= OwnUser(
id: 'testUserId',
role: 'admin',
createdAt: DateTime.now(),
diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart
index ee9a86cb..addcab87 100644
--- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart
+++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart
@@ -431,7 +431,7 @@ void main() {
);
testWidgets(
- 'streamChatCoreState.userStream should emit all the user events '
+ 'streamChatCoreState.currentUserStream should emit all the user events '
'provided by client',
(tester) async {
await tester.runAsync(() async {
@@ -451,7 +451,7 @@ void main() {
expect(find.byKey(streamChatCoreKey), findsOneWidget);
expect(find.byKey(childKey), findsOneWidget);
- when(() => mockClient.state.userStream)
+ when(() => mockClient.state.currentUserStream)
.thenAnswer((_) => userController.stream);
final streamChatCoreState = tester.state(
@@ -462,7 +462,7 @@ void main() {
userController.add(ownUser);
await expectLater(
- streamChatCoreState.userStream,
+ streamChatCoreState.currentUserStream,
emits(ownUser),
);
diff --git a/packages/stream_chat_localizations/.gitignore b/packages/stream_chat_localizations/.gitignore
new file mode 100644
index 00000000..1985397a
--- /dev/null
+++ b/packages/stream_chat_localizations/.gitignore
@@ -0,0 +1,74 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+build/
+
+# Android related
+**/android/**/gradle-wrapper.jar
+**/android/.gradle
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
+
+# iOS/XCode related
+**/ios/**/*.mode1v3
+**/ios/**/*.mode2v3
+**/ios/**/*.moved-aside
+**/ios/**/*.pbxuser
+**/ios/**/*.perspectivev3
+**/ios/**/*sync/
+**/ios/**/.sconsign.dblite
+**/ios/**/.tags*
+**/ios/**/.vagrant/
+**/ios/**/DerivedData/
+**/ios/**/Icon?
+**/ios/**/Pods/
+**/ios/**/.symlinks/
+**/ios/**/profile
+**/ios/**/xcuserdata
+**/ios/.generated/
+**/ios/Flutter/App.framework
+**/ios/Flutter/Flutter.framework
+**/ios/Flutter/Flutter.podspec
+**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/app.flx
+**/ios/Flutter/app.zip
+**/ios/Flutter/flutter_assets/
+**/ios/Flutter/flutter_export_environment.sh
+**/ios/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!**/ios/**/default.mode1v3
+!**/ios/**/default.mode2v3
+!**/ios/**/default.pbxuser
+!**/ios/**/default.perspectivev3
diff --git a/packages/stream_chat_localizations/.metadata b/packages/stream_chat_localizations/.metadata
new file mode 100644
index 00000000..936336f9
--- /dev/null
+++ b/packages/stream_chat_localizations/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: d79295af24c3ed621c33713ecda14ad196fd9c31
+ channel: stable
+
+project_type: package
diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md
new file mode 100644
index 00000000..4667ab93
--- /dev/null
+++ b/packages/stream_chat_localizations/CHANGELOG.md
@@ -0,0 +1,11 @@
+## 1.0.2
+
+* Updated stream_chat_flutter dependency
+
+## 1.0.1
+
+* Minor Fixes
+
+## 1.0.0
+
+* First release
diff --git a/packages/stream_chat_localizations/LICENSE b/packages/stream_chat_localizations/LICENSE
new file mode 100644
index 00000000..49088d47
--- /dev/null
+++ b/packages/stream_chat_localizations/LICENSE
@@ -0,0 +1,219 @@
+SOURCE CODE LICENSE AGREEMENT
+
+IMPORTANT - READ THIS CAREFULLY BEFORE DOWNLOADING, INSTALLING, USING OR
+ELECTRONICALLY ACCESSING THIS PROPRIETARY PRODUCT.
+
+THIS IS A LEGAL AGREEMENT BETWEEN STREAM.IO, INC. (“STREAM.IO”) AND THE
+BUSINESS ENTITY OR PERSON FOR WHOM YOU (“YOU”) ARE ACTING (“CUSTOMER”) AS THE
+LICENSEE OF THE PROPRIETARY SOFTWARE INTO WHICH THIS AGREEMENT HAS BEEN
+INCLUDED (THE “AGREEMENT”). YOU AGREE THAT YOU ARE THE CUSTOMER, OR YOU ARE AN
+EMPLOYEE OR AGENT OF CUSTOMER AND ARE ENTERING INTO THIS AGREEMENT FOR LICENSE
+OF THE SOFTWARE BY CUSTOMER FOR CUSTOMER’S BUSINESS PURPOSES AS DESCRIBED IN
+AND IN ACCORDANCE WITH THIS AGREEMENT. YOU HEREBY AGREE THAT YOU ENTER INTO
+THIS AGREEMENT ON BEHALF OF CUSTOMER AND THAT YOU HAVE THE AUTHORITY TO BIND
+CUSTOMER TO THIS AGREEMENT.
+
+STREAM.IO IS WILLING TO LICENSE THE SOFTWARE TO CUSTOMER ONLY ON THE FOLLOWING
+CONDITIONS: (1) YOU ARE A CURRENT CUSTOMER OF STREAM.IO; (2) YOU ARE NOT A
+COMPETITOR OF STREAM.IO; AND (3) THAT YOU ACCEPT ALL THE TERMS IN THIS
+AGREEMENT. BY DOWNLOADING, INSTALLING, CONFIGURING, ACCESSING OR OTHERWISE
+USING THE SOFTWARE, INCLUDING ANY UPDATES, UPGRADES, OR NEWER VERSIONS, YOU
+REPRESENT, WARRANT AND ACKNOWLEDGE THAT (A) CUSTOMER IS A CURRENT CUSTOMER OF
+STREAM.IO; (B) CUSTOMER IS NOT A COMPETITOR OF STREAM.IO; AND THAT (C) YOU HAVE
+READ THIS AGREEMENT, UNDERSTAND THIS AGREEMENT, AND THAT CUSTOMER AGREES TO BE
+BOUND BY ALL THE TERMS OF THIS AGREEMENT.
+
+IF YOU DO NOT AGREE TO ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT,
+STREAM.IO IS UNWILLING TO LICENSE THE SOFTWARE TO CUSTOMER, AND THEREFORE, DO
+NOT COMPLETE THE DOWNLOAD PROCESS, ACCESS OR OTHERWISE USE THE SOFTWARE, AND
+CUSTOMER SHOULD IMMEDIATELY RETURN THE SOFTWARE AND CEASE ANY USE OF THE
+SOFTWARE.
+
+1. SOFTWARE. The Stream.io software accompanying this Agreement, may include
+Source Code, Executable Object Code, associated media, printed materials and
+documentation (collectively, the “Software”). The Software also includes any
+updates or upgrades to or new versions of the original Software, if and when
+made available to you by Stream.io. “Source Code” means computer programming
+code in human readable form that is not suitable for machine execution without
+the intervening steps of interpretation or compilation. “Executable Object
+Code" means the computer programming code in any other form than Source Code
+that is not readily perceivable by humans and suitable for machine execution
+without the intervening steps of interpretation or compilation. “Site” means a
+Customer location controlled by Customer. “Authorized User” means any employee
+or contractor of Customer working at the Site, who has signed a written
+confidentiality agreement with Customer or is otherwise bound in writing by
+confidentiality and use obligations at least as restrictive as those imposed
+under this Agreement.
+
+2. LICENSE GRANT. Subject to the terms and conditions of this Agreement, in
+consideration for the representations, warranties, and covenants made by
+Customer in this Agreement, Stream.io grants to Customer, during the term of
+this Agreement, a personal, non-exclusive, non-transferable, non-sublicensable
+license to:
+
+a. install and use Software Source Code on password protected computers at a Site,
+restricted to Authorized Users;
+
+b. create derivative works, improvements (whether or not patentable), extensions
+and other modifications to the Software Source Code (“Modifications”) to build
+unique scalable newsfeeds, activity streams, and in-app messaging via Stream’s
+application program interface (“API”);
+
+c. compile the Software Source Code to create Executable Object Code versions of
+the Software Source Code and Modifications to build such newsfeeds, activity
+streams, and in-app messaging via the API;
+
+d. install, execute and use such Executable Object Code versions solely for
+Customer’s internal business use (including development of websites through
+which data generated by Stream services will be streamed (“Apps”));
+
+e. use and distribute such Executable Object Code as part of Customer’s Apps; and
+
+f. make electronic copies of the Software and Modifications as required for backup
+or archival purposes.
+
+3. RESTRICTIONS. Customer is responsible for all activities that occur in
+connection with the Software. Customer will not, and will not attempt to: (a)
+sublicense or transfer the Software or any Source Code related to the Software
+or any of Customer’s rights under this Agreement, except as otherwise provided
+in this Agreement, (b) use the Software Source Code for the benefit of a third
+party or to operate a service; (c) allow any third party to access or use the
+Software Source Code; (d) sublicense or distribute the Software Source Code or
+any Modifications in Source Code or other derivative works based on any part of
+the Software Source Code; (e) use the Software in any manner that competes with
+Stream.io or its business; or (e) otherwise use the Software in any manner that
+exceeds the scope of use permitted in this Agreement. Customer shall use the
+Software in compliance with any accompanying documentation any laws applicable
+to Customer.
+
+4. OPEN SOURCE. Customer and its Authorized Users shall not use any software or
+software components that are open source in conjunction with the Software
+Source Code or any Modifications in Source Code or in any way that could
+subject the Software to any open source licenses.
+
+5. CONTRACTORS. Under the rights granted to Customer under this Agreement,
+Customer may permit its employees, contractors, and agencies of Customer to
+become Authorized Users to exercise the rights to the Software granted to
+Customer in accordance with this Agreement solely on behalf of Customer to
+provide services to Customer; provided that Customer shall be liable for the
+acts and omissions of all Authorized Users to the extent any of such acts or
+omissions, if performed by Customer, would constitute a breach of, or otherwise
+give rise to liability to Customer under, this Agreement. Customer shall not
+and shall not permit any Authorized User to use the Software except as
+expressly permitted in this Agreement.
+
+6. COMPETITIVE PRODUCT DEVELOPMENT. Customer shall not use the Software in any way
+to engage in the development of products or services which could be reasonably
+construed to provide a complete or partial functional or commercial alternative
+to Stream.io’s products or services (a “Competitive Product”). Customer shall
+ensure that there is no direct or indirect use of, or sharing of, Software
+source code, or other information based upon or derived from the Software to
+develop such products or services. Without derogating from the generality of
+the foregoing, development of Competitive Products shall include having direct
+or indirect access to, supervising, consulting or assisting in the development
+of, or producing any specifications, documentation, object code or source code
+for, all or part of a Competitive Product.
+
+7. LIMITATION ON MODIFICATIONS. Notwithstanding any provision in this Agreement,
+Modifications may only be created and used by Customer as permitted by this
+Agreement and Modification Source Code may not be distributed to third parties.
+Customer will not assert against Stream.io, its affiliates, or their customers,
+direct or indirect, agents and contractors, in any way, any patent rights that
+Customer may obtain relating to any Modifications for Stream.io, its
+affiliates’, or their customers’, direct or indirect, agents’ and contractors’
+manufacture, use, import, offer for sale or sale of any Stream.io products or
+services.
+
+8. DELIVERY AND ACCEPTANCE. The Software will be delivered electronically pursuant
+to Stream.io standard download procedures. The Software is deemed accepted upon
+delivery.
+
+9. IMPLEMENTATION AND SUPPORT. Stream.io has no obligation under this Agreement to
+provide any support or consultation concerning the Software.
+
+10. TERM AND TERMINATION. The term of this Agreement begins when the Software is
+downloaded or accessed and shall continue until terminated. Either party may
+terminate this Agreement upon written notice. This Agreement shall
+automatically terminate if Customer is or becomes a competitor of Stream.io or
+makes or sells any Competitive Products. Upon termination of this Agreement for
+any reason, (a) all rights granted to Customer in this Agreement immediately
+cease to exist, (b) Customer must promptly discontinue all use of the Software
+and return to Stream.io or destroy all copies of the Software in Customer’s
+possession or control. Any continued use of the Software by Customer or attempt
+by Customer to exercise any rights under this Agreement after this Agreement
+has terminated shall be considered copyright infringement and subject Customer
+to applicable remedies for copyright infringement. Sections 2, 5, 6, 8 and 9
+shall survive expiration or termination of this Agreement for any reason.
+
+11. OWNERSHIP. As between the parties, the Software and all worldwide intellectual
+property rights and proprietary rights relating thereto or embodied therein,
+are the exclusive property of Stream.io and its suppliers. Stream.io and its
+suppliers reserve all rights in and to the Software not expressly granted to
+Customer in this Agreement, and no other licenses or rights are granted by
+implication, estoppel or otherwise.
+
+12. WARRANTY DISCLAIMER. USE OF THIS SOFTWARE IS ENTIRELY AT YOURS AND CUSTOMER’S
+OWN RISK. THE SOFTWARE IS PROVIDED “AS IS” WITHOUT ANY WARRANTY OF ANY KIND
+WHATSOEVER. STREAM.IO DOES NOT MAKE, AND HEREBY DISCLAIMS, ANY WARRANTY OF ANY
+KIND, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING WITHOUT
+LIMITATION, THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE, TITLE, NON-INFRINGEMENT OF THIRD-PARTY RIGHTS, RESULTS, EFFORTS,
+QUALITY OR QUIET ENJOYMENT. STREAM.IO DOES NOT WARRANT THAT THE SOFTWARE IS
+ERROR-FREE, WILL FUNCTION WITHOUT INTERRUPTION, WILL MEET ANY SPECIFIC NEED
+THAT CUSTOMER HAS, THAT ALL DEFECTS WILL BE CORRECTED OR THAT IT IS
+SUFFICIENTLY DOCUMENTED TO BE USABLE BY CUSTOMER. TO THE EXTENT THAT STREAM.IO
+MAY NOT DISCLAIM ANY WARRANTY AS A MATTER OF APPLICABLE LAW, THE SCOPE AND
+DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER SUCH LAW.
+CUSTOMER ACKNOWLEDGES THAT IT HAS RELIED ON NO WARRANTIES OTHER THAN THE
+EXPRESS WARRANTIES IN THIS AGREEMENT.
+
+13. LIMITATION OF LIABILITY. TO THE FULLEST EXTENT PERMISSIBLE BY LAW, STREAM.IO’S
+TOTAL LIABILITY FOR ALL DAMAGES ARISING OUT OF OR RELATED TO THE SOFTWARE OR
+THIS AGREEMENT, WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE) OR OTHERWISE,
+SHALL NOT EXCEED $100. IN NO EVENT WILL STREAM.IO BE LIABLE FOR ANY INDIRECT,
+CONSEQUENTIAL, EXEMPLARY, PUNITIVE, SPECIAL OR INCIDENTAL DAMAGES OF ANY KIND
+WHATSOEVER, INCLUDING ANY LOST DATA AND LOST PROFITS, ARISING FROM OR RELATING
+TO THE SOFTWARE EVEN IF STREAM.IO HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES. CUSTOMER ACKNOWLEDGES THAT THIS PROVISION REFLECTS THE AGREED UPON
+ALLOCATION OF RISK FOR THIS AGREEMENT AND THAT STREAM.IO WOULD NOT ENTER INTO
+THIS AGREEMENT WITHOUT THESE LIMITATIONS ON ITS LIABILITY.
+
+14. General. Customer may not assign or transfer this Agreement, by operation of
+law or otherwise, or any of its rights under this Agreement (including the
+license rights granted to Customer) to any third party without Stream.io’s
+prior written consent, which consent will not be unreasonably withheld or
+delayed. Stream.io may assign this Agreement, without consent, including, but
+limited to, affiliate or any successor to all or substantially all its business
+or assets to which this Agreement relates, whether by merger, sale of assets,
+sale of stock, reorganization or otherwise. Any attempted assignment or
+transfer in violation of the foregoing will be null and void. Stream.io shall
+not be liable hereunder by reason of any failure or delay in the performance of
+its obligations hereunder for any cause which is beyond the reasonable control.
+All notices, consents, and approvals under this Agreement must be delivered in
+writing by courier, by electronic mail, or by certified or registered mail,
+(postage prepaid and return receipt requested) to the other party at the
+address set forth in the customer agreement between Stream.io and Customer and
+will be effective upon receipt or when delivery is refused. This Agreement will
+be governed by and interpreted in accordance with the laws of the State of
+Colorado, without reference to its choice of laws rules. The United Nations
+Convention on Contracts for the International Sale of Goods does not apply to
+this Agreement. Any action or proceeding arising from or relating to this
+Agreement shall be brought in a federal or state court in Denver, Colorado, and
+each party irrevocably submits to the jurisdiction and venue of any such court
+in any such action or proceeding. All waivers must be in writing. Any waiver or
+failure to enforce any provision of this Agreement on one occasion will not be
+deemed a waiver of any other provision or of such provision on any other
+occasion. If any provision of this Agreement is unenforceable, such provision
+will be changed and interpreted to accomplish the objectives of such provision
+to the greatest extent possible under applicable law and the remaining
+provisions will continue in full force and effect. Customer shall not violate
+any applicable law, rule or regulation, including those regarding the export of
+technical data. The headings of Sections of this Agreement are for convenience
+and are not to be used in interpreting this Agreement. As used in this
+Agreement, the word “including” means “including but not limited to.” This
+Agreement (including all exhibits and attachments) constitutes the entire
+agreement between the parties regarding the subject hereof and supersedes all
+prior or contemporaneous agreements, understandings and communication, whether
+written or oral. This Agreement may be amended only by a written document
+signed by both parties. The terms of any purchase order or similar document
+submitted by Customer to Stream.io will have no effect.
diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md
new file mode 100644
index 00000000..bf648e45
--- /dev/null
+++ b/packages/stream_chat_localizations/README.md
@@ -0,0 +1,121 @@
+# Official Localizations for [Stream Chat Flutter](https://getstream.io/chat/sdk/flutter/) library.
+
+> The Official localizations for Stream Chat Flutter, a service for
+> building chat applications.
+
+[](https://pub.dartlang.org/packages/stream_chat_localizations)
+
+
+
+
+**Quick Links**
+
+- [Register](https://getstream.io/chat/trial/) to get an API key for Stream Chat
+- [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/)
+- [Chat UI Kit](https://getstream.io/chat/ui-kit/)
+- [UI Docs](https://getstream.io/chat/docs/sdk/flutter/stream_chat_flutter/introduction/)
+
+### Changelog
+
+Check out the [changelog on pub.dev](https://pub.dev/packages/stream_chat_localizations/changelog) to see the latest changes in the package.
+
+
+
+## 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**).
+
+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.
+
+## Supported languages
+
+At the moment we support the following languages:
+- [English](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart)
+- [Hindi](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart)
+- [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)
+
+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
+
+Add this to your package's pubspec.yaml file, use the latest version [](https://pub.dartlang.org/packages/stream_chat_localizations)
+```yaml
+dependencies:
+ stream_chat_localizations: ^latest_version
+```
+
+Then run `flutter packages get`
+
+### Usage
+
+```dart
+import 'package:flutter/material.dart';
+import 'package:stream_chat_localizations/stream_chat_localizations.dart';
+
+void main() {
+ WidgetsFlutterBinding.ensureInitialized();
+ runApp(MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ // Add all the supported locales
+ supportedLocales: const [
+ Locale('en'),
+ Locale('hi'),
+ Locale('fr'),
+ Locale('it'),
+ ],
+ // Add GlobalStreamChatLocalizations.delegates
+ localizationsDelegates: GlobalStreamChatLocalizations.delegates,
+ builder: (context, widget) => StreamChat(
+ client: client,
+ child: widget,
+ ),
+ home: StreamChannel(
+ channel: channel,
+ child: const ChannelPage(),
+ ),
+ );
+ }
+}
+```
+
+### Adding a new language
+
+To add a new language, you need to create a new class extending `GlobalStreamChatLocalizations` and create a delegate for it adding it to the `delegates` array.
+
+Check out [this example](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/example/lib/add_new_lang.dart) to see how to add a new language.
+
+### Override existing languages
+
+To override an existing language, you need to create a new class extending that particular language class and create a delegate for it adding it to the `delegates` array.
+
+Check out [this example](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/example/lib/override_lang.dart) to see how to override an existing language.
+
+### ⚠️ Note on **iOS**
+
+For translation to work on **iOS** you need to add supported locales to
+`ios/Runner/Info.plist` as described [here](https://flutter.dev/docs/development/accessibility-and-localization/internationalization#localizing-for-ios-updating-the-ios-app-bundle).
+
+Example:
+
+```xml
+CFBundleLocalizations
+
+ en
+ nb
+ fr
+ it
+
+```
+
+## Contributing
+
+We welcome code changes that improve this library or fix a problem. Please make sure to follow all best practices and add tests if applicable before submitting a Pull Request on Github.
+We are pleased to merge your code into the official repository.
+Make sure to sign our [Contributor License Agreement (CLA)](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) first.
+See our license file for more details.
diff --git a/packages/stream_chat_localizations/example/.gitignore b/packages/stream_chat_localizations/example/.gitignore
new file mode 100644
index 00000000..9d532b18
--- /dev/null
+++ b/packages/stream_chat_localizations/example/.gitignore
@@ -0,0 +1,41 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
diff --git a/packages/stream_chat_localizations/example/.metadata b/packages/stream_chat_localizations/example/.metadata
new file mode 100644
index 00000000..182cccaf
--- /dev/null
+++ b/packages/stream_chat_localizations/example/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: 78910062997c3a836feee883712c241a5fd22983
+ channel: stable
+
+project_type: app
diff --git a/packages/stream_chat_localizations/example/README.md b/packages/stream_chat_localizations/example/README.md
new file mode 100644
index 00000000..003f56ee
--- /dev/null
+++ b/packages/stream_chat_localizations/example/README.md
@@ -0,0 +1,27 @@
+# Stream Chat Persistence Example
+Please see `lib/` for example code.
+
+## Setting a language
+The application language can be changed through system preferences or programmatically.
+
+### System Preferences
+The application locale can be changed by changing the language for your device or emulator within the device's system preferences.
+
+[iOS change language](https://support.apple.com/en-us/HT204031)
+
+[Android change language](https://support.google.com/websearch/answer/3333234?co=GENIE.Platform%3DAndroid&hl=en)
+
+Note that the language needs to be supported in your application to work.
+
+### Programmatically
+You can also set the locale programmatically in your Flutter application without changing the device's language.
+
+```dart
+return MaterialApp(
+ ...
+ locale: const Locale('fr'),
+ ...
+);
+```
+
+There are many ways that this can be set for additional control. For information and examples, see this [Stack Overflow post](https://stackoverflow.com/questions/49441212/flutter-multi-lingual-application-how-to-override-the-locale).
\ No newline at end of file
diff --git a/packages/stream_chat_localizations/example/android/.gitignore b/packages/stream_chat_localizations/example/android/.gitignore
new file mode 100644
index 00000000..0a741cb4
--- /dev/null
+++ b/packages/stream_chat_localizations/example/android/.gitignore
@@ -0,0 +1,11 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
diff --git a/packages/stream_chat_localizations/example/android/app/build.gradle b/packages/stream_chat_localizations/example/android/app/build.gradle
new file mode 100644
index 00000000..fbd6268e
--- /dev/null
+++ b/packages/stream_chat_localizations/example/android/app/build.gradle
@@ -0,0 +1,64 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader('UTF-8') { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+ throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+ flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+ flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+ compileSdkVersion 30
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ lintOptions {
+ disable 'InvalidPackage'
+ checkReleaseBuilds false
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId "com.example.example"
+ minSdkVersion 21
+ targetSdkVersion 30
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source '../..'
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/packages/stream_chat_localizations/example/android/app/src/debug/AndroidManifest.xml b/packages/stream_chat_localizations/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 00000000..c208884f
--- /dev/null
+++ b/packages/stream_chat_localizations/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/packages/stream_chat_localizations/example/android/app/src/main/AndroidManifest.xml b/packages/stream_chat_localizations/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..55ca830c
--- /dev/null
+++ b/packages/stream_chat_localizations/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/stream_chat_localizations/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/stream_chat_localizations/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt
new file mode 100644
index 00000000..e793a000
--- /dev/null
+++ b/packages/stream_chat_localizations/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.example.example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/packages/stream_chat_localizations/example/android/app/src/main/res/drawable/launch_background.xml b/packages/stream_chat_localizations/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 00000000..304732f8
--- /dev/null
+++ b/packages/stream_chat_localizations/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..db77bb4b
Binary files /dev/null and b/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..17987b79
Binary files /dev/null and b/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..09d43914
Binary files /dev/null and b/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..d5f1c8d3
Binary files /dev/null and b/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..4d6372ee
Binary files /dev/null and b/packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/packages/stream_chat_localizations/example/android/app/src/main/res/values/styles.xml b/packages/stream_chat_localizations/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..1f83a33f
--- /dev/null
+++ b/packages/stream_chat_localizations/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/packages/stream_chat_localizations/example/android/app/src/profile/AndroidManifest.xml b/packages/stream_chat_localizations/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 00000000..c208884f
--- /dev/null
+++ b/packages/stream_chat_localizations/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/packages/stream_chat_localizations/example/android/build.gradle b/packages/stream_chat_localizations/example/android/build.gradle
new file mode 100644
index 00000000..3e0873de
--- /dev/null
+++ b/packages/stream_chat_localizations/example/android/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+ ext.kotlin_version = '1.5.20'
+ repositories {
+ google()
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:4.2.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/packages/stream_chat_localizations/example/android/gradle.properties b/packages/stream_chat_localizations/example/android/gradle.properties
new file mode 100644
index 00000000..a6738207
--- /dev/null
+++ b/packages/stream_chat_localizations/example/android/gradle.properties
@@ -0,0 +1,4 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
+android.enableR8=true
diff --git a/packages/stream_chat_localizations/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/stream_chat_localizations/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..3df6b338
--- /dev/null
+++ b/packages/stream_chat_localizations/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
diff --git a/packages/stream_chat_localizations/example/android/settings.gradle b/packages/stream_chat_localizations/example/android/settings.gradle
new file mode 100644
index 00000000..44e62bcf
--- /dev/null
+++ b/packages/stream_chat_localizations/example/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/packages/stream_chat_localizations/example/ios/.gitignore b/packages/stream_chat_localizations/example/ios/.gitignore
new file mode 100644
index 00000000..151026b9
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/.gitignore
@@ -0,0 +1,33 @@
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/packages/stream_chat_localizations/example/ios/Flutter/AppFrameworkInfo.plist b/packages/stream_chat_localizations/example/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 00000000..9367d483
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 8.0
+
+
diff --git a/packages/stream_chat_localizations/example/ios/Flutter/Debug.xcconfig b/packages/stream_chat_localizations/example/ios/Flutter/Debug.xcconfig
new file mode 100644
index 00000000..ec97fc6f
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Flutter/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "Generated.xcconfig"
diff --git a/packages/stream_chat_localizations/example/ios/Flutter/Release.xcconfig b/packages/stream_chat_localizations/example/ios/Flutter/Release.xcconfig
new file mode 100644
index 00000000..c4855bfe
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Flutter/Release.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "Generated.xcconfig"
diff --git a/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.pbxproj b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..3ea4f826
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,539 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 710C8D61445A55538E32FA76 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 90B326A922854E6FACB68418 /* Pods_Runner.framework */; };
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 2C064A83D93670719ABCFB4E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 406F253F79064E73E6570F91 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ 425E1032537699D21AA7599E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 90B326A922854E6FACB68418 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 710C8D61445A55538E32FA76 /* Pods_Runner.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 9740EEB11CF90186004384FC /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
+ );
+ name = Flutter;
+ sourceTree = "";
+ };
+ 97C146E51CF9000F007C117D = {
+ isa = PBXGroup;
+ children = (
+ 9740EEB11CF90186004384FC /* Flutter */,
+ 97C146F01CF9000F007C117D /* Runner */,
+ 97C146EF1CF9000F007C117D /* Products */,
+ D27696257385EFA4B6A3AECC /* Pods */,
+ A92D4F040E5D68A2DFDFF35C /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 97C146F01CF9000F007C117D /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146FA1CF9000F007C117D /* Main.storyboard */,
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */,
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+ 97C147021CF9000F007C117D /* Info.plist */,
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+ A92D4F040E5D68A2DFDFF35C /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 90B326A922854E6FACB68418 /* Pods_Runner.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ D27696257385EFA4B6A3AECC /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 2C064A83D93670719ABCFB4E /* Pods-Runner.debug.xcconfig */,
+ 406F253F79064E73E6570F91 /* Pods-Runner.release.xcconfig */,
+ 425E1032537699D21AA7599E /* Pods-Runner.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 97C146ED1CF9000F007C117D /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ BC7B06B6092566B153F8E77E /* [CP] Check Pods Manifest.lock */,
+ 9740EEB61CF901F6004384FC /* Run Script */,
+ 97C146EA1CF9000F007C117D /* Sources */,
+ 97C146EB1CF9000F007C117D /* Frameworks */,
+ 97C146EC1CF9000F007C117D /* Resources */,
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ 91A9FD3C359F260AE72CC421 /* [CP] Embed Pods Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 97C146E61CF9000F007C117D /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 1020;
+ ORGANIZATIONNAME = "";
+ TargetAttributes = {
+ 97C146ED1CF9000F007C117D = {
+ CreatedOnToolsVersion = 7.3.1;
+ LastSwiftMigration = 1100;
+ };
+ };
+ };
+ buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 97C146E51CF9000F007C117D;
+ productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 97C146ED1CF9000F007C117D /* Runner */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 97C146EC1CF9000F007C117D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Thin Binary";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+ };
+ 91A9FD3C359F260AE72CC421 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Run Script";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+ };
+ BC7B06B6092566B153F8E77E /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 97C146EA1CF9000F007C117D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C146FB1CF9000F007C117D /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C147001CF9000F007C117D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 249021D3217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Profile;
+ };
+ 249021D4217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Profile;
+ };
+ 97C147031CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 97C147041CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 97C147061CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Debug;
+ };
+ 97C147071CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147031CF9000F007C117D /* Debug */,
+ 97C147041CF9000F007C117D /* Release */,
+ 249021D3217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147061CF9000F007C117D /* Debug */,
+ 97C147071CF9000F007C117D /* Release */,
+ 249021D4217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..919434a6
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 00000000..f9b0d7c5
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 00000000..a28140cf
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/stream_chat_localizations/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/stream_chat_localizations/example/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..21a3cc14
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/packages/stream_chat_localizations/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/stream_chat_localizations/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/packages/stream_chat_localizations/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/stream_chat_localizations/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 00000000..f9b0d7c5
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/packages/stream_chat_localizations/example/ios/Runner/AppDelegate.swift b/packages/stream_chat_localizations/example/ios/Runner/AppDelegate.swift
new file mode 100644
index 00000000..70693e4a
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+ override func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+ ) -> Bool {
+ GeneratedPluginRegistrant.register(with: self)
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+ }
+}
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..d36b1fab
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+ "images" : [
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-83.5x83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "Icon-App-1024x1024@1x.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 00000000..dc9ada47
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 00000000..28c6bf03
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 00000000..2ccbfd96
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 00000000..f091b6b0
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 00000000..4cde1211
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 00000000..d0ef06e7
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 00000000..dcdc2306
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 00000000..2ccbfd96
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 00000000..c8f9ed8f
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 00000000..a6d6b860
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 00000000..a6d6b860
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 00000000..75b2d164
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 00000000..c4df70d3
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 00000000..6a84f41e
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 00000000..d0e1f585
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 00000000..0bedcf2f
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 00000000..9da19eac
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 00000000..9da19eac
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 00000000..9da19eac
Binary files /dev/null and b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 00000000..89c2725b
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/stream_chat_localizations/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 00000000..f2e259c7
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Base.lproj/Main.storyboard b/packages/stream_chat_localizations/example/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 00000000..f3c28516
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Info.plist b/packages/stream_chat_localizations/example/ios/Runner/Info.plist
new file mode 100644
index 00000000..c9bfb029
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner/Info.plist
@@ -0,0 +1,52 @@
+
+
+
+
+ CFBundleLocalizations
+
+ en
+ it
+ fr
+ hi
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ example
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+
+
diff --git a/packages/stream_chat_localizations/example/ios/Runner/Runner-Bridging-Header.h b/packages/stream_chat_localizations/example/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 00000000..308a2a56
--- /dev/null
+++ b/packages/stream_chat_localizations/example/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/packages/stream_chat_localizations/example/lib/add_new_lang.dart b/packages/stream_chat_localizations/example/lib/add_new_lang.dart
new file mode 100644
index 00000000..1412f4e0
--- /dev/null
+++ b/packages/stream_chat_localizations/example/lib/add_new_lang.dart
@@ -0,0 +1,500 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:stream_chat_flutter/stream_chat_flutter.dart';
+import 'package:stream_chat_localizations/stream_chat_localizations.dart';
+
+class _NnStreamChatLocalizationsDelegate
+ extends LocalizationsDelegate {
+ const _NnStreamChatLocalizationsDelegate();
+
+ @override
+ bool isSupported(Locale locale) => locale.languageCode == 'nn';
+
+ @override
+ Future load(Locale locale) =>
+ SynchronousFuture(const NnStreamChatLocalizations());
+
+ @override
+ bool shouldReload(_NnStreamChatLocalizationsDelegate old) => false;
+}
+
+/// A custom set of localizations for the 'nn' locale. In this example, only
+/// the value for launchUrlError was modified to use a custom message as
+/// an example. Everything else uses the American English (en_US) messages
+/// and formatting.
+class NnStreamChatLocalizations extends GlobalStreamChatLocalizations {
+ /// Create an instance of the translation bundle for English.
+ const NnStreamChatLocalizations({String localeName = 'nn'})
+ : super(localeName: localeName);
+
+ /// A [LocalizationsDelegate] for [NnStreamChatLocalizations].
+ static const delegate = _NnStreamChatLocalizationsDelegate();
+
+ @override
+ String get launchUrlError => 'Custom error';
+
+ @override
+ String get loadingUsersError => 'Error loading users';
+
+ @override
+ String get noUsersLabel => 'There are no users currently';
+
+ @override
+ String get retryLabel => 'Retry';
+
+ @override
+ String get userLastOnlineText => 'Last online';
+
+ @override
+ String get userOnlineText => 'Online';
+
+ @override
+ String userTypingText(Iterable users) {
+ if (users.isEmpty) return '';
+ final first = users.first;
+ if (users.length == 1) {
+ return '${first.name} is typing';
+ }
+ return '${first.name} and ${users.length - 1} more are typing';
+ }
+
+ @override
+ String get threadReplyLabel => 'Thread Reply';
+
+ @override
+ String get onlyVisibleToYouText => 'Only visible to you';
+
+ @override
+ String threadReplyCountText(int count) => '$count Thread Replies';
+
+ @override
+ String attachmentsUploadProgressText({
+ required int remaining,
+ required int total,
+ }) =>
+ 'Uploading $remaining/$total ...';
+
+ @override
+ String pinnedByUserText({
+ required User pinnedBy,
+ required User currentUser,
+ }) {
+ final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
+ if (pinnedByCurrentUser) return 'Pinned by You';
+ return 'Pinned by ${pinnedBy.name}';
+ }
+
+ @override
+ String get emptyMessagesText => 'There are no messages currently';
+
+ @override
+ String get genericErrorText => 'Something went wrong';
+
+ @override
+ String get loadingMessagesError => 'Error loading messages';
+
+ @override
+ String resultCountText(int count) => '$count results';
+
+ @override
+ String get messageDeletedText => 'This message is deleted.';
+
+ @override
+ String get messageDeletedLabel => 'Message deleted';
+
+ @override
+ String get messageReactionsLabel => 'Message Reactions';
+
+ @override
+ String get emptyChatMessagesText => 'No chats here yet...';
+
+ @override
+ String threadSeparatorText(int replyCount) {
+ if (replyCount == 1) return '1 Reply';
+ return '$replyCount Replies';
+ }
+
+ @override
+ String get connectedLabel => 'Connected';
+
+ @override
+ String get disconnectedLabel => 'Disconnected';
+
+ @override
+ String get reconnectingLabel => 'Reconnecting...';
+
+ @override
+ String get alsoSendAsDirectMessageLabel => 'Also send as direct message';
+
+ @override
+ String get addACommentOrSendLabel => 'Add a comment or send';
+
+ @override
+ String get searchGifLabel => 'Search GIFs';
+
+ @override
+ String get writeAMessageLabel => 'Write a message';
+
+ @override
+ String get instantCommandsLabel => 'Instant Commands';
+
+ @override
+ String fileTooLargeAfterCompressionError(double limitInMB) =>
+ 'The file is too large to upload. '
+ 'The file size limit is $limitInMB MB. '
+ 'We tried compressing it, but it was not enough.';
+
+ @override
+ String fileTooLargeError(double limitInMB) =>
+ 'The file is too large to upload. The file size limit is $limitInMB MB.';
+
+ @override
+ String emojiMatchingQueryText(String query) => 'Emoji matching "$query"';
+
+ @override
+ String get addAFileLabel => 'Add a file';
+
+ @override
+ String get photoFromCameraLabel => 'Photo from camera';
+
+ @override
+ String get uploadAFileLabel => 'Upload a file';
+
+ @override
+ String get uploadAPhotoLabel => 'Upload a photo';
+
+ @override
+ String get uploadAVideoLabel => 'Upload a video';
+
+ @override
+ String get videoFromCameraLabel => 'Video from camera';
+
+ @override
+ String get okLabel => 'OK';
+
+ @override
+ String get somethingWentWrongError => 'Something went wrong';
+
+ @override
+ String get addMoreFilesLabel => 'Add more files';
+
+ @override
+ String get enablePhotoAndVideoAccessMessage =>
+ 'Please enable access to your photos'
+ '\nand videos so you can share them with friends.';
+
+ @override
+ String get allowGalleryAccessMessage => 'Allow access to your gallery';
+
+ @override
+ String get flagMessageLabel => 'Flag Message';
+
+ @override
+ String get flagMessageQuestion =>
+ 'Do you want to send a copy of this message to a'
+ '\nmoderator for further investigation?';
+
+ @override
+ String get flagLabel => 'FLAG';
+
+ @override
+ String get cancelLabel => 'CANCEL';
+
+ @override
+ String get flagMessageSuccessfulLabel => 'Message flagged';
+
+ @override
+ String get flagMessageSuccessfulText =>
+ 'The message has been reported to a moderator.';
+
+ @override
+ String get deleteLabel => 'DELETE';
+
+ @override
+ String get deleteMessageLabel => 'Delete Message';
+
+ @override
+ String get deleteMessageQuestion =>
+ 'Are you sure you want to permanently delete this\nmessage?';
+
+ @override
+ String get operationCouldNotBeCompletedText =>
+ 'The operation couldn\'t be completed.';
+
+ @override
+ String get replyLabel => 'Reply';
+
+ @override
+ String togglePinUnpinText({required bool pinned}) {
+ if (pinned) return 'Unpin from Conversation';
+ return 'Pin to Conversation';
+ }
+
+ @override
+ String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
+ if (isDeleteFailed) return 'Retry Deleting Message';
+ return 'Delete Message';
+ }
+
+ @override
+ String get copyMessageLabel => 'Copy Message';
+
+ @override
+ String get editMessageLabel => 'Edit Message';
+
+ @override
+ String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
+ if (isUpdateFailed) return 'Resend Edited Message';
+ return 'Resend';
+ }
+
+ @override
+ String get photosLabel => 'Photos';
+
+ String _getDay(DateTime dateTime) {
+ final now = DateTime.now();
+ final today = DateTime(now.year, now.month, now.day);
+ final yesterday = DateTime(now.year, now.month, now.day - 1);
+
+ final date = DateTime(dateTime.year, dateTime.month, dateTime.day);
+
+ if (date == today) {
+ return 'today';
+ } else if (date == yesterday) {
+ return 'yesterday';
+ } else {
+ return 'on ${Jiffy(date).MMMd}';
+ }
+ }
+
+ @override
+ String sentAtText({required DateTime date, required DateTime time}) =>
+ 'Sent ${_getDay(date)} at ${Jiffy(time.toLocal()).format('HH:mm')}';
+
+ @override
+ String get todayLabel => 'Today';
+
+ @override
+ String get yesterdayLabel => 'Yesterday';
+
+ @override
+ String get channelIsMutedText => 'Channel is muted';
+
+ @override
+ String get noTitleText => 'No title';
+
+ @override
+ String get letsStartChattingLabel => 'Let’s start chatting!';
+
+ @override
+ String get sendingFirstMessageLabel =>
+ 'How about sending your first message to a friend?';
+
+ @override
+ String get startAChatLabel => 'Start a chat';
+
+ @override
+ String get loadingChannelsError => 'Error loading channels';
+
+ @override
+ String get deleteConversationLabel => 'Delete Conversation';
+
+ @override
+ String get deleteConversationQuestion =>
+ 'Are you sure you want to delete this conversation?';
+
+ @override
+ String get streamChatLabel => 'Stream Chat';
+
+ @override
+ String get searchingForNetworkText => 'Searching for Network';
+
+ @override
+ String get offlineLabel => 'Offline...';
+
+ @override
+ String get tryAgainLabel => 'Try Again';
+
+ @override
+ String membersCountText(int count) {
+ if (count == 1) return '1 Member';
+ return '$count Members';
+ }
+
+ @override
+ String watchersCountText(int count) {
+ if (count == 1) return '1 Online';
+ return '$count Online';
+ }
+
+ @override
+ String get viewInfoLabel => 'View Info';
+
+ @override
+ String get leaveGroupLabel => 'Leave Group';
+
+ @override
+ String get leaveLabel => 'LEAVE';
+
+ @override
+ String get leaveConversationLabel => 'Leave conversation';
+
+ @override
+ String get leaveConversationQuestion =>
+ 'Are you sure you want to leave this conversation?';
+
+ @override
+ String get showInChatLabel => 'Show in Chat';
+
+ @override
+ String get saveImageLabel => 'Save Image';
+
+ @override
+ String get saveVideoLabel => 'Save Video';
+
+ @override
+ String get uploadErrorLabel => 'UPLOAD ERROR';
+
+ @override
+ String get giphyLabel => 'Giphy';
+
+ @override
+ String get shuffleLabel => 'Shuffle';
+
+ @override
+ String get sendLabel => 'Send';
+
+ @override
+ String get withText => 'with';
+
+ @override
+ String get inText => 'in';
+
+ @override
+ String get youText => 'You';
+
+ @override
+ String get ofText => 'of';
+
+ @override
+ String get fileText => 'File';
+
+ @override
+ String get replyToMessageLabel => 'Reply to Message';
+}
+
+void main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ /// Create a new instance of [StreamChatClient] passing the apikey obtained
+ /// from your project dashboard.
+ final client = StreamChatClient(
+ 's2dxdhpxd94g',
+ logLevel: Level.INFO,
+ );
+
+ /// Set the current user and connect the websocket. In a production
+ /// scenario, this should be done using a backend to generate a user token
+ /// using our server SDK.
+ ///
+ /// Please see the following for more information:
+ /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
+ await client.connectUser(
+ User(id: 'super-band-9'),
+ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.'
+ '0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
+ );
+
+ final channel = client.channel('messaging', id: 'godevs');
+
+ await channel.watch();
+
+ runApp(
+ MyApp(
+ client: client,
+ channel: channel,
+ ),
+ );
+}
+
+/// Example application using Stream Chat Flutter widgets.
+///
+/// Stream Chat Flutter is a set of Flutter widgets which provide full chat
+/// functionalities for building Flutter applications using Stream. If you'd
+/// prefer using minimal wrapper widgets for your app, please see our other
+/// package, `stream_chat_flutter_core`.
+class MyApp extends StatelessWidget {
+ /// Example using Stream's Flutter package.
+ ///
+ /// If you'd prefer using minimal wrapper widgets for your app, please see
+ /// our other package, `stream_chat_flutter_core`.
+ const MyApp({
+ Key? key,
+ required this.client,
+ required this.channel,
+ }) : super(key: key);
+
+ /// Instance of Stream Client.
+ ///
+ /// Stream's [StreamChatClient] can be used to connect to our servers and
+ /// set the default user for the application. Performing these actions
+ /// trigger a websocket connection allowing for real-time updates.
+ final StreamChatClient client;
+
+ /// Instance of the Channel
+ final Channel channel;
+
+ @override
+ Widget build(BuildContext context) => MaterialApp(
+ theme: ThemeData.light(),
+ darkTheme: ThemeData.dark(),
+ // Add all the supported locales
+ supportedLocales: const [
+ Locale('en'),
+ Locale('hi'),
+ Locale('fr'),
+ Locale('it'),
+ // Add support for additional 'nn' locale
+ Locale('nn'),
+ ],
+ // Add overridden "NnStreamChatLocalizations.delegate" along with
+ // "GlobalStreamChatLocalizations.delegates"
+ localizationsDelegates: const [
+ NnStreamChatLocalizations.delegate,
+ ...GlobalStreamChatLocalizations.delegates,
+ ],
+ builder: (context, widget) => StreamChat(
+ client: client,
+ child: widget,
+ ),
+ home: StreamChannel(
+ channel: channel,
+ child: const ChannelPage(),
+ ),
+ );
+}
+
+/// A list of messages sent in the current channel.
+///
+/// This is implemented using [MessageListView], a widget that provides query
+/// functionalities fetching the messages from the api and showing them in a
+/// listView.
+class ChannelPage extends StatelessWidget {
+ /// Creates the page that shows the list of messages
+ const ChannelPage({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) => Scaffold(
+ appBar: const ChannelHeader(),
+ body: Column(
+ children: const [
+ Expanded(
+ child: MessageListView(),
+ ),
+ MessageInput(),
+ ],
+ ),
+ );
+}
diff --git a/packages/stream_chat_localizations/example/lib/main.dart b/packages/stream_chat_localizations/example/lib/main.dart
new file mode 100644
index 00000000..818086df
--- /dev/null
+++ b/packages/stream_chat_localizations/example/lib/main.dart
@@ -0,0 +1,115 @@
+import 'package:flutter/material.dart';
+import 'package:stream_chat_flutter/stream_chat_flutter.dart';
+import 'package:stream_chat_localizations/stream_chat_localizations.dart';
+
+void main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ /// Create a new instance of [StreamChatClient] passing the apikey obtained
+ /// from your project dashboard.
+ final client = StreamChatClient(
+ 's2dxdhpxd94g',
+ logLevel: Level.INFO,
+ );
+
+ /// Set the current user and connect the websocket. In a production
+ /// scenario, this should be done using a backend to generate a user token
+ /// using our server SDK.
+ ///
+ /// Please see the following for more information:
+ /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
+ await client.connectUser(
+ User(id: 'super-band-9'),
+ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.'
+ '0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
+ );
+
+ final channel = client.channel('messaging', id: 'godevs');
+
+ await channel.watch();
+
+ runApp(
+ MyApp(
+ client: client,
+ channel: channel,
+ ),
+ );
+}
+
+/// Example application using Stream Chat Flutter widgets.
+///
+/// Stream Chat Flutter is a set of Flutter widgets which provide full chat
+/// functionalities for building Flutter applications using Stream. If you'd
+/// prefer using minimal wrapper widgets for your app, please see our other
+/// package, `stream_chat_flutter_core`.
+class MyApp extends StatelessWidget {
+ /// Example using Stream's Flutter package.
+ ///
+ /// If you'd prefer using minimal wrapper widgets for your app, please see
+ /// our other package, `stream_chat_flutter_core`.
+ const MyApp({
+ Key? key,
+ required this.client,
+ required this.channel,
+ }) : super(key: key);
+
+ /// Instance of Stream Client.
+ ///
+ /// Stream's [StreamChatClient] can be used to connect to our servers and
+ /// set the default user for the application. Performing these actions
+ /// trigger a websocket connection allowing for real-time updates.
+ final StreamChatClient client;
+
+ /// Instance of the Channel
+ final Channel channel;
+
+ @override
+ Widget build(BuildContext context) => MaterialApp(
+ theme: ThemeData.light(),
+ darkTheme: ThemeData.dark(),
+ // Add all the supported locales
+ supportedLocales: const [
+ Locale('en'),
+ Locale('hi'),
+ Locale('fr'),
+ Locale('it'),
+ ],
+ // Add GlobalStreamChatLocalizations.delegates
+ localizationsDelegates: GlobalStreamChatLocalizations.delegates,
+ // Programatically set the locale (this is a global change)
+ locale: const Locale('fr'),
+ builder: (context, widget) => StreamChat(
+ client: client,
+ child: widget,
+ ),
+ home: StreamChannel(
+ channel: channel,
+ child: const ChannelPage(),
+ ),
+ );
+}
+
+/// A list of messages sent in the current channel.
+///
+/// This is implemented using [MessageListView], a widget that provides query
+/// functionalities fetching the messages from the api and showing them in a
+/// listView.
+class ChannelPage extends StatelessWidget {
+ /// Creates the page that shows the list of messages
+ const ChannelPage({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) => Scaffold(
+ appBar: const ChannelHeader(),
+ body: Column(
+ children: const [
+ Expanded(
+ child: MessageListView(),
+ ),
+ MessageInput(),
+ ],
+ ),
+ );
+}
diff --git a/packages/stream_chat_localizations/example/lib/override_lang.dart b/packages/stream_chat_localizations/example/lib/override_lang.dart
new file mode 100644
index 00000000..0e45019a
--- /dev/null
+++ b/packages/stream_chat_localizations/example/lib/override_lang.dart
@@ -0,0 +1,142 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:stream_chat_flutter/stream_chat_flutter.dart';
+import 'package:stream_chat_localizations/stream_chat_localizations.dart';
+
+class _CustomStreamChatLocalizationsDelegate
+ extends LocalizationsDelegate {
+ const _CustomStreamChatLocalizationsDelegate();
+
+ @override
+ bool isSupported(Locale locale) => locale.languageCode == 'en';
+
+ @override
+ Future load(Locale locale) =>
+ SynchronousFuture(CustomStreamChatLocalizationsEn());
+
+ @override
+ bool shouldReload(_CustomStreamChatLocalizationsDelegate old) => false;
+}
+
+/// Customized translations for English ('en')
+class CustomStreamChatLocalizationsEn extends StreamChatLocalizationsEn {
+ /// A [LocalizationsDelegate] for [StreamChatLocalizationsEn].
+ static const delegate = _CustomStreamChatLocalizationsDelegate();
+
+ @override
+ String get launchUrlError => 'My custom error';
+}
+
+void main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ /// Create a new instance of [StreamChatClient] passing the apikey obtained
+ /// from your project dashboard.
+ final client = StreamChatClient(
+ 's2dxdhpxd94g',
+ logLevel: Level.INFO,
+ );
+
+ /// Set the current user and connect the websocket. In a production
+ /// scenario, this should be done using a backend to generate a user token
+ /// using our server SDK.
+ ///
+ /// Please see the following for more information:
+ /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
+ await client.connectUser(
+ User(id: 'super-band-9'),
+ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.'
+ '0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
+ );
+
+ final channel = client.channel('messaging', id: 'godevs');
+
+ await channel.watch();
+
+ runApp(
+ MyApp(
+ client: client,
+ channel: channel,
+ ),
+ );
+}
+
+/// Example application using Stream Chat Flutter widgets.
+///
+/// Stream Chat Flutter is a set of Flutter widgets which provide full chat
+/// functionalities for building Flutter applications using Stream. If you'd
+/// prefer using minimal wrapper widgets for your app, please see our other
+/// package, `stream_chat_flutter_core`.
+class MyApp extends StatelessWidget {
+ /// Example using Stream's Flutter package.
+ ///
+ /// If you'd prefer using minimal wrapper widgets for your app, please see
+ /// our other package, `stream_chat_flutter_core`.
+ const MyApp({
+ Key? key,
+ required this.client,
+ required this.channel,
+ }) : super(key: key);
+
+ /// Instance of Stream Client.
+ ///
+ /// Stream's [StreamChatClient] can be used to connect to our servers and
+ /// set the default user for the application. Performing these actions
+ /// trigger a websocket connection allowing for real-time updates.
+ final StreamChatClient client;
+
+ /// Instance of the Channel
+ final Channel channel;
+
+ @override
+ Widget build(BuildContext context) => MaterialApp(
+ theme: ThemeData.light(),
+ darkTheme: ThemeData.dark(),
+ // Add all the supported locales
+ supportedLocales: const [
+ Locale('en'),
+ Locale('hi'),
+ Locale('fr'),
+ Locale('it'),
+ ],
+ // Add overridden "CustomStreamChatLocalizationsEn.delegate" along with
+ // "GlobalStreamChatLocalizations.delegates"
+ localizationsDelegates: const [
+ CustomStreamChatLocalizationsEn.delegate,
+ ...GlobalStreamChatLocalizations.delegates,
+ ],
+ builder: (context, widget) => StreamChat(
+ client: client,
+ child: widget,
+ ),
+ home: StreamChannel(
+ channel: channel,
+ child: const ChannelPage(),
+ ),
+ );
+}
+
+/// A list of messages sent in the current channel.
+///
+/// This is implemented using [MessageListView], a widget that provides query
+/// functionalities fetching the messages from the api and showing them in a
+/// listView.
+class ChannelPage extends StatelessWidget {
+ /// Creates the page that shows the list of messages
+ const ChannelPage({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) => Scaffold(
+ appBar: const ChannelHeader(),
+ body: Column(
+ children: const [
+ Expanded(
+ child: MessageListView(),
+ ),
+ MessageInput(),
+ ],
+ ),
+ );
+}
diff --git a/packages/stream_chat_localizations/example/pubspec.yaml b/packages/stream_chat_localizations/example/pubspec.yaml
new file mode 100644
index 00000000..a139831e
--- /dev/null
+++ b/packages/stream_chat_localizations/example/pubspec.yaml
@@ -0,0 +1,26 @@
+name: example
+description: A new Flutter project.
+
+publish_to: 'none'
+version: 1.0.0+1
+
+environment:
+ sdk: ">=2.12.0 <3.0.0"
+
+dependencies:
+ cupertino_icons: ^1.0.3
+ flutter:
+ sdk: flutter
+ stream_chat_localizations:
+ path: ../
+
+dependency_overrides:
+ stream_chat_flutter:
+ path: ../../stream_chat_flutter
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+flutter:
+ uses-material-design: true
\ No newline at end of file
diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart
new file mode 100644
index 00000000..f98932b5
--- /dev/null
+++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart
@@ -0,0 +1,164 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:stream_chat_flutter/stream_chat_flutter.dart';
+
+part 'stream_chat_localizations_en.dart';
+
+part 'stream_chat_localizations_fr.dart';
+
+part 'stream_chat_localizations_it.dart';
+
+part 'stream_chat_localizations_hi.dart';
+
+/// The set of supported languages, as language code strings.
+///
+/// The [GlobalStreamChatLocalizations.delegate] can generate localizations for
+/// any [Locale] with a language code from this set.
+///
+/// See also:
+///
+/// * [getStreamChatTranslation], whose documentation describes these values.
+const kStreamChatSupportedLanguages = {
+ 'en',
+ 'hi',
+ 'fr',
+ 'it',
+};
+
+/// Creates a [GlobalStreamChatLocalizations] instance for the given `locale`.
+///
+/// All of the function's arguments except `locale` will be passed to the
+/// [GlobalStreamChatLocalizations] constructor. (The `localeName` argument
+/// of that constructor is specified by the actual subclass constructor by this
+/// function.)
+///
+/// The following locales are supported by this package:
+///
+/// * `en` - English
+///
+/// Generally speaking, this method is only intended to be used by
+/// [GlobalStreamChatLocalizations.delegate].
+GlobalStreamChatLocalizations? getStreamChatTranslation(Locale locale) {
+ final languageCode = locale.languageCode;
+ assert(
+ kStreamChatSupportedLanguages.contains(languageCode),
+ 'getStreamChatTranslation() called for unsupported locale "$locale"',
+ );
+ switch (locale.languageCode) {
+ case 'en':
+ return const StreamChatLocalizationsEn();
+ case 'hi':
+ return const StreamChatLocalizationsHi();
+ case 'fr':
+ return const StreamChatLocalizationsFr();
+ case 'it':
+ return const StreamChatLocalizationsIt();
+ }
+}
+
+/// Implementation of localized strings for the stream chat widgets
+///
+/// ## Supported languages
+///
+/// This class supports locales with the following [Locale.languageCode]s:
+///
+/// {@macro flutter.localizations.material.languages}
+///
+/// This list is available programmatically via [kStreamChatSupportedLanguages].
+///
+/// ## Sample code
+///
+/// To include the localizations provided by this class in a [MaterialApp],
+/// add [GlobalStreamChatLocalizations.delegates] to
+/// [MaterialApp.localizationsDelegates], and specify the locales your
+/// app supports with [MaterialApp.supportedLocales]:
+///
+/// ```dart
+/// new MaterialApp(
+/// localizationsDelegates: GlobalStreamChatLocalizations.delegates,
+/// supportedLocales: [
+/// const Locale('en', 'US'), // American English
+/// // ...
+/// ],
+/// // ...
+/// )
+/// ```
+///
+abstract class GlobalStreamChatLocalizations
+ implements StreamChatLocalizations {
+ /// Initializes an object that defines the StreamChat widget's localized
+ /// strings for the given `localeName`.
+ const GlobalStreamChatLocalizations({
+ required String localeName,
+ }) : _localeName = localeName;
+
+ // ignore: unused_field
+ final String _localeName;
+
+ /// A [LocalizationsDelegate] for [StreamChatLocalizations].
+ ///
+ /// Most internationalized apps will use
+ /// [GlobalStreamChatLocalizations.delegates] as the value of
+ /// [MaterialApp.localizationsDelegates] to include the localizations for both
+ /// the flutter and stream chat widget libraries.
+ static const LocalizationsDelegate delegate =
+ _StreamChatLocalizationsDelegate();
+
+ /// A value for [MaterialApp.localizationsDelegates] that's typically used by
+ /// internationalized apps.
+ ///
+ /// ## Sample code
+ ///
+ /// To include the localizations provided by this class and by
+ /// [GlobalWidgetsLocalizations] in a [MaterialApp],
+ /// use [GlobalStreamChatLocalizations.delegates] as the value of
+ /// [MaterialApp.localizationsDelegates], and specify the locales your
+ /// app supports with [MaterialApp.supportedLocales]:
+ ///
+ /// ```dart
+ /// new MaterialApp(
+ /// localizationsDelegates: GlobalStreamChatLocalizations.delegates,
+ /// supportedLocales: [
+ /// const Locale('en', 'US'), // English
+ /// ],
+ /// // ...
+ /// )
+ /// ```
+ static const List delegates = [
+ GlobalStreamChatLocalizations.delegate,
+ GlobalCupertinoLocalizations.delegate,
+ GlobalMaterialLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate,
+ ];
+}
+
+class _StreamChatLocalizationsDelegate
+ extends LocalizationsDelegate {
+ const _StreamChatLocalizationsDelegate();
+
+ @override
+ bool isSupported(Locale locale) =>
+ kStreamChatSupportedLanguages.contains(locale.languageCode);
+
+ static final _loadedTranslations =
+ >{};
+
+ @override
+ Future load(Locale locale) {
+ assert(isSupported(locale), '');
+ return _loadedTranslations.putIfAbsent(
+ locale,
+ () => SynchronousFuture(
+ getStreamChatTranslation(locale)!,
+ ),
+ );
+ }
+
+ @override
+ bool shouldReload(_StreamChatLocalizationsDelegate old) => false;
+
+ @override
+ String toString() => 'GlobalStreamChatLocalizations.delegate('
+ '${kStreamChatSupportedLanguages.length} locales)';
+}
diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart
new file mode 100644
index 00000000..5041d398
--- /dev/null
+++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart
@@ -0,0 +1,360 @@
+part of 'stream_chat_localizations.dart';
+
+/// The translations for English (`en`).
+class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations {
+ /// Create an instance of the translation bundle for English.
+ const StreamChatLocalizationsEn({String localeName = 'en'})
+ : super(localeName: localeName);
+
+ @override
+ String get launchUrlError => 'Cannot launch the url';
+
+ @override
+ String get loadingUsersError => 'Error loading users';
+
+ @override
+ String get noUsersLabel => 'There are no users currently';
+
+ @override
+ String get retryLabel => 'Retry';
+
+ @override
+ String get userLastOnlineText => 'Last online';
+
+ @override
+ String get userOnlineText => 'Online';
+
+ @override
+ String userTypingText(Iterable users) {
+ if (users.isEmpty) return '';
+ final first = users.first;
+ if (users.length == 1) {
+ return '${first.name} is typing';
+ }
+ return '${first.name} and ${users.length - 1} more are typing';
+ }
+
+ @override
+ String get threadReplyLabel => 'Thread Reply';
+
+ @override
+ String get onlyVisibleToYouText => 'Only visible to you';
+
+ @override
+ String threadReplyCountText(int count) => '$count Thread Replies';
+
+ @override
+ String attachmentsUploadProgressText({
+ required int remaining,
+ required int total,
+ }) =>
+ 'Uploading $remaining/$total ...';
+
+ @override
+ String pinnedByUserText({
+ required User pinnedBy,
+ required User currentUser,
+ }) {
+ final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
+ if (pinnedByCurrentUser) return 'Pinned by You';
+ return 'Pinned by ${pinnedBy.name}';
+ }
+
+ @override
+ String get emptyMessagesText => 'There are no messages currently';
+
+ @override
+ String get genericErrorText => 'Something went wrong';
+
+ @override
+ String get loadingMessagesError => 'Error loading messages';
+
+ @override
+ String resultCountText(int count) => '$count results';
+
+ @override
+ String get messageDeletedText => 'This message is deleted.';
+
+ @override
+ String get messageDeletedLabel => 'Message deleted';
+
+ @override
+ String get messageReactionsLabel => 'Message Reactions';
+
+ @override
+ String get emptyChatMessagesText => 'No chats here yet...';
+
+ @override
+ String threadSeparatorText(int replyCount) {
+ if (replyCount == 1) return '1 Reply';
+ return '$replyCount Replies';
+ }
+
+ @override
+ String get connectedLabel => 'Connected';
+
+ @override
+ String get disconnectedLabel => 'Disconnected';
+
+ @override
+ String get reconnectingLabel => 'Reconnecting...';
+
+ @override
+ String get alsoSendAsDirectMessageLabel => 'Also send as direct message';
+
+ @override
+ String get addACommentOrSendLabel => 'Add a comment or send';
+
+ @override
+ String get searchGifLabel => 'Search GIFs';
+
+ @override
+ String get writeAMessageLabel => 'Write a message';
+
+ @override
+ String get instantCommandsLabel => 'Instant Commands';
+
+ @override
+ String fileTooLargeAfterCompressionError(double limitInMB) =>
+ 'The file is too large to upload. '
+ 'The file size limit is $limitInMB MB. '
+ 'We tried compressing it, but it was not enough.';
+
+ @override
+ String fileTooLargeError(double limitInMB) =>
+ 'The file is too large to upload. The file size limit is $limitInMB MB.';
+
+ @override
+ String emojiMatchingQueryText(String query) => 'Emoji matching "$query"';
+
+ @override
+ String get addAFileLabel => 'Add a file';
+
+ @override
+ String get photoFromCameraLabel => 'Photo from camera';
+
+ @override
+ String get uploadAFileLabel => 'Upload a file';
+
+ @override
+ String get uploadAPhotoLabel => 'Upload a photo';
+
+ @override
+ String get uploadAVideoLabel => 'Upload a video';
+
+ @override
+ String get videoFromCameraLabel => 'Video from camera';
+
+ @override
+ String get okLabel => 'OK';
+
+ @override
+ String get somethingWentWrongError => 'Something went wrong';
+
+ @override
+ String get addMoreFilesLabel => 'Add more files';
+
+ @override
+ String get enablePhotoAndVideoAccessMessage =>
+ 'Please enable access to your photos'
+ '\nand videos so you can share them with friends.';
+
+ @override
+ String get allowGalleryAccessMessage => 'Allow access to your gallery';
+
+ @override
+ String get flagMessageLabel => 'Flag Message';
+
+ @override
+ String get flagMessageQuestion =>
+ 'Do you want to send a copy of this message to a'
+ '\nmoderator for further investigation?';
+
+ @override
+ String get flagLabel => 'FLAG';
+
+ @override
+ String get cancelLabel => 'CANCEL';
+
+ @override
+ String get flagMessageSuccessfulLabel => 'Message flagged';
+
+ @override
+ String get flagMessageSuccessfulText =>
+ 'The message has been reported to a moderator.';
+
+ @override
+ String get deleteLabel => 'DELETE';
+
+ @override
+ String get deleteMessageLabel => 'Delete Message';
+
+ @override
+ String get deleteMessageQuestion =>
+ 'Are you sure you want to permanently delete this\nmessage?';
+
+ @override
+ String get operationCouldNotBeCompletedText =>
+ 'The operation couldn\'t be completed.';
+
+ @override
+ String get replyLabel => 'Reply';
+
+ @override
+ String togglePinUnpinText({required bool pinned}) {
+ if (pinned) return 'Unpin from Conversation';
+ return 'Pin to Conversation';
+ }
+
+ @override
+ String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
+ if (isDeleteFailed) return 'Retry Deleting Message';
+ return 'Delete Message';
+ }
+
+ @override
+ String get copyMessageLabel => 'Copy Message';
+
+ @override
+ String get editMessageLabel => 'Edit Message';
+
+ @override
+ String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
+ if (isUpdateFailed) return 'Resend Edited Message';
+ return 'Resend';
+ }
+
+ @override
+ String get photosLabel => 'Photos';
+
+ String _getDay(DateTime dateTime) {
+ final now = DateTime.now();
+ final today = DateTime(now.year, now.month, now.day);
+ final yesterday = DateTime(now.year, now.month, now.day - 1);
+
+ final date = DateTime(dateTime.year, dateTime.month, dateTime.day);
+
+ if (date == today) {
+ return 'today';
+ } else if (date == yesterday) {
+ return 'yesterday';
+ } else {
+ return 'on ${Jiffy(date).MMMd}';
+ }
+ }
+
+ @override
+ String sentAtText({required DateTime date, required DateTime time}) =>
+ 'Sent ${_getDay(date)} at ${Jiffy(time.toLocal()).format('HH:mm')}';
+
+ @override
+ String get todayLabel => 'Today';
+
+ @override
+ String get yesterdayLabel => 'Yesterday';
+
+ @override
+ String get channelIsMutedText => 'Channel is muted';
+
+ @override
+ String get noTitleText => 'No title';
+
+ @override
+ String get letsStartChattingLabel => 'Let’s start chatting!';
+
+ @override
+ String get sendingFirstMessageLabel =>
+ 'How about sending your first message to a friend?';
+
+ @override
+ String get startAChatLabel => 'Start a chat';
+
+ @override
+ String get loadingChannelsError => 'Error loading channels';
+
+ @override
+ String get deleteConversationLabel => 'Delete Conversation';
+
+ @override
+ String get deleteConversationQuestion =>
+ 'Are you sure you want to delete this conversation?';
+
+ @override
+ String get streamChatLabel => 'Stream Chat';
+
+ @override
+ String get searchingForNetworkText => 'Searching for Network';
+
+ @override
+ String get offlineLabel => 'Offline...';
+
+ @override
+ String get tryAgainLabel => 'Try Again';
+
+ @override
+ String membersCountText(int count) {
+ if (count == 1) return '1 Member';
+ return '$count Members';
+ }
+
+ @override
+ String watchersCountText(int count) {
+ if (count == 1) return '1 Online';
+ return '$count Online';
+ }
+
+ @override
+ String get viewInfoLabel => 'View Info';
+
+ @override
+ String get leaveGroupLabel => 'Leave Group';
+
+ @override
+ String get leaveLabel => 'LEAVE';
+
+ @override
+ String get leaveConversationLabel => 'Leave conversation';
+
+ @override
+ String get leaveConversationQuestion =>
+ 'Are you sure you want to leave this conversation?';
+
+ @override
+ String get showInChatLabel => 'Show in Chat';
+
+ @override
+ String get saveImageLabel => 'Save Image';
+
+ @override
+ String get saveVideoLabel => 'Save Video';
+
+ @override
+ String get uploadErrorLabel => 'UPLOAD ERROR';
+
+ @override
+ String get giphyLabel => 'Giphy';
+
+ @override
+ String get shuffleLabel => 'Shuffle';
+
+ @override
+ String get sendLabel => 'Send';
+
+ @override
+ String get withText => 'with';
+
+ @override
+ String get inText => 'in';
+
+ @override
+ String get youText => 'You';
+
+ @override
+ String get ofText => 'of';
+
+ @override
+ String get fileText => 'File';
+
+ @override
+ String get replyToMessageLabel => 'Reply to Message';
+}
diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart
new file mode 100644
index 00000000..1bbfbc5a
--- /dev/null
+++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart
@@ -0,0 +1,364 @@
+part of 'stream_chat_localizations.dart';
+
+/// The translations for French (`fr`).
+class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations {
+ /// Create an instance of the translation bundle for French.
+ const StreamChatLocalizationsFr({String localeName = 'fr'})
+ : super(localeName: localeName);
+
+ @override
+ String get launchUrlError => "Impossible de lancer l'url";
+
+ @override
+ String get loadingUsersError => 'Erreur de chargement des utilisateurs';
+
+ @override
+ String get noUsersLabel => "Il n'y a pas d'utilisateurs actuellement";
+
+ @override
+ String get retryLabel => 'Réessayer';
+
+ @override
+ String get userLastOnlineText => 'Dernière fois en ligne';
+
+ @override
+ String get userOnlineText => 'En ligne';
+
+ @override
+ String userTypingText(Iterable users) {
+ if (users.isEmpty) return '';
+ final first = users.first;
+ if (users.length == 1) {
+ return "${first.name} est en train d'écrire";
+ }
+ return "${first.name} and ${users.length - 1} sont entrain d'écrire";
+ }
+
+ @override
+ String get threadReplyLabel => 'Réponse au fil de discussion';
+
+ @override
+ String get onlyVisibleToYouText => 'Seulement visible par vous';
+
+ @override
+ String threadReplyCountText(int count) =>
+ '$count Réponses au fil de discussion';
+
+ @override
+ String attachmentsUploadProgressText({
+ required int remaining,
+ required int total,
+ }) =>
+ 'Uploading $remaining/$total ...';
+
+ @override
+ String pinnedByUserText({
+ required User pinnedBy,
+ required User currentUser,
+ }) {
+ final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
+ if (pinnedByCurrentUser) return 'Épinglé par vous';
+ return 'Épinglé par ${pinnedBy.name}';
+ }
+
+ @override
+ String get emptyMessagesText => "Il n'y a pas de messages actuellement";
+
+ @override
+ String get genericErrorText => 'Il y a eu un problème';
+
+ @override
+ String get loadingMessagesError => 'Erreur de chargement des messages';
+
+ @override
+ String resultCountText(int count) => '$count résultats';
+
+ @override
+ String get messageDeletedText => 'Ce message a été supprimé.';
+
+ @override
+ String get messageDeletedLabel => 'Message supprimé';
+
+ @override
+ String get messageReactionsLabel => 'Réactions aux messages';
+
+ @override
+ String get emptyChatMessagesText => 'Pas encore de chats ici...';
+
+ @override
+ String threadSeparatorText(int replyCount) {
+ if (replyCount == 1) return '1 Réponse';
+ return '$replyCount Replies';
+ }
+
+ @override
+ String get connectedLabel => 'Connecté';
+
+ @override
+ String get disconnectedLabel => 'Déconnecté';
+
+ @override
+ String get reconnectingLabel => 'Reconnexion...';
+
+ @override
+ String get alsoSendAsDirectMessageLabel =>
+ 'Envoyer aussi comme message direct';
+
+ @override
+ String get addACommentOrSendLabel => 'Ajouter un commentaire ou envoyer';
+
+ @override
+ String get searchGifLabel => 'Recherche de GIFs';
+
+ @override
+ String get writeAMessageLabel => 'Écrire un message';
+
+ @override
+ String get instantCommandsLabel => 'Commandes instantanées';
+
+ @override
+ String fileTooLargeAfterCompressionError(double limitInMB) =>
+ 'Le fichier est trop volumineux pour être téléchargé. '
+ 'La taille maximale des fichiers est de $limitInMB Mo. '
+ "Nous avons essayé de le compresser, mais ce n'était pas suffisant.";
+
+ @override
+ String fileTooLargeError(double limitInMB) =>
+ 'Le fichier est trop volumineux pour être téléchargé. '
+ 'La taille limite du fichier est de $limitInMB Mo.';
+
+ @override
+ String emojiMatchingQueryText(String query) =>
+ 'Emoji qui correspond à "$query"';
+
+ @override
+ String get addAFileLabel => 'Ajouter un fichier';
+
+ @override
+ String get photoFromCameraLabel => "Photo de l'appareil photo";
+
+ @override
+ String get uploadAFileLabel => 'Transférer un fichier';
+
+ @override
+ String get uploadAPhotoLabel => 'Transférer une photo';
+
+ @override
+ String get uploadAVideoLabel => 'Transférer une vidéo';
+
+ @override
+ String get videoFromCameraLabel => 'Vidéo depuis la camera';
+
+ @override
+ String get okLabel => 'OK';
+
+ @override
+ String get somethingWentWrongError => 'Quelque chose a mal tourné';
+
+ @override
+ String get addMoreFilesLabel => "Ajouter d'autres fichiers";
+
+ @override
+ String get enablePhotoAndVideoAccessMessage =>
+ "Veuillez autoriser l'accès à vos photos"
+ '\net vidéos afin de pouvoir les partager avec vos amis.';
+
+ @override
+ String get allowGalleryAccessMessage => "Autoriser l'accès à votre galerie";
+
+ @override
+ String get flagMessageLabel => 'Signaler un message';
+
+ @override
+ String get flagMessageQuestion =>
+ 'Voulez-vous envoyer une copie de ce message à un'
+ '\nmodérateur pour une enquête plus approfondie ?';
+
+ @override
+ String get flagLabel => 'SIGNALER';
+
+ @override
+ String get cancelLabel => 'ANNULER';
+
+ @override
+ String get flagMessageSuccessfulLabel => 'Message signalé';
+
+ @override
+ String get flagMessageSuccessfulText =>
+ 'Ce message a été signalé à un modérateur.';
+
+ @override
+ String get deleteLabel => 'SUPPRIMER';
+
+ @override
+ String get deleteMessageLabel => 'Supprimer le message';
+
+ @override
+ String get deleteMessageQuestion =>
+ 'Êtes-vous sûr de vouloir supprimer définitivement ce\nmessage ?';
+
+ @override
+ String get operationCouldNotBeCompletedText =>
+ "L'opération n'a pas pu être terminée.";
+
+ @override
+ String get replyLabel => 'Répondre';
+
+ @override
+ String togglePinUnpinText({required bool pinned}) {
+ if (pinned) return 'Détacher de la conversation';
+ return 'Attacher à la conversation';
+ }
+
+ @override
+ String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
+ if (isDeleteFailed) return 'Retenter de supprimer le message';
+ return 'Supprimer le message';
+ }
+
+ @override
+ String get copyMessageLabel => 'Copier le message';
+
+ @override
+ String get editMessageLabel => 'Modifier le message';
+
+ @override
+ String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
+ if (isUpdateFailed) return 'Renvoyer le message modifié';
+ return 'Renvoyer';
+ }
+
+ @override
+ String get photosLabel => 'Photos';
+
+ String _getDay(DateTime dateTime) {
+ final now = DateTime.now();
+ final today = DateTime(now.year, now.month, now.day);
+ final yesterday = DateTime(now.year, now.month, now.day - 1);
+
+ final date = DateTime(dateTime.year, dateTime.month, dateTime.day);
+
+ if (date == today) {
+ return "aujourd'hui";
+ } else if (date == yesterday) {
+ return 'hier';
+ } else {
+ return 'le ${Jiffy(date).MMMd}';
+ }
+ }
+
+ @override
+ String sentAtText({required DateTime date, required DateTime time}) =>
+ 'Envoyé ${_getDay(date)} à ${Jiffy(time.toLocal()).format('HH:mm')}';
+
+ @override
+ String get todayLabel => "Aujourd'hui";
+
+ @override
+ String get yesterdayLabel => 'Hier';
+
+ @override
+ String get channelIsMutedText => 'Le canal est coupé';
+
+ @override
+ String get noTitleText => 'Aucun titre';
+
+ @override
+ String get letsStartChattingLabel => 'Commençons à discuter !';
+
+ @override
+ String get sendingFirstMessageLabel =>
+ "Que diriez-vous d'envoyer votre premier message à un ami ?";
+
+ @override
+ String get startAChatLabel => 'Commencer une discussion';
+
+ @override
+ String get loadingChannelsError => 'Erreur lors du chargement des canaux';
+
+ @override
+ String get deleteConversationLabel => 'Supprimer la conversation';
+
+ @override
+ String get deleteConversationQuestion =>
+ 'Vous êtes sûr de vouloir supprimer cette conversation ?';
+
+ @override
+ String get streamChatLabel => 'Stream Chat';
+
+ @override
+ String get searchingForNetworkText => 'Recherche de réseau';
+
+ @override
+ String get offlineLabel => 'Hors ligne...';
+
+ @override
+ String get tryAgainLabel => 'Essayer à nouveau';
+
+ @override
+ String membersCountText(int count) {
+ if (count == 1) return '1 Membre';
+ return '$count Membres';
+ }
+
+ @override
+ String watchersCountText(int count) {
+ if (count == 1) return '1 En ligne';
+ return '$count En ligne';
+ }
+
+ @override
+ String get viewInfoLabel => 'Voir les informations';
+
+ @override
+ String get leaveGroupLabel => 'Quitter le Group';
+
+ @override
+ String get leaveLabel => 'QUITTER';
+
+ @override
+ String get leaveConversationLabel => 'Quitter la conversation';
+
+ @override
+ String get leaveConversationQuestion =>
+ 'Etes-vous sûr de vouloir quitter cette conversation ?';
+
+ @override
+ String get showInChatLabel => 'Montrer dans le Chat';
+
+ @override
+ String get saveImageLabel => "Sauvegarder l'image";
+
+ @override
+ String get saveVideoLabel => 'Sauvegarder la vidéo';
+
+ @override
+ String get uploadErrorLabel => 'ERREUR DE TRANSFERT';
+
+ @override
+ String get giphyLabel => 'Giphy';
+
+ @override
+ String get shuffleLabel => 'Mélanger';
+
+ @override
+ String get sendLabel => 'Envoyer';
+
+ @override
+ String get withText => 'avec';
+
+ @override
+ String get inText => 'dans';
+
+ @override
+ String get youText => 'Vous';
+
+ @override
+ String get ofText => 'de';
+
+ @override
+ String get fileText => 'Fichier';
+
+ @override
+ String get replyToMessageLabel => 'Répondre au Message';
+}
diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart
new file mode 100644
index 00000000..850e481b
--- /dev/null
+++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart
@@ -0,0 +1,359 @@
+part of 'stream_chat_localizations.dart';
+
+/// The translations for Hindi (`hi`).
+class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations {
+ /// Create an instance of the translation bundle for Hindi.
+ const StreamChatLocalizationsHi({String localeName = 'hi'})
+ : super(localeName: localeName);
+
+ @override
+ String get launchUrlError => 'यूआरएल लॉन्च नहीं कर सकते';
+
+ @override
+ String get loadingUsersError => 'यूजर लोड करने में समस्या';
+
+ @override
+ String get noUsersLabel => 'वर्तमान में कोई यूजर नहीं हैं';
+
+ @override
+ String get retryLabel => 'पुन: प्रयास करे';
+
+ @override
+ String get userLastOnlineText => 'अंतिम ऑनलाइन';
+
+ @override
+ String get userOnlineText => 'ऑनलाइन';
+
+ @override
+ String userTypingText(Iterable users) {
+ if (users.isEmpty) return '';
+ final first = users.first;
+ if (users.length == 1) {
+ return '${first.name} टाइप कर रहा है';
+ }
+ return '${first.name} और ${users.length - 1} और टाइप कर रहे हैं';
+ }
+
+ @override
+ String get threadReplyLabel => 'थ्रेड जवाब';
+
+ @override
+ String get onlyVisibleToYouText => 'केवल आपको दिखाई दे रहा है';
+
+ @override
+ String threadReplyCountText(int count) => '$count थ्रेड जवाब';
+
+ @override
+ String attachmentsUploadProgressText({
+ required int remaining,
+ required int total,
+ }) =>
+ 'अपलोडिंग $remaining/$total ...';
+
+ @override
+ String pinnedByUserText({
+ required User pinnedBy,
+ required User currentUser,
+ }) {
+ final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
+ if (pinnedByCurrentUser) return 'आपके द्वारा पिन किया गया';
+ return '${pinnedBy.name} द्वारा पिन किया गया';
+ }
+
+ @override
+ String get emptyMessagesText => 'वर्तमान में कोई संदेश नहीं है';
+
+ @override
+ String get genericErrorText => 'कुछ समस्या हो गई';
+
+ @override
+ String get loadingMessagesError => 'संदेश लोड करने में समस्या';
+
+ @override
+ String resultCountText(int count) => '$count परिणाम';
+
+ @override
+ String get messageDeletedText => 'यह संदेश हटा दिया गया है।';
+
+ @override
+ String get messageDeletedLabel => 'संदेश हटाये';
+
+ @override
+ String get messageReactionsLabel => 'संदेश प्रतिक्रियाएं';
+
+ @override
+ String get emptyChatMessagesText => 'यहां अभी तक कोई चैट नहीं...';
+
+ @override
+ String threadSeparatorText(int replyCount) {
+ if (replyCount == 1) return '1 जवाब';
+ return '$replyCount जवाब';
+ }
+
+ @override
+ String get connectedLabel => 'कनेक्टेड';
+
+ @override
+ String get disconnectedLabel => 'डिस्कनेक्टेड';
+
+ @override
+ String get reconnectingLabel => 'पुनः कनेक्टिंग...';
+
+ @override
+ String get alsoSendAsDirectMessageLabel => 'सीधे संदेश के रूप में भी भेजें';
+
+ @override
+ String get addACommentOrSendLabel => 'एक टिप्पणी जोड़ें या भेजें';
+
+ @override
+ String get searchGifLabel => 'जीआईएफ खोजें';
+
+ @override
+ String get writeAMessageLabel => 'एक सन्देश लिखिए';
+
+ @override
+ String get instantCommandsLabel => 'तत्काल आदेश';
+
+ @override
+ String fileTooLargeAfterCompressionError(double limitInMB) =>
+ 'फ़ाइल अपलोड करने के लिए बहुत बड़ी है। '
+ 'फ़ाइल आकार सीमा $limitInMB MB है। '
+ 'हमने इसे कंप्रेस करने की कोशिश की, लेकिन यह काफी नहीं था।';
+
+ @override
+ String fileTooLargeError(double limitInMB) =>
+ 'फ़ाइल अपलोड करने के लिए बहुत बड़ी है। फ़ाइल आकार सीमा $limitInMB MB है।';
+
+ @override
+ String emojiMatchingQueryText(String query) => '"$query" से मिलते हुए इमोजी';
+
+ @override
+ String get addAFileLabel => 'एक फ़ाइल जोड़ें';
+
+ @override
+ String get photoFromCameraLabel => 'कैमरे से फोटो';
+
+ @override
+ String get uploadAFileLabel => 'एक फाइल अपलोड करें';
+
+ @override
+ String get uploadAPhotoLabel => 'एक फोटो अपलोड करो';
+
+ @override
+ String get uploadAVideoLabel => 'एक वीडियो अपलोड करें';
+
+ @override
+ String get videoFromCameraLabel => 'कैमरे से वीडियो';
+
+ @override
+ String get okLabel => 'ठीक';
+
+ @override
+ String get somethingWentWrongError => 'लोड करने में समस्या';
+
+ @override
+ String get addMoreFilesLabel => 'और फ़ाइलें जोड़ें';
+
+ @override
+ String get enablePhotoAndVideoAccessMessage =>
+ 'कृपया अपने फ़ोटो और वीडियो तक पहुंच सक्षम करें'
+ '\nताकि आप उन्हें मित्रों के साथ साझा कर सकें।';
+
+ @override
+ String get allowGalleryAccessMessage => 'अपनी गैलरी तक पहुंच की अनुमति दें';
+
+ @override
+ String get flagMessageLabel => 'फ्लैग संदेश';
+
+ @override
+ String get flagMessageQuestion => 'क्या आप आगे की जांच के लिए इस संदेश की'
+ '\nएक प्रति मॉडरेटर को भेजना चाहते हैं?';
+
+ @override
+ String get flagLabel => 'फ्लैग';
+
+ @override
+ String get cancelLabel => 'रद्द करें';
+
+ @override
+ String get flagMessageSuccessfulLabel => 'संदेश फ्लैग हो गया';
+
+ @override
+ String get flagMessageSuccessfulText =>
+ 'संदेश की रिपोर्ट एक मॉडरेटर को कर दी गई है।';
+
+ @override
+ String get deleteLabel => 'हटाएँ';
+
+ @override
+ String get deleteMessageLabel => 'संदेश हटाएं';
+
+ @override
+ String get deleteMessageQuestion =>
+ 'क्या आप वाकई इस संदेश को स्थायी रूप से\nहटाना चाहते हैं?';
+
+ @override
+ String get operationCouldNotBeCompletedText =>
+ 'कार्रवाई पूरी नहीं की जा सकी.';
+
+ @override
+ String get replyLabel => 'रिप्लाई';
+
+ @override
+ String togglePinUnpinText({required bool pinned}) {
+ if (pinned) return 'बातचीत से अनपिन करें';
+ return 'बातचीत में पिन करें';
+ }
+
+ @override
+ String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
+ if (isDeleteFailed) return 'संदेश हटाने का पुनः प्रयास करें';
+ return 'संदेश को हटाएं';
+ }
+
+ @override
+ String get copyMessageLabel => 'संदेश कॉपी करें';
+
+ @override
+ String get editMessageLabel => 'संदेश एडिट करें';
+
+ @override
+ String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
+ if (isUpdateFailed) return 'एडिट संदेश फिर से भेजें';
+ return 'पुन: भेजें';
+ }
+
+ @override
+ String get photosLabel => 'तस्वीरें';
+
+ String _getDay(DateTime dateTime) {
+ final now = DateTime.now();
+ final today = DateTime(now.year, now.month, now.day);
+ final yesterday = DateTime(now.year, now.month, now.day - 1);
+
+ final date = DateTime(dateTime.year, dateTime.month, dateTime.day);
+
+ if (date == today) {
+ return 'आज';
+ } else if (date == yesterday) {
+ return 'कल';
+ } else {
+ return '${Jiffy(date).MMMd} को';
+ }
+ }
+
+ @override
+ String sentAtText({required DateTime date, required DateTime time}) =>
+ '${_getDay(date)} ${Jiffy(time.toLocal()).format('HH:mm')} बजे भेजा गया';
+
+ @override
+ String get todayLabel => 'आज';
+
+ @override
+ String get yesterdayLabel => 'बिता हुआ कल';
+
+ @override
+ String get channelIsMutedText => 'चैनल मौन है';
+
+ @override
+ String get noTitleText => 'कोई शीर्षक नहीं';
+
+ @override
+ String get letsStartChattingLabel => 'चलो चैट करना शुरू करें!';
+
+ @override
+ String get sendingFirstMessageLabel =>
+ 'किसी मित्र को अपना पहला संदेश भेजने के बारे में क्या विचार है?';
+
+ @override
+ String get startAChatLabel => 'चैट शुरू करें';
+
+ @override
+ String get loadingChannelsError => 'चैनल लोड करने में समस्या';
+
+ @override
+ String get deleteConversationLabel => 'वार्तालाप हटाए';
+
+ @override
+ String get deleteConversationQuestion =>
+ 'क्या आप वाकई इस वार्तालाप को हटाना चाहते हैं?';
+
+ @override
+ String get streamChatLabel => 'स्ट्रीम चैट';
+
+ @override
+ String get searchingForNetworkText => 'नेटवर्क खोज रहे हैं';
+
+ @override
+ String get offlineLabel => 'ऑफलाइन...';
+
+ @override
+ String get tryAgainLabel => 'पुनः प्रयास करें';
+
+ @override
+ String membersCountText(int count) {
+ if (count == 1) return '1 सदस्य';
+ return '$count सदस्य';
+ }
+
+ @override
+ String watchersCountText(int count) {
+ if (count == 1) return '1 ऑनलाइन';
+ return '$count ऑनलाइन';
+ }
+
+ @override
+ String get viewInfoLabel => 'जानकारी देखें';
+
+ @override
+ String get leaveGroupLabel => 'समूह छोड़े';
+
+ @override
+ String get leaveLabel => 'छोड़े';
+
+ @override
+ String get leaveConversationLabel => 'वार्तालाप छोड़े';
+
+ @override
+ String get leaveConversationQuestion =>
+ 'क्या आप वाकई इस बातचीत को छोड़ना चाहते हैं?';
+
+ @override
+ String get showInChatLabel => 'चैट में दिखाएं';
+
+ @override
+ String get saveImageLabel => 'चित्र को सेव करें';
+
+ @override
+ String get saveVideoLabel => 'वीडियो को सेव करे';
+
+ @override
+ String get uploadErrorLabel => 'अपलोड समस्या';
+
+ @override
+ String get giphyLabel => 'जिफ़ी';
+
+ @override
+ String get shuffleLabel => 'बदलें';
+
+ @override
+ String get sendLabel => 'भेजें';
+
+ @override
+ String get withText => 'विद';
+
+ @override
+ String get inText => 'इन';
+
+ @override
+ String get youText => 'आप';
+
+ @override
+ String get ofText => 'ऑफ़';
+
+ @override
+ String get fileText => 'फ़ाइल';
+
+ @override
+ String get replyToMessageLabel => 'संदेश का जवाब';
+}
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
new file mode 100644
index 00000000..8b2e1692
--- /dev/null
+++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart
@@ -0,0 +1,361 @@
+part of 'stream_chat_localizations.dart';
+
+/// The translations for Italian (`it`).
+class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations {
+ /// Create an instance of the translation bundle for Italian.
+ const StreamChatLocalizationsIt({String localeName = 'it'})
+ : super(localeName: localeName);
+
+ @override
+ String get launchUrlError => 'Impossibile aprire l\'url';
+
+ @override
+ String get loadingUsersError => 'Errore durante il carimento degli utenti';
+
+ @override
+ String get noUsersLabel => 'Non c\'é nessun utente al momento';
+
+ @override
+ String get retryLabel => 'Riprova';
+
+ @override
+ String get userLastOnlineText => 'Ultimo accesso';
+
+ @override
+ String get userOnlineText => 'Online';
+
+ @override
+ String userTypingText(Iterable users) {
+ if (users.isEmpty) return '';
+ final first = users.first;
+ if (users.length == 1) {
+ return '${first.name} sta scrivendo';
+ }
+ return '${first.name} e altri ${users.length - 1} stanno scrivendo';
+ }
+
+ @override
+ String get threadReplyLabel => 'Rispondi nel thread';
+
+ @override
+ String get onlyVisibleToYouText => 'Visible solo a te';
+
+ @override
+ String threadReplyCountText(int count) => '$count risposte al thread';
+
+ @override
+ String attachmentsUploadProgressText({
+ required int remaining,
+ required int total,
+ }) =>
+ 'Caricamento $remaining/$total ...';
+
+ @override
+ String pinnedByUserText({
+ required User pinnedBy,
+ required User currentUser,
+ }) {
+ final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
+ if (pinnedByCurrentUser) return 'Messo in evidenza da te';
+ return 'Messo in evidenza da ${pinnedBy.name}';
+ }
+
+ @override
+ String get emptyMessagesText => 'Non c\'é nessun messaggio al momento';
+
+ @override
+ String get genericErrorText => 'Qualcosa è andato storto';
+
+ @override
+ String get loadingMessagesError =>
+ 'Errore durante il caricamento dei messaggi';
+
+ @override
+ String resultCountText(int count) => '$count risultati';
+
+ @override
+ String get messageDeletedText => 'Questo messaggio è stato eliminato';
+
+ @override
+ String get messageDeletedLabel => 'Messaggio cancellato';
+
+ @override
+ String get messageReactionsLabel => 'Reazioni al messaggio';
+
+ @override
+ String get emptyChatMessagesText => 'Nessuna conversazione al momento...';
+
+ @override
+ String threadSeparatorText(int replyCount) {
+ if (replyCount == 1) return '1 risposta';
+ return '$replyCount risposte';
+ }
+
+ @override
+ String get connectedLabel => 'Connesso';
+
+ @override
+ String get disconnectedLabel => 'Disconnesso';
+
+ @override
+ String get reconnectingLabel => 'Riconnessione in corso...';
+
+ @override
+ String get alsoSendAsDirectMessageLabel =>
+ 'Manda anche come messaggio diretto';
+
+ @override
+ String get addACommentOrSendLabel => 'Aggiungi un commento o invia';
+
+ @override
+ String get searchGifLabel => 'Cerca una GIF';
+
+ @override
+ String get writeAMessageLabel => 'Scrivi un messaggio';
+
+ @override
+ String get instantCommandsLabel => 'Commandi istantanei';
+
+ @override
+ String fileTooLargeAfterCompressionError(double limitInMB) =>
+ 'Il file è troppo grande per essere caricato. '
+ 'Il file eccede il limite di $limitInMB MB. '
+ 'Abbiamo provato a comprimerlo, ma non è stato abbastanza.';
+
+ @override
+ String fileTooLargeError(double limitInMB) => '''
+Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.''';
+
+ @override
+ String emojiMatchingQueryText(String query) => 'Emoji per "$query"';
+
+ @override
+ String get addAFileLabel => 'Aggiungi un file';
+
+ @override
+ String get photoFromCameraLabel => 'Immagine dalla fotocamera';
+
+ @override
+ String get uploadAFileLabel => 'Carica un file';
+
+ @override
+ String get uploadAPhotoLabel => 'Carica una foto';
+
+ @override
+ String get uploadAVideoLabel => 'Carica un video';
+
+ @override
+ String get videoFromCameraLabel => 'Video dalla fotocamera';
+
+ @override
+ String get okLabel => 'Ok';
+
+ @override
+ String get somethingWentWrongError => 'Qualcosa è andato storto';
+
+ @override
+ String get addMoreFilesLabel => 'Aggiungi altri file';
+
+ @override
+ String get enablePhotoAndVideoAccessMessage =>
+ 'Per favore attiva l\'accesso alle foto'
+ '\ne ai video cosí potrai condividerli con i tuoi amici.';
+
+ @override
+ String get allowGalleryAccessMessage => 'Permetti l\'accesso alla galleria';
+
+ @override
+ String get flagMessageLabel => 'Segnala messaggio';
+
+ @override
+ String get flagMessageQuestion => 'Vuoi mandare una copia di questo messaggio'
+ '\nad un moderatore?';
+
+ @override
+ String get flagLabel => 'SEGNALA';
+
+ @override
+ String get cancelLabel => 'ANNULLA';
+
+ @override
+ String get flagMessageSuccessfulLabel => 'Messaggio segnalato';
+
+ @override
+ String get flagMessageSuccessfulText =>
+ 'Questo messaggio è stato segnalato ad un moderatore.';
+
+ @override
+ String get deleteLabel => 'CANCELLA';
+
+ @override
+ String get deleteMessageLabel => 'Cancella messaggio';
+
+ @override
+ String get deleteMessageQuestion =>
+ 'Sei sicuro di voler definitivamente cancellare questo\nmessaggio?';
+
+ @override
+ String get operationCouldNotBeCompletedText =>
+ 'Non è stato possibile completare questa operazione.';
+
+ @override
+ String get replyLabel => 'Rispondi';
+
+ @override
+ String togglePinUnpinText({required bool pinned}) {
+ if (pinned) return 'Rimuovi dagli elementi in evidenza';
+ return 'Metti in evidenza';
+ }
+
+ @override
+ String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
+ if (isDeleteFailed) return 'Riprova a cancellare il messaggio';
+ return 'Cancella il messaggio';
+ }
+
+ @override
+ String get copyMessageLabel => 'Copia messaggio';
+
+ @override
+ String get editMessageLabel => 'Modifica messaggio';
+
+ @override
+ String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
+ if (isUpdateFailed) return 'Riprova modifica messaggio';
+ return 'Riprova';
+ }
+
+ @override
+ String get photosLabel => 'Foto';
+
+ String _getDay(DateTime dateTime) {
+ final now = DateTime.now();
+ final today = DateTime(now.year, now.month, now.day);
+ final yesterday = DateTime(now.year, now.month, now.day - 1);
+
+ final date = DateTime(dateTime.year, dateTime.month, dateTime.day);
+
+ if (date == today) {
+ return 'oggi';
+ } else if (date == yesterday) {
+ return 'ieri';
+ } else {
+ return 'il ${Jiffy(date).MMMd}';
+ }
+ }
+
+ @override
+ String sentAtText({required DateTime date, required DateTime time}) =>
+ "Inviato ${_getDay(date)} alle ${Jiffy(time.toLocal()).format('HH:mm')}";
+
+ @override
+ String get todayLabel => 'Oggi';
+
+ @override
+ String get yesterdayLabel => 'Ieri';
+
+ @override
+ String get channelIsMutedText => 'Il canale è mutato';
+
+ @override
+ String get noTitleText => 'Nessun titolo';
+
+ @override
+ String get letsStartChattingLabel => 'Inizia una conversazione!';
+
+ @override
+ String get sendingFirstMessageLabel =>
+ 'Che ne dici di mandare il tuo primo messaggio ad un amico?';
+
+ @override
+ String get startAChatLabel => 'Inizia una conversazione';
+
+ @override
+ String get loadingChannelsError => 'Errore durante il caricamento dei canali';
+
+ @override
+ String get deleteConversationLabel => 'Elemina conversazione';
+
+ @override
+ String get deleteConversationQuestion =>
+ 'Sei sicuro di voler eliminare questa conversazione?';
+
+ @override
+ String get streamChatLabel => 'Stream Chat';
+
+ @override
+ String get searchingForNetworkText => 'Cercando una connessione';
+
+ @override
+ String get offlineLabel => 'Offline...';
+
+ @override
+ String get tryAgainLabel => 'Riprova';
+
+ @override
+ String membersCountText(int count) {
+ if (count == 1) return '1 membro';
+ return '$count membri';
+ }
+
+ @override
+ String watchersCountText(int count) {
+ if (count == 1) return '1 Online';
+ return '$count Online';
+ }
+
+ @override
+ String get viewInfoLabel => 'Vedi info';
+
+ @override
+ String get leaveGroupLabel => 'Esci dal gruppo';
+
+ @override
+ String get leaveLabel => 'ESCI';
+
+ @override
+ String get leaveConversationLabel => 'Esci dalla conversazione';
+
+ @override
+ String get leaveConversationQuestion =>
+ 'Sei sicuro di voler lasciare questa conversazione?';
+
+ @override
+ String get showInChatLabel => 'Mostra nella chat';
+
+ @override
+ String get saveImageLabel => 'Salva immagine';
+
+ @override
+ String get saveVideoLabel => 'Salva video';
+
+ @override
+ String get uploadErrorLabel => 'ERRORE DURANTE IL CARICAMENTO';
+
+ @override
+ String get giphyLabel => 'Giphy';
+
+ @override
+ String get shuffleLabel => 'Shuffle';
+
+ @override
+ String get sendLabel => 'Invia';
+
+ @override
+ String get withText => 'con';
+
+ @override
+ String get inText => 'in';
+
+ @override
+ String get youText => 'te';
+
+ @override
+ String get ofText => 'di';
+
+ @override
+ String get fileText => 'file';
+
+ @override
+ String get replyToMessageLabel => 'Rispondi al messaggio';
+}
diff --git a/packages/stream_chat_localizations/lib/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/stream_chat_localizations.dart
new file mode 100644
index 00000000..27c01880
--- /dev/null
+++ b/packages/stream_chat_localizations/lib/stream_chat_localizations.dart
@@ -0,0 +1,9 @@
+/// Localizations for the StreamChat Flutter library.
+library stream_chat_localizations;
+
+export 'package:flutter_localizations/flutter_localizations.dart'
+ show
+ GlobalCupertinoLocalizations,
+ GlobalMaterialLocalizations,
+ GlobalWidgetsLocalizations;
+export 'src/stream_chat_localizations.dart' hide getStreamChatTranslation;
diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml
new file mode 100644
index 00000000..4d982a60
--- /dev/null
+++ b/packages/stream_chat_localizations/pubspec.yaml
@@ -0,0 +1,21 @@
+name: stream_chat_localizations
+description: The Official localizations for Stream Chat Flutter, a service for building chat applications
+version: 1.0.2
+homepage: https://github.com/GetStream/stream-chat-flutter
+repository: https://github.com/GetStream/stream-chat-flutter
+issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
+
+environment:
+ sdk: ">=2.12.0 <3.0.0"
+ flutter: ">=1.17.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+ flutter_localizations:
+ sdk: flutter
+ stream_chat_flutter: ^2.1.1
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
diff --git a/packages/stream_chat_localizations/test/basics_test.dart b/packages/stream_chat_localizations/test/basics_test.dart
new file mode 100644
index 00000000..c1315a9a
--- /dev/null
+++ b/packages/stream_chat_localizations/test/basics_test.dart
@@ -0,0 +1,109 @@
+// ignore_for_file: omit_local_variable_types
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:stream_chat_localizations/src/stream_chat_localizations.dart';
+
+void main() {
+ testWidgets('Nested Localizations', (WidgetTester tester) async {
+ await tester.pumpWidget(MaterialApp(
+ // Creates the outer Localizations widget.
+ home: ListView(
+ children: [
+ const LocalizationTracker(key: ValueKey('outer')),
+ Localizations(
+ locale: const Locale('hi'),
+ delegates: GlobalStreamChatLocalizations.delegates,
+ child: const LocalizationTracker(key: ValueKey('inner')),
+ ),
+ ],
+ ),
+ ));
+
+ final LocalizationTrackerState outerTracker = tester.state(
+ find.byKey(const ValueKey('outer'), skipOffstage: false));
+ expect(outerTracker.captionFontSize, 12.0);
+ final LocalizationTrackerState innerTracker = tester.state(
+ find.byKey(const ValueKey('inner'), skipOffstage: false));
+ expect(innerTracker.captionFontSize, 13.0);
+ });
+
+ testWidgets(
+ 'Localizations is compatible with ChangeNotifier.dispose() called '
+ 'during didChangeDependencies',
+ (WidgetTester tester) async {
+ // PageView calls ScrollPosition.dispose() during didChangeDependencies.
+ await tester.pumpWidget(MaterialApp(
+ supportedLocales: const