From d3e9bdafbde52b0fd083d124d60771d2c649183a Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 1 Jul 2021 20:26:01 +0530 Subject: [PATCH 001/113] feat(stream_chat_localizations): Initial implementation Signed-off-by: Sahil Kumar --- .../lib/src/extension.dart | 6 + .../lib/src/stream_chat.dart | 5 - .../lib/src/stream_chat_localizations.dart | 97 ++++++++ .../lib/stream_chat_flutter.dart | 1 + packages/stream_chat_localizations/.gitignore | 74 ++++++ packages/stream_chat_localizations/.metadata | 10 + .../stream_chat_localizations/CHANGELOG.md | 3 + packages/stream_chat_localizations/LICENSE | 219 ++++++++++++++++++ packages/stream_chat_localizations/README.md | 14 ++ .../lib/src/i18n/en.json | 0 .../lib/src/stream_chat_localizations.dart | 129 +++++++++++ .../lib/stream_chat_localizations.dart | 4 + .../stream_chat_localizations/pubspec.yaml | 57 +++++ .../test/stream_chat_localization_test.dart | 12 + 14 files changed, 626 insertions(+), 5 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/stream_chat_localizations.dart create mode 100644 packages/stream_chat_localizations/.gitignore create mode 100644 packages/stream_chat_localizations/.metadata create mode 100644 packages/stream_chat_localizations/CHANGELOG.md create mode 100644 packages/stream_chat_localizations/LICENSE create mode 100644 packages/stream_chat_localizations/README.md create mode 100644 packages/stream_chat_localizations/lib/src/i18n/en.json create mode 100644 packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart create mode 100644 packages/stream_chat_localizations/lib/stream_chat_localizations.dart create mode 100644 packages/stream_chat_localizations/pubspec.yaml create mode 100644 packages/stream_chat_localizations/test/stream_chat_localization_test.dart diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index 5161c8e8..1a80d19e 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -103,6 +103,12 @@ extension BuildContextX on BuildContext { // ignore: public_member_api_docs double get textScaleFactor => MediaQuery.maybeOf(this)?.textScaleFactor ?? 1.0; + + String translate({ + required String key, + required String defaultValue, + }) => + StreamChatLocalizations.of(this)?.translate(key) ?? defaultValue; } /// Extension on [BorderRadius] diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 64acae98..dfffce88 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -133,11 +133,6 @@ class StreamChatState extends State { /// The current user as a stream Stream get userStream => widget.client.state.userStream; - @override - void initState() { - super.initState(); - } - @override void didChangeDependencies() { final locale = ui.window.locale; diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_localizations.dart b/packages/stream_chat_flutter/lib/src/stream_chat_localizations.dart new file mode 100644 index 00000000..3ed1a9c3 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/stream_chat_localizations.dart @@ -0,0 +1,97 @@ +import 'package:flutter/widgets.dart'; + +// TODO : Fix localization instructions +// ADDING A NEW STRING +// +// If you (someone contributing to the Stream Chat Flutter) want to add a new +// string to the StreamChatLocalizations object (e.g. because you've added a new +// widget and it has a tooltip), follow these steps: +// +// 1. Add the new getter to StreamChatLocalizations below. +// +// 2. Implement a default value in DefaultMaterialLocalizations below. +// +// 3. Add a test to test/material/localizations_test.dart that verifies that +// this new value is implemented. +// +// 4. Update the flutter_localizations package. To add a new string to the +// flutter_localizations package, you must first add it to the English +// translations (lib/src/l10n/en.json), including a description. +// +// Then you need to add new entries for the string to all of the other +// language locale files by running: +// ``` +// dart dev/tools/localization/bin/gen_missing_localizations.dart +// ``` +// Which will copy the english strings into the other locales as placeholders +// until they can be translated. +// +// Finally you need to re-generate lib/src/l10n/localizations.dart by running: +// ``` +// dart dev/tools/localization/bin/gen_localizations.dart --overwrite +// ``` +// +// There is a README file with further information in the lib/src/l10n/ +// directory. +// +// 5. If you are a Google employee, you should then also follow the instructions +// at go/flutter-l10n. If you're not, don't worry about it. +// +// UPDATING AN EXISTING STRING +// +// If you (someone contributing to the Flutter framework) want to modify an +// existing string in the MaterialLocalizations objects, follow these steps: +// +// 1. Modify the default value of the relevant getter(s) in +// DefaultMaterialLocalizations below. +// +// 2. Update the flutter_localizations package. Modify the out-of-date English +// strings in lib/src/l10n/material_en.arb. +// +// You also need to re-generate lib/src/l10n/localizations.dart by running: +// ``` +// dart dev/tools/localization/bin/gen_localizations.dart --overwrite +// ``` +// +// This script may result in your updated getters being created in newer +// locales and set to the old value of the strings. This is to be expected. +// Leave them as they were generated, and they will be picked up for +// translation. +// +// There is a README file with further information in the lib/src/l10n/ +// directory. +// +// 3. If you are a Google employee, you should then also follow the instructions +// at go/flutter-l10n. If you're not, don't worry about it. + +/// Defines the localized resource values used by the StreamChatFlutter widgets. +/// +/// See also: +/// +/// * [GlobalStreamChatLocalizations], which provides material localizations +/// for many languages. +abstract class StreamChatLocalizations { + /// + String? translate(String key); + + /// 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).backButtonTooltip, + /// ``` + static StreamChatLocalizations? of(BuildContext context) => + Localizations.of( + context, + StreamChatLocalizations, + ); +} diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 62bfd299..20b2396a 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -28,6 +28,7 @@ export 'src/reaction_icon.dart'; export 'src/reaction_picker.dart'; export 'src/sending_indicator.dart'; export 'src/stream_chat.dart'; +export 'src/stream_chat_localizations.dart'; export 'src/stream_chat_theme.dart'; export 'src/stream_neumorphic_button.dart'; export 'src/stream_svg_icon.dart'; 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..41cc7d81 --- /dev/null +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial 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..3ec4d86e --- /dev/null +++ b/packages/stream_chat_localizations/README.md @@ -0,0 +1,14 @@ +# stream_chat_localizations + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Dart +[package](https://flutter.dev/developing-packages/), +a library module containing code that can be shared easily across +multiple Flutter or Dart projects. + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/stream_chat_localizations/lib/src/i18n/en.json b/packages/stream_chat_localizations/lib/src/i18n/en.json new file mode 100644 index 00000000..e69de29b 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..25ca28ea --- /dev/null +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -0,0 +1,129 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show rootBundle; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart' + show StreamChatLocalizations; + +const kStreamChatSupportedLanguages = []; + +/// 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 +/// const Locale('he', 'IL'), // Israeli Hebrew +/// // ... +/// ], +/// // ... +/// ) +/// ``` +/// +class GlobalStreamChatLocalizations implements StreamChatLocalizations { + /// Construct an object that defines the localized values for the widgets + /// library for US English (only). + /// + /// [LocalizationsDelegate] implementations typically call the static [load] + const GlobalStreamChatLocalizations(this.locale, this.translations); + + final Locale locale; + + final Map translations; + + static String getLocalePath(Locale locale) => + 'packages/stream_chat_localizations/i18n/${locale.languageCode}.json'; + + /// Creates an object that provides US English resource values for the + /// lowest levels of the widgets library. + /// + /// The [locale] parameter is ignored. + /// + /// This method is typically used to create a [LocalizationsDelegate]. + /// The [WidgetsApp] does so by default. + static Future load(Locale locale) async { + final localePath = getLocalePath(locale); + final rawTranslations = await rootBundle.loadString(localePath); + Map translations = json.decode(rawTranslations); + translations = translations.map( + (key, value) => MapEntry(key, value?.toString()), + ); + return GlobalStreamChatLocalizations(locale, translations); + } + + /// 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 + /// const Locale('he', 'IL'), // Hebrew + /// ], + /// // ... + /// ) + /// ``` + static const List delegates = [ + delegate, + GlobalCupertinoLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + @override + String? translate(String key) => translations[key]; +} + +class _StreamChatLocalizationsDelegate + extends LocalizationsDelegate { + const _StreamChatLocalizationsDelegate(); + + @override + bool isSupported(Locale locale) => + kStreamChatSupportedLanguages.contains(locale.languageCode); + + @override + Future load(Locale locale) => + GlobalStreamChatLocalizations.load(locale); + + @override + bool shouldReload(_StreamChatLocalizationsDelegate old) => false; + + @override + String toString() => 'StreamChatLocalizations.delegate(' + '${kStreamChatSupportedLanguages.length} locales)'; +} 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..9c1146d8 --- /dev/null +++ b/packages/stream_chat_localizations/lib/stream_chat_localizations.dart @@ -0,0 +1,4 @@ +/// Localizations for the StreamChat Flutter library. +library stream_chat_localization; + +export 'src/stream_chat_localizations.dart'; diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml new file mode 100644 index 00000000..34986658 --- /dev/null +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -0,0 +1,57 @@ +name: stream_chat_localizations +description: A new Flutter project. +version: 0.0.1 +homepage: + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + stream_chat_flutter: + path: ../stream_chat_flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stream_chat_localizations/test/stream_chat_localization_test.dart b/packages/stream_chat_localizations/test/stream_chat_localization_test.dart new file mode 100644 index 00000000..9ea48cb5 --- /dev/null +++ b/packages/stream_chat_localizations/test/stream_chat_localization_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:stream_chat_localization/stream_chat_localizations.dart'; + +void main() { + test('adds one to input values', () { + final calculator = Calculator(); + expect(calculator.addOne(2), 3); + expect(calculator.addOne(-7), -6); + expect(calculator.addOne(0), 1); + }); +} From 26912431028dfc97d55b8941d25572b44103f36b Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 8 Jul 2021 16:43:24 +0530 Subject: [PATCH 002/113] Remove json translations approach, add example for adding language Signed-off-by: Sahil Kumar --- .../stream_chat_flutter/example/lib/main.dart | 46 +++++++- .../stream_chat_flutter/example/pubspec.yaml | 2 + .../lib/src/extension.dart | 6 +- .../lib/src/message_text.dart | 3 + .../lib/src/stream_chat_localizations.dart | 5 +- packages/stream_chat_flutter/pubspec.yaml | 1 + .../lib/src/i18n/en.json | 0 .../lib/src/stream_chat_localizations.dart | 103 +++++++++++------- .../lib/src/stream_chat_localizations_en.dart | 11 ++ .../lib/stream_chat_localizations.dart | 7 +- .../stream_chat_localizations/pubspec.yaml | 37 ------- .../test/stream_chat_localization_test.dart | 22 ++-- 12 files changed, 144 insertions(+), 99 deletions(-) delete mode 100644 packages/stream_chat_localizations/lib/src/i18n/en.json create mode 100644 packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index d3522886..c1246a31 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -1,10 +1,35 @@ +import 'package:flutter/cupertino.dart'; +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'; -import 'package:stream_chat_persistence/stream_chat_persistence.dart'; +import 'package:stream_chat_localizations/stream_chat_localizations.dart'; -final chatPersistentClient = StreamChatPersistenceClient( - logLevel: Level.INFO, -); +/// A custom set of localizations for the 'hi' locale. +class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { + const StreamChatLocalizationsHi() : super(localeName: 'hi'); + + static const LocalizationsDelegate delegate = + _HindiStreamChatLocalizationsDelegate(); + + @override + String get launchUrlError => 'URL लॉन्च नहीं कर सकता'; +} + +class _HindiStreamChatLocalizationsDelegate + extends LocalizationsDelegate { + const _HindiStreamChatLocalizationsDelegate(); + + @override + bool isSupported(Locale locale) => locale.languageCode == 'hi'; + + @override + Future load(Locale locale) => + SynchronousFuture(const StreamChatLocalizationsHi()); + + @override + bool shouldReload(_HindiStreamChatLocalizationsDelegate old) => false; +} void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -14,7 +39,7 @@ void main() async { final client = StreamChatClient( 's2dxdhpxd94g', 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 using our server SDK. @@ -58,6 +83,17 @@ class MyApp extends StatelessWidget { theme: ThemeData.light(), darkTheme: ThemeData.dark(), themeMode: ThemeMode.system, + supportedLocales: [ + Locale('en', 'US'), + Locale('hi', 'IN'), + ], + localizationsDelegates: [ + GlobalStreamChatLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + StreamChatLocalizationsHi.delegate, + ], builder: (context, widget) { return StreamChat( client: client, diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index 408bf9e1..4eb99d86 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -29,6 +29,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/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index 1a80d19e..3a495ca5 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -104,11 +104,7 @@ extension BuildContextX on BuildContext { double get textScaleFactor => MediaQuery.maybeOf(this)?.textScaleFactor ?? 1.0; - String translate({ - required String key, - required String defaultValue, - }) => - StreamChatLocalizations.of(this)?.translate(key) ?? defaultValue; + StreamChatLocalizations? get translations => StreamChatLocalizations.of(this); } /// Extension on [BorderRadius] diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index 6d006085..b0bd13ca 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.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'; /// Text widget to display in message class MessageText extends StatelessWidget { @@ -29,6 +30,8 @@ class MessageText extends StatelessWidget { @override Widget build(BuildContext context) { + final texts = context.translations?.launchUrlError ?? 'defaultValue'; + return Text(texts); final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\n\n'); final themeData = Theme.of(context); diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_localizations.dart b/packages/stream_chat_flutter/lib/src/stream_chat_localizations.dart index 3ed1a9c3..b1d81fde 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_localizations.dart @@ -71,9 +71,6 @@ import 'package:flutter/widgets.dart'; /// * [GlobalStreamChatLocalizations], which provides material localizations /// for many languages. abstract class StreamChatLocalizations { - /// - String? translate(String key); - /// The `StreamChatLocalizations` from the closest [Localizations] instance /// that encloses the given context. /// @@ -94,4 +91,6 @@ abstract class StreamChatLocalizations { context, StreamChatLocalizations, ); + + String get launchUrlError; } diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 342914d4..e15f51c7 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -7,6 +7,7 @@ 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 diff --git a/packages/stream_chat_localizations/lib/src/i18n/en.json b/packages/stream_chat_localizations/lib/src/i18n/en.json deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart index 25ca28ea..b0ca6cd5 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -1,12 +1,45 @@ -import 'dart:convert'; - +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart' show rootBundle; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart' show StreamChatLocalizations; -const kStreamChatSupportedLanguages = []; +part 'stream_chat_localizations_en.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'}; + +/// 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) { + switch (locale.languageCode) { + case 'en': + return const StreamChatLocalizationsEn(); + } + assert( + false, + 'getStreamChatTranslation() called for unsupported locale "$locale"', + ); + return null; +} /// Implementation of localized strings for the stream chat widgets /// @@ -30,43 +63,21 @@ const kStreamChatSupportedLanguages = []; /// localizationsDelegates: GlobalStreamChatLocalizations.delegates, /// supportedLocales: [ /// const Locale('en', 'US'), // American English -/// const Locale('he', 'IL'), // Israeli Hebrew /// // ... /// ], /// // ... /// ) /// ``` /// -class GlobalStreamChatLocalizations implements StreamChatLocalizations { - /// Construct an object that defines the localized values for the widgets - /// library for US English (only). - /// - /// [LocalizationsDelegate] implementations typically call the static [load] - const GlobalStreamChatLocalizations(this.locale, this.translations); +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; - final Locale locale; - - final Map translations; - - static String getLocalePath(Locale locale) => - 'packages/stream_chat_localizations/i18n/${locale.languageCode}.json'; - - /// Creates an object that provides US English resource values for the - /// lowest levels of the widgets library. - /// - /// The [locale] parameter is ignored. - /// - /// This method is typically used to create a [LocalizationsDelegate]. - /// The [WidgetsApp] does so by default. - static Future load(Locale locale) async { - final localePath = getLocalePath(locale); - final rawTranslations = await rootBundle.loadString(localePath); - Map translations = json.decode(rawTranslations); - translations = translations.map( - (key, value) => MapEntry(key, value?.toString()), - ); - return GlobalStreamChatLocalizations(locale, translations); - } + final String _localeName; /// A [LocalizationsDelegate] for [StreamChatLocalizations]. /// @@ -74,7 +85,7 @@ class GlobalStreamChatLocalizations implements StreamChatLocalizations { /// as the value of [MaterialApp.localizationsDelegates] to include /// the localizations for both the flutter and stream chat widget libraries. static const LocalizationsDelegate delegate = - _StreamChatLocalizationsDelegate(); + _StreamChatLocalizationsDelegate(); /// A value for [MaterialApp.localizationsDelegates] that's typically used by /// internationalized apps. @@ -92,20 +103,19 @@ class GlobalStreamChatLocalizations implements StreamChatLocalizations { /// localizationsDelegates: GlobalStreamChatLocalizations.delegates, /// supportedLocales: [ /// const Locale('en', 'US'), // English - /// const Locale('he', 'IL'), // Hebrew /// ], /// // ... /// ) /// ``` static const List delegates = [ - delegate, + GlobalStreamChatLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ]; @override - String? translate(String key) => translations[key]; + String get launchUrlError; } class _StreamChatLocalizationsDelegate @@ -116,14 +126,25 @@ class _StreamChatLocalizationsDelegate bool isSupported(Locale locale) => kStreamChatSupportedLanguages.contains(locale.languageCode); + static final _loadedTranslations = + >{}; + @override - Future load(Locale locale) => - GlobalStreamChatLocalizations.load(locale); + Future load(Locale locale) { + assert(isSupported(locale), ''); + return _loadedTranslations.putIfAbsent( + locale, + () => + SynchronousFuture( + getStreamChatTranslation(locale)!, + ), + ); + } @override bool shouldReload(_StreamChatLocalizationsDelegate old) => false; @override - String toString() => 'StreamChatLocalizations.delegate(' + 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..7566be30 --- /dev/null +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -0,0 +1,11 @@ +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'; +} diff --git a/packages/stream_chat_localizations/lib/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/stream_chat_localizations.dart index 9c1146d8..0cfb60ba 100644 --- a/packages/stream_chat_localizations/lib/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/stream_chat_localizations.dart @@ -1,4 +1,9 @@ /// Localizations for the StreamChat Flutter library. library stream_chat_localization; -export 'src/stream_chat_localizations.dart'; +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 index 34986658..b755a6dd 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -18,40 +18,3 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stream_chat_localizations/test/stream_chat_localization_test.dart b/packages/stream_chat_localizations/test/stream_chat_localization_test.dart index 9ea48cb5..0324dd17 100644 --- a/packages/stream_chat_localizations/test/stream_chat_localization_test.dart +++ b/packages/stream_chat_localizations/test/stream_chat_localization_test.dart @@ -1,12 +1,20 @@ +import 'dart:ui'; + import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_localization/stream_chat_localizations.dart'; +import 'package:stream_chat_localizations/stream_chat_localizations.dart'; void main() { - test('adds one to input values', () { - final calculator = Calculator(); - expect(calculator.addOne(2), 3); - expect(calculator.addOne(-7), -6); - expect(calculator.addOne(0), 1); - }); + for (final language in kStreamChatSupportedLanguages) { + test('translations exist for $language', () async { + final locale = Locale(language); + expect( + GlobalStreamChatLocalizations.delegate.isSupported(locale), + isTrue, + ); + final localizations = + await GlobalStreamChatLocalizations.delegate.load(locale); + expect(localizations.launchUrlError, isNotNull); + }); + } } From ffb46ecdca728a39be4f51461a2a9ab2c97df411 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 8 Jul 2021 16:59:57 +0530 Subject: [PATCH 003/113] minor changes --- .../lib/src/stream_chat_localizations.dart | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart index b0ca6cd5..bef008fd 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -30,15 +30,15 @@ const kStreamChatSupportedLanguages = {'en'}; /// 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(); } - assert( - false, - 'getStreamChatTranslation() called for unsupported locale "$locale"', - ); - return null; } /// Implementation of localized strings for the stream chat widgets @@ -85,7 +85,7 @@ abstract class GlobalStreamChatLocalizations /// as the value of [MaterialApp.localizationsDelegates] to include /// the localizations for both the flutter and stream chat widget libraries. static const LocalizationsDelegate delegate = - _StreamChatLocalizationsDelegate(); + _StreamChatLocalizationsDelegate(); /// A value for [MaterialApp.localizationsDelegates] that's typically used by /// internationalized apps. @@ -127,17 +127,16 @@ class _StreamChatLocalizationsDelegate kStreamChatSupportedLanguages.contains(locale.languageCode); static final _loadedTranslations = - >{}; + >{}; @override Future load(Locale locale) { assert(isSupported(locale), ''); return _loadedTranslations.putIfAbsent( locale, - () => - SynchronousFuture( - getStreamChatTranslation(locale)!, - ), + () => SynchronousFuture( + getStreamChatTranslation(locale)!, + ), ); } From a48389a814317ff8c52faf502cda7fbb4a7d59e9 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 12 Jul 2021 15:01:36 +0530 Subject: [PATCH 004/113] add remaining translation strings Signed-off-by: xsahil03x --- .../attachment_upload_state_builder.dart | 3 +- .../lib/src/attachment/file_attachment.dart | 9 +- .../lib/src/attachment/giphy_attachment.dart | 17 +- .../lib/src/attachment_actions_modal.dart | 13 +- .../lib/src/channel_bottom_sheet.dart | 25 +- .../lib/src/channel_header.dart | 7 +- .../lib/src/channel_info.dart | 21 +- .../lib/src/channel_list_header.dart | 18 +- .../lib/src/channel_list_view.dart | 34 +- .../lib/src/channel_name.dart | 9 +- .../lib/src/channel_preview.dart | 5 +- .../lib/src/date_divider.dart | 5 +- .../lib/src/deleted_message.dart | 3 +- .../lib/src/extension.dart | 4 +- .../lib/src/full_screen_media.dart | 9 +- .../lib/src/image_footer.dart | 3 +- .../stream_chat_localizations.dart | 36 ++ .../lib/src/localization/translations.dart | 517 ++++++++++++++++++ .../lib/src/message_actions_modal.dart | 62 ++- .../lib/src/message_input.dart | 78 ++- .../lib/src/message_list_view.dart | 20 +- .../lib/src/message_reactions_modal.dart | 3 +- .../lib/src/message_search_item.dart | 3 +- .../lib/src/message_search_list_view.dart | 16 +- .../lib/src/message_text.dart | 3 - .../lib/src/message_widget.dart | 21 +- .../lib/src/stream_chat_localizations.dart | 96 ---- .../lib/src/thread_header.dart | 3 +- .../lib/src/typing_indicator.dart | 4 +- .../lib/src/user_item.dart | 8 +- .../lib/src/user_list_view.dart | 20 +- .../stream_chat_flutter/lib/src/utils.dart | 8 +- .../lib/stream_chat_flutter.dart | 2 +- .../lib/src/stream_chat_localizations.dart | 5 +- .../lib/src/stream_chat_localizations_en.dart | 315 +++++++++++ 35 files changed, 1104 insertions(+), 301 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart create mode 100644 packages/stream_chat_flutter/lib/src/localization/translations.dart delete mode 100644 packages/stream_chat_flutter/lib/src/stream_chat_localizations.dart 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 825def58..ebbaed7d 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.white, ), 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 7ec5fb04..547c5821 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 { @@ -285,7 +285,10 @@ class FileAttachment extends AttachmentWidget { progressIndicatorColor: theme.colorTheme.accentBlue, ), 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 1572061e..1ae03ca1 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -4,6 +4,7 @@ import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/attachment/attachment_widget.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 +72,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 +135,7 @@ class GiphyAttachment extends AttachmentWidget { }); }, child: Text( - 'Cancel', + context.translations.cancelLabel.toLowerCase(), style: StreamChatTheme.of(context) .textTheme .bodyBold @@ -166,7 +167,7 @@ class GiphyAttachment extends AttachmentWidget { }); }, child: Text( - 'Shuffle', + context.translations.shuffleLabel, style: StreamChatTheme.of(context) .textTheme .bodyBold @@ -199,7 +200,7 @@ class GiphyAttachment extends AttachmentWidget { }); }, child: Text( - 'Send', + context.translations.sendLabel, style: TextStyle( color: StreamChatTheme.of(context) .colorTheme @@ -234,7 +235,7 @@ class GiphyAttachment extends AttachmentWidget { width: 8, ), Text( - 'Only visible to you', + context.translations.onlyVisibleToYouText, style: StreamChatTheme.of(context) .textTheme .footnote @@ -339,7 +340,7 @@ class GiphyAttachment extends AttachmentWidget { size: 16, ), Text( - 'GIPHY', + context.translations.giphyLabel.toUpperCase(), style: TextStyle( color: StreamChatTheme.of(context).colorTheme.white, fontWeight: FontWeight.bold, 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 363fac45..48eea143 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.grey, @@ -80,7 +80,7 @@ class AttachmentActionsModal extends StatelessWidget { ), _buildButton( context, - 'Show in Chat', + context.translations.showInChatLabel, StreamSvgIcon.eye( size: 24, color: theme.colorTheme.black, @@ -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.grey, @@ -141,7 +142,7 @@ class AttachmentActionsModal extends StatelessWidget { if (StreamChat.of(context).user?.id == message.user?.id) _buildButton( context, - 'Delete', + context.translations.deleteLabel, StreamSvgIcon.delete( size: 24, color: theme.colorTheme.accentRed, 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 eaabb7e2..9a8a3d1a 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 { @@ -149,7 +150,7 @@ class _ChannelBottomSheetState extends State { color: _streamChatThemeData.colorTheme.grey, ), ), - title: 'View Info', + title: context.translations.viewInfoLabel, onTap: widget.onViewInfoTap, ), if (!channel.isDistinct) @@ -160,7 +161,7 @@ class _ChannelBottomSheetState extends State { color: _streamChatThemeData.colorTheme.grey, ), ), - title: 'Leave Group', + title: context.translations.leaveGroupLabel, onTap: () async { setState(() { _showActions = false; @@ -179,7 +180,7 @@ class _ChannelBottomSheetState extends State { color: _streamChatThemeData.colorTheme.accentRed, ), ), - title: 'Delete Conversation', + title: context.translations.deleteConversationLabel, titleColor: _streamChatThemeData.colorTheme.accentRed, onTap: () async { setState(() { @@ -198,7 +199,7 @@ class _ChannelBottomSheetState extends State { color: _streamChatThemeData.colorTheme.grey, ), ), - 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.accentRed, ), @@ -237,10 +238,10 @@ 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.accentRed, ), diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index 16c1f1b7..a844af1c 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'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header_paint.png) @@ -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 07920b95..c4ea92c9 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) @@ -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.searchingForNetworkLabel, 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.accentBlue, ), 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 5a4275a6..6c3eea20 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( @@ -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.black, ), @@ -226,7 +226,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { ), const SizedBox(width: 10), Text( - 'Searching for Network', + context.translations.searchingForNetworkLabel, 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 b14e0b75..847a265b 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?); @@ -202,6 +203,7 @@ class _ChannelListViewState extends State { final _slideController = SlidableController(); late final _defaultController = ChannelListController(); + ChannelListController get _channelListController => widget.channelListController ?? _defaultController; @@ -296,7 +298,7 @@ class _ChannelListViewState extends State { Padding( padding: const EdgeInsets.all(8), child: Text( - 'Let’s start chatting!', + context.translations.letsStartChattingLabel, style: chatThemeData.textTheme.headline, ), ), @@ -306,7 +308,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.grey, @@ -325,7 +327,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.accentBlue, ), @@ -461,9 +463,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, @@ -471,14 +473,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), ), ], ), @@ -558,12 +560,12 @@ class _ChannelListViewState extends State { : () async { final res = await showConfirmationDialog( context, - title: 'Delete Conversation', - okText: 'DELETE', - question: - // ignore: lines_longer_than_80_chars - 'Are you sure you want to delete this conversation?', - cancelText: 'CANCEL', + title: + context.translations.deleteConversationLabel, + question: context + .translations.deleteConversationQuestion, + okText: context.translations.deleteLabel, + cancelText: context.translations.cancelLabel, icon: StreamSvgIcon.delete( color: chatThemeData.colorTheme.accentRed, ), @@ -666,10 +668,10 @@ class _ChannelListViewState extends State { .colorTheme .accentRed .withOpacity(.2), - child: const Padding( - padding: EdgeInsets.symmetric(vertical: 16), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), child: Center( - child: Text('Error loading channels'), + child: Text(context.translations.loadingChannelsError), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart index 4819ecc7..ef5dcdee 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,8 +45,10 @@ class ChannelName extends StatelessWidget { ) => LayoutBuilder( builder: (context, constraints) { - var title = 'No title'; - if (extraData['name'] == null) { + var title = context.translations.noTitleText; + if (extraData['name'] != null) { + title = extraData['name']; + } else { final otherMembers = members?.where((member) => member.userId != client.user!.id); if (otherMembers?.length == 1) { @@ -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 231b5f94..f21625c6 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'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png) @@ -183,7 +184,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 { @@ -208,7 +209,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 185683be..e23f0233 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 9a4e0ae2..e62a7cfa 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 3a495ca5..b185ae3b 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(); @@ -104,7 +105,8 @@ extension BuildContextX on BuildContext { double get textScaleFactor => MediaQuery.maybeOf(this)?.textScaleFactor ?? 1.0; - StreamChatLocalizations? get translations => StreamChatLocalizations.of(this); + 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 f86ac60b..6810816f 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -10,6 +10,7 @@ import 'package:stream_chat_flutter/src/image_footer.dart'; import 'package:stream_chat_flutter/src/image_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 +183,11 @@ class _FullScreenMediaState extends State children: [ ImageHeader( 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')}', + // TODO: Fix this + sentAt: context.translations.sentAtText( + date: widget.message.createdAt, + time: widget.message.createdAt, + ), onBackPressed: () { Navigator.of(context).pop(); }, diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/image_footer.dart index 0a6571cc..3d12bf94 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/image_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 ImageFooter extends StatefulWidget implements PreferredSizeWidget { @@ -190,7 +191,7 @@ class _ImageFooterState extends State { child: Padding( padding: const EdgeInsets.all(16), child: Text( - 'Photos', + context.translations.photosLabel, style: chatThemeData.textTheme.headlineBold, ), ), 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..b2a1bd5e --- /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 material 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..651f97f8 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -0,0 +1,517 @@ +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart' + show User; + +abstract class Translations { + String get launchUrlError; + + String get loadingUsersError; + + String get retryLabel; + + String get noUsersLabel; + + String get userOnlineText; + + String get userLastOnlineText; + + String userTypingText(Iterable users); + + String get threadReplyLabel; + + String get onlyVisibleToYouText; + + String threadReplyCountText(int count); + + String attachmentsUploadProgressText({ + required int remaining, + required int total, + }); + + String pinnedByUserText({ + required User pinnedBy, + required User currentUser, + }); + + String get emptyMessagesText; + + String get genericErrorText; + + String get loadingMessagesError; + + String resultCountText(int count); + + String get messageDeletedText; + + String get messageDeletedLabel; + + String get messageReactionsText; + + String get emptyChatMessagesText; + + String threadSeparatorText(int replyCount); + + String get connectedLabel; + + String get disconnectedLabel; + + String get reconnectingLabel; + + String get alsoSendAsDirectMessageLabel; + + String get searchGifLabel; + + String get addACommentOrSendLabel; + + String get writeAMessageLabel; + + String get instantCommandsLabel; + + String get fileTooLargeAfterCompressionError; + + String get fileTooLargeError; + + String emojiMatchingQueryText(String query); + + String get addAFileLabel; + + String get uploadAPhotoLabel; + + String get uploadAVideoLabel; + + String get photoFromCameraLabel; + + String get videoFromCameraLabel; + + String get uploadAFileLabel; + + String get somethingWentWrongLabel; + + String get okLabel; + + String get addMoreFilesLabel; + + String get enablePhotoAndVideoAccessMessage; + + String get allowGalleryAccessMessage; + + String get flagMessageLabel; + + String get flagMessageQuestion; + + String get flagLabel; + + String get cancelLabel; + + String get flagMessageSuccessfulLabel; + + String get flagMessageSuccessfulText; + + String get deleteMessageLabel; + + String get deleteMessageQuestion; + + String get deleteLabel; + + String get operationCouldNotBeCompletedText; + + String get replyLabel; + + String togglePinUnpinText({required bool pinned}); + + String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}); + + String get copyMessageLabel; + + String get editMessageLabel; + + String toggleResendOrResendEditedMessage({required bool isUpdateFailed}); + + String get photosLabel; + + String sentAtText({required DateTime date, required DateTime time}); + + String get todayLabel; + + String get yesterdayLabel; + + String get channelIsMutedText; + + String get noTitleText; + + String get letsStartChattingLabel; + + String get sendingFirstMessageLabel; + + String get startAChatLabel; + + String get loadingChannelsError; + + // title: 'Delete Conversation', +// okText: 'DELETE', +// question: +// 'Are you sure you want to delete this conversation?', + + String get deleteConversationLabel; + + String get deleteConversationQuestion; + + String get streamChatLabel; + + String get searchingForNetworkLabel; + + String get offlineLabel; + + String get tryAgainLabel; + + String membersCountText(int count); + + String watchersCountText(int count); + + String get viewInfoLabel; + + String get leaveGroupLabel; + + String get leaveLabel; + + String get leaveConversationLabel; + + String get leaveConversationQuestion; + + String get showInChatLabel; + + String get saveImageLabel; + + String get saveVideoLabel; + + String get uploadErrorLabel; + + String get giphyLabel; + + String get shuffleLabel; + + String get sendLabel; +} + +class DefaultTranslations implements Translations { + const 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 was deleted.'; + + @override + String get messageDeletedLabel => 'Message deleted'; + + @override + String get messageReactionsText => '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 get fileTooLargeAfterCompressionError => + 'The file is too large to upload. ' + 'The file size limit is 20MB. ' + 'We tried compressing it, but it was not enough.'; + + @override + String get fileTooLargeError => + 'The file is too large to upload. The file size limit is 20MB.'; + + @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 somethingWentWrongLabel => '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'; + + @override + String sentAtText({required DateTime date, required DateTime time}) => + 'Sent $date at $time'; + + @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 searchingForNetworkLabel => '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'; +} 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 296b1e59..b7ba4655 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -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.accentRed, 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.okLabel, + cancelText: context.translations.cancelLabel, ); final theme = streamChatThemeData; @@ -290,9 +288,9 @@ class _MessageActionsModalState extends State { color: theme.colorTheme.accentRed, 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.accentRed, 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.accentRed, 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.accentRed, size: 24, ), - details: 'The operation couldn\'t be completed.', - title: 'Something went wrong', - okText: 'OK', + details: context.translations.operationCouldNotBeCompletedText, + title: context.translations.somethingWentWrongLabel, + 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,8 +592,8 @@ class _MessageActionsModalState extends State { color: streamChatThemeData.colorTheme.greyGainsboro, ), ), - const Text( - 'Edit Message', + Text( + context.translations.editMessageLabel, style: TextStyle(fontWeight: FontWeight.bold), ), IconButton( @@ -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 39211116..8bea5313 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -24,6 +24,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:substring_highlight/substring_highlight.dart'; import 'package:video_compress/video_compress.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; /// Builder for attachment thumbnails typedef AttachmentThumbnailBuilder = Widget Function( @@ -229,16 +230,12 @@ class MessageInput extends StatefulWidget { /// Use this method to get the current [StreamChatState] instance static MessageInputState of(BuildContext context) { MessageInputState? messageInputState; - messageInputState = context.findAncestorStateOfType(); - - if (messageInputState == null) { - throw Exception( - // ignore: lines_longer_than_80_chars - 'You must have a MessageInput widget as ancestor of your widget tree'); - } - - return messageInputState; + assert( + messageInputState != null, + 'You must have a MessageInput widget as ancestor of your widget tree', + ); + return messageInputState!; } } @@ -440,7 +437,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.black.withOpacity(0.5), ), @@ -564,7 +561,7 @@ class MessageInputState extends State { style: _streamChatTheme.messageInputTheme.inputTextStyle, autofocus: widget.autofocus, textAlignVertical: TextAlignVertical.center, - decoration: _getInputDecoration(), + decoration: _getInputDecoration(context), textCapitalization: TextCapitalization.sentences, ), ) @@ -576,11 +573,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.grey, ), @@ -729,14 +726,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) { @@ -859,7 +856,7 @@ class MessageInputState extends State { ), ), Text( - 'Instant Commands', + context.translations.instantCommandsLabel, style: TextStyle( color: _streamChatTheme.colorTheme.black.withOpacity(.5), @@ -1114,8 +1111,7 @@ 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, ); return; } @@ -1126,9 +1122,7 @@ 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); return; } } @@ -1396,7 +1390,9 @@ class MessageInputState extends State { ), Flexible( child: Text( - 'Emoji matching "$query"', + context.translations.emojiMatchingQueryText( + query, + ), style: TextStyle( color: _streamChatTheme.colorTheme.black .withOpacity(.5), @@ -1744,17 +1740,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); @@ -1762,7 +1758,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); @@ -1771,7 +1767,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); @@ -1780,7 +1776,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); @@ -1788,7 +1784,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); @@ -1888,8 +1884,7 @@ 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, ); return; } @@ -1900,9 +1895,7 @@ 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); return; } } @@ -2079,7 +2072,7 @@ class MessageInputState extends State { height: 26, ), Text( - 'Something went wrong', + context.translations.somethingWentWrongLabel, style: _streamChatTheme.textTheme.headlineBold, ), const SizedBox( @@ -2107,7 +2100,7 @@ class MessageInputState extends State { Navigator.of(context).pop(); }, child: Text( - 'OK', + context.translations.okLabel, style: _streamChatTheme.textTheme.bodyBold .copyWith(color: _streamChatTheme.colorTheme.accentBlue), ), @@ -2253,7 +2246,7 @@ class __PickerWidgetState extends State<_PickerWidget> { color: widget.streamChatTheme.colorTheme.whiteSmoke, alignment: Alignment.center, child: Text( - 'Add more files', + context.translations.addMoreFilesLabel, style: TextStyle( color: widget.streamChatTheme.colorTheme.accentBlue, fontWeight: FontWeight.bold, @@ -2285,8 +2278,7 @@ class __PickerWidgetState extends State<_PickerWidget> { color: widget.streamChatTheme.colorTheme.greyGainsboro, ), 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.grey), textAlign: TextAlign.center, @@ -2294,7 +2286,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.accentBlue, ), 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 e6bf7a17..72a2a314 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -17,6 +17,7 @@ import 'package:stream_chat_flutter/src/system_message.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:visibility_detector/visibility_detector.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; /// Widget builder for message /// [defaultMessageWidget] is the default [MessageWidget] configuration @@ -353,6 +354,7 @@ class _MessageListViewState extends State { bool _inBetweenList = false; late final _defaultController = MessageListController(); + MessageListController get _messageListController => widget.messageListController ?? _defaultController; @@ -366,7 +368,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.black.withOpacity(.5)), ), @@ -378,7 +380,7 @@ class _MessageListViewState extends State { errorWidgetBuilder: widget.errorWidgetBuilder ?? (BuildContext context, Object error) => Center( child: Text( - 'Something went wrong', + context.translations.genericErrorText, style: _streamTheme.textTheme.footnote.copyWith( color: _streamTheme.colorTheme.black.withOpacity(.5)), ), @@ -423,14 +425,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; } @@ -619,7 +621,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, @@ -627,7 +629,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, ), @@ -1253,8 +1255,8 @@ class _LoadingIndicator extends StatelessWidget { initialData: false, errorBuilder: (context, error) => Container( color: streamTheme.colorTheme.accentRed.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 0292a05a..f87b8740 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 { @@ -154,7 +155,7 @@ class MessageReactionsModal extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - 'Message Reactions', + context.translations.messageReactionsText, 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..0444d37f 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. /// @@ -98,7 +99,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 1a67702f..9d6d36d6 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; @@ -160,8 +162,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), ), ), ), @@ -175,7 +177,7 @@ class _MessageSearchListViewState extends State { showMessage: widget.showErrorTile, tileAnchor: Alignment.topCenter, childAnchor: Alignment.topCenter, - message: 'An error occurred.', + message: context.translations.genericErrorText, child: Container(), ); }, @@ -226,10 +228,10 @@ class _MessageSearchListViewState extends State { .colorTheme .accentRed .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), ), ), ); @@ -292,7 +294,7 @@ class _MessageSearchListViewState extends State { horizontal: 8, ), child: Text( - '${items.length} results', + context.translations.resultCountText(items.length), style: TextStyle( color: chatThemeData.colorTheme.grey, ), diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index b0bd13ca..6d006085 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.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'; /// Text widget to display in message class MessageText extends StatelessWidget { @@ -30,8 +29,6 @@ class MessageText extends StatelessWidget { @override Widget build(BuildContext context) { - final texts = context.translations?.launchUrlError ?? 'defaultValue'; - return Text(texts); final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\n\n'); final themeData = Theme.of(context); diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 7c95bf0a..03c77a44 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -16,6 +16,7 @@ import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; /// Widget builder for building attachments typedef AttachmentBuilder = Widget Function( @@ -840,7 +841,7 @@ class _MessageWidgetState extends State ), const SizedBox(width: 8), Text( - 'Only visible to you', + context.translations.onlyVisibleToYouText, style: chatThemeData.textTheme.footnote .copyWith(color: chatThemeData.colorTheme.grey), ), @@ -854,9 +855,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 @@ -1201,7 +1202,10 @@ class _MessageWidgetState extends State ); } return Text( - 'Uploading $uploadRemaining/$totalAttachments ...', + context.translations.attachmentsUploadProgressText( + remaining: uploadRemaining, + total: totalAttachments, + ), style: style, ); } @@ -1275,8 +1279,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.user!; return Padding( padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8), @@ -1290,7 +1294,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.grey, fontSize: 13, diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_localizations.dart b/packages/stream_chat_flutter/lib/src/stream_chat_localizations.dart deleted file mode 100644 index b1d81fde..00000000 --- a/packages/stream_chat_flutter/lib/src/stream_chat_localizations.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:flutter/widgets.dart'; - -// TODO : Fix localization instructions -// ADDING A NEW STRING -// -// If you (someone contributing to the Stream Chat Flutter) want to add a new -// string to the StreamChatLocalizations object (e.g. because you've added a new -// widget and it has a tooltip), follow these steps: -// -// 1. Add the new getter to StreamChatLocalizations below. -// -// 2. Implement a default value in DefaultMaterialLocalizations below. -// -// 3. Add a test to test/material/localizations_test.dart that verifies that -// this new value is implemented. -// -// 4. Update the flutter_localizations package. To add a new string to the -// flutter_localizations package, you must first add it to the English -// translations (lib/src/l10n/en.json), including a description. -// -// Then you need to add new entries for the string to all of the other -// language locale files by running: -// ``` -// dart dev/tools/localization/bin/gen_missing_localizations.dart -// ``` -// Which will copy the english strings into the other locales as placeholders -// until they can be translated. -// -// Finally you need to re-generate lib/src/l10n/localizations.dart by running: -// ``` -// dart dev/tools/localization/bin/gen_localizations.dart --overwrite -// ``` -// -// There is a README file with further information in the lib/src/l10n/ -// directory. -// -// 5. If you are a Google employee, you should then also follow the instructions -// at go/flutter-l10n. If you're not, don't worry about it. -// -// UPDATING AN EXISTING STRING -// -// If you (someone contributing to the Flutter framework) want to modify an -// existing string in the MaterialLocalizations objects, follow these steps: -// -// 1. Modify the default value of the relevant getter(s) in -// DefaultMaterialLocalizations below. -// -// 2. Update the flutter_localizations package. Modify the out-of-date English -// strings in lib/src/l10n/material_en.arb. -// -// You also need to re-generate lib/src/l10n/localizations.dart by running: -// ``` -// dart dev/tools/localization/bin/gen_localizations.dart --overwrite -// ``` -// -// This script may result in your updated getters being created in newer -// locales and set to the old value of the strings. This is to be expected. -// Leave them as they were generated, and they will be picked up for -// translation. -// -// There is a README file with further information in the lib/src/l10n/ -// directory. -// -// 3. If you are a Google employee, you should then also follow the instructions -// at go/flutter-l10n. If you're not, don't worry about it. - -/// Defines the localized resource values used by the StreamChatFlutter widgets. -/// -/// See also: -/// -/// * [GlobalStreamChatLocalizations], which provides material localizations -/// for many languages. -abstract class StreamChatLocalizations { - /// 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).backButtonTooltip, - /// ``` - static StreamChatLocalizations? of(BuildContext context) => - Localizations.of( - context, - StreamChatLocalizations, - ); - - String get launchUrlError; -} diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart index bfde9a4c..8caf5ada 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'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header_paint.png) @@ -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 e9d9f7c0..4b42eb6a 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.black.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 f64f3c76..3ad1a337 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; @@ -207,9 +209,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, @@ -217,14 +219,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), ), ], ), @@ -237,8 +239,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), ), ), ), @@ -387,10 +389,10 @@ class _UserListViewState extends State .colorTheme .accentRed .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 d923b752..e6fda991 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/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 20b2396a..af8e66d7 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -15,6 +15,7 @@ export 'src/full_screen_media.dart'; export 'src/image_footer.dart'; export 'src/image_header.dart'; export 'src/info_tile.dart'; +export 'src/localization/stream_chat_localizations.dart'; export 'src/mention_tile.dart'; export 'src/message_action.dart'; export 'src/message_input.dart'; @@ -28,7 +29,6 @@ export 'src/reaction_icon.dart'; export 'src/reaction_picker.dart'; export 'src/sending_indicator.dart'; export 'src/stream_chat.dart'; -export 'src/stream_chat_localizations.dart'; export 'src/stream_chat_theme.dart'; export 'src/stream_neumorphic_button.dart'; export 'src/stream_svg_icon.dart'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart index bef008fd..4dba5948 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -2,7 +2,7 @@ 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' - show StreamChatLocalizations; + show StreamChatLocalizations, User; part 'stream_chat_localizations_en.dart'; @@ -113,9 +113,6 @@ abstract class GlobalStreamChatLocalizations GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ]; - - @override - String get launchUrlError; } class _StreamChatLocalizationsDelegate 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 index 7566be30..66abf0ce 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -8,4 +8,319 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { @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 was deleted.'; + + @override + String get messageDeletedLabel => 'Message deleted'; + + @override + String get messageReactionsText => '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 get fileTooLargeAfterCompressionError => + 'The file is too large to upload. ' + 'The file size limit is 20MB. ' + 'We tried compressing it, but it was not enough.'; + + @override + String get fileTooLargeError => + 'The file is too large to upload. The file size limit is 20MB.'; + + @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 somethingWentWrongLabel => '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'; + + @override + String sentAtText({required DateTime date, required DateTime time}) => + 'Sent $date at $time'; + + @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 searchingForNetworkLabel => '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'; } From 67a36d9c35a34b7ca07460cc2cc04ec37eff8117 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 19 Jul 2021 16:51:49 +0530 Subject: [PATCH 005/113] chore: add remaining translations Signed-off-by: xsahil03x --- .../stream_chat_flutter/example/lib/main.dart | 44 +++---------------- .../lib/src/attachment/file_attachment.dart | 2 +- .../lib/src/full_screen_media.dart | 2 +- .../lib/src/gallery_footer.dart | 4 +- .../lib/src/gallery_header.dart | 4 +- .../lib/src/localization/translations.dart | 25 +++++++++++ .../lib/src/message_input.dart | 4 +- .../lib/src/message_search_item.dart | 6 ++- .../lib/src/message_text.dart | 11 ++--- .../lib/src/system_message.dart | 4 +- .../lib/src/thread_header.dart | 2 +- .../lib/src/stream_chat_localizations_en.dart | 15 +++++++ 12 files changed, 65 insertions(+), 58 deletions(-) diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 91d44176..de2433ab 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -5,32 +5,6 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_localizations/stream_chat_localizations.dart'; -/// A custom set of localizations for the 'hi' locale. -class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { - const StreamChatLocalizationsHi() : super(localeName: 'hi'); - - static const LocalizationsDelegate delegate = - _HindiStreamChatLocalizationsDelegate(); - - @override - String get launchUrlError => 'URL लॉन्च नहीं कर सकता'; -} - -class _HindiStreamChatLocalizationsDelegate - extends LocalizationsDelegate { - const _HindiStreamChatLocalizationsDelegate(); - - @override - bool isSupported(Locale locale) => locale.languageCode == 'hi'; - - @override - Future load(Locale locale) => - SynchronousFuture(const StreamChatLocalizationsHi()); - - @override - bool shouldReload(_HindiStreamChatLocalizationsDelegate old) => false; -} - void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -96,18 +70,12 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) => MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), - themeMode: ThemeMode.system, - supportedLocales: [ - Locale('en', 'US'), - Locale('hi', 'IN'), - ], - localizationsDelegates: [ - GlobalStreamChatLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - StreamChatLocalizationsHi.delegate, - ], + localizationsDelegates: const [ + GlobalStreamChatLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], builder: (context, widget) => StreamChat( client: client, child: widget, 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 7377ba20..f57e1ee6 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -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, 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 287612ad..13ec3b3b 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -200,7 +200,7 @@ class _FullScreenMediaState extends State ); }, ), - if (widget.message.type != 'ephemeral') + if (!widget.message.isEphemeral) GalleryFooter( currentPage: _currentPage, totalPages: widget.mediaAttachments.length, diff --git a/packages/stream_chat_flutter/lib/src/gallery_footer.dart b/packages/stream_chat_flutter/lib/src/gallery_footer.dart index ed8c39f9..03d575ae 100644 --- a/packages/stream_chat_flutter/lib/src/gallery_footer.dart +++ b/packages/stream_chat_flutter/lib/src/gallery_footer.dart @@ -136,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, ), ], 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/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 651f97f8..9b270bd5 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -190,6 +190,16 @@ abstract class Translations { String get shuffleLabel; String get sendLabel; + + String get withText; + + String get inText; + + String get youText; + + String get ofText; + + String get fileText; } class DefaultTranslations implements Translations { @@ -514,4 +524,19 @@ class DefaultTranslations implements Translations { @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'; } diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 702cfe4e..456357ce 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -2254,10 +2254,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 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 0444d37f..de6c5e75 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_item.dart @@ -50,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).user?.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, ), diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index 6d006085..8fcf331c 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -43,15 +43,10 @@ class MessageText extends StatelessWidget { final mentionedUser = message.mentionedUsers.firstWhereOrNull( (u) => '@${u.name}' == link, ); - if (mentionedUser == null) { - return; - } - if (onMentionTap != null) { - onMentionTap!(mentionedUser); - } else { - print('tap on ${mentionedUser.name}'); - } + if (mentionedUser == null) return; + + onMentionTap?.call(mentionedUser); } else { if (onLinkTap != null) { onLinkTap!(link); 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 8caf5ada..a5eb62db 100644 --- a/packages/stream_chat_flutter/lib/src/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/thread_header.dart @@ -112,7 +112,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - 'with ', + '${context.translations.withText} ', style: chatThemeData.channelTheme.channelHeaderTheme.subtitle, ), Flexible( 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 index 66abf0ce..91f41b64 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -323,4 +323,19 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { @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'; } From 297092802dd8a34044be769fba1f30ba73488115 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 19 Jul 2021 16:56:08 +0530 Subject: [PATCH 006/113] chore: flutter format Signed-off-by: xsahil03x --- .../lib/src/channel_list_view.dart | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) 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 5ef8d706..0977f662 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -548,19 +548,18 @@ class _ChannelListViewState extends State { color: backgroundColor, iconWidget: StreamSvgIcon.delete( color: chatThemeData.colorTheme.accentError, - ), - onTap: widget.onDeletePressed != null - ? () { - widget.onDeletePressed!(channel); - } - : () async { - final res = await showConfirmationDialog( - context, - title: - context.translations.deleteConversationLabel, - question: context - .translations.deleteConversationQuestion, - okText: context.translations.deleteLabel, + ), + onTap: widget.onDeletePressed != null + ? () { + widget.onDeletePressed?.call(channel); + } + : () async { + final res = await showConfirmationDialog( + context, + title: context.translations.deleteConversationLabel, + question: + context.translations.deleteConversationQuestion, + okText: context.translations.deleteLabel, cancelText: context.translations.cancelLabel, icon: StreamSvgIcon.delete( color: chatThemeData.colorTheme.accentError, From 5f7b3ffae85119619adb7b36717334521c3f15c7 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 19 Jul 2021 17:42:36 +0530 Subject: [PATCH 007/113] test: fix tests Signed-off-by: xsahil03x --- .../stream_chat_flutter/lib/src/attachment_actions_modal.dart | 2 +- packages/stream_chat_flutter/lib/src/extension.dart | 3 ++- .../stream_chat_flutter/lib/src/message_actions_modal.dart | 4 ++-- packages/stream_chat_flutter/lib/src/message_input.dart | 1 - packages/stream_chat_flutter/lib/src/message_list_view.dart | 1 - packages/stream_chat_flutter/lib/src/message_widget.dart | 1 - .../test/src/attachment_actions_modal_test.dart | 1 + .../test/src/message_action_modal_test.dart | 4 ++-- 8 files changed, 8 insertions(+), 9 deletions(-) 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 0c224d20..9b64cce7 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart @@ -142,7 +142,7 @@ class AttachmentActionsModal extends StatelessWidget { if (StreamChat.of(context).user?.id == message.user?.id) _buildButton( context, - context.translations.deleteLabel, + context.translations.deleteLabel.capitalize(), StreamSvgIcon.delete( size: 24, color: theme.colorTheme.accentError, diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index b185ae3b..5d67ee50 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -10,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. /// 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 2f37d637..3e0afe75 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -274,7 +274,7 @@ class _MessageActionsModalState extends State { size: 24, ), question: context.translations.flagMessageQuestion, - okText: context.translations.okLabel, + okText: context.translations.flagLabel, cancelText: context.translations.cancelLabel, ); @@ -594,7 +594,7 @@ class _MessageActionsModalState extends State { ), Text( context.translations.editMessageLabel, - style: TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle(fontWeight: FontWeight.bold), ), IconButton( visualDensity: VisualDensity.compact, diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 456357ce..e5e6ca48 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -24,7 +24,6 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:substring_highlight/substring_highlight.dart'; import 'package:video_compress/video_compress.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; export 'package:video_compress/video_compress.dart' show VideoQuality; 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 2c250eaa..ac124b46 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -17,7 +17,6 @@ import 'package:stream_chat_flutter/src/system_message.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:visibility_detector/visibility_detector.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Widget builder for message /// [defaultMessageWidget] is the default [MessageWidget] configuration diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 32e4794b..44038282 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -16,7 +16,6 @@ import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Widget builder for building attachments typedef AttachmentBuilder = Widget Function( 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..59a49ac7 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 @@ -7,6 +7,7 @@ import 'package:stream_chat_flutter/src/attachment_actions_modal.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; class MockAttachmentDownloader extends Mock { ProgressCallback? progressCallback; 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..3aec707b 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 @@ -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(); @@ -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(); From 2b36f009c2eb43e18b473270d7345b10cf8d02c4 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 20 Jul 2021 18:09:12 +0530 Subject: [PATCH 008/113] chore: minor fixes, add support for "hi" locale Signed-off-by: xsahil03x --- .../stream_chat_flutter/example/lib/main.dart | 4 + .../lib/src/channel_info.dart | 4 +- .../lib/src/channel_preview.dart | 2 +- .../lib/src/full_screen_media.dart | 18 - .../lib/src/localization/translations.dart | 25 +- .../lib/src/user_item.dart | 4 +- .../lib/stream_chat_flutter.dart | 2 + .../lib/src/stream_chat_localizations.dart | 7 +- .../lib/src/stream_chat_localizations_en.dart | 19 +- .../lib/src/stream_chat_localizations_hi.dart | 353 ++++++++++++++++++ 10 files changed, 403 insertions(+), 35 deletions(-) create mode 100644 packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index de2433ab..d643dfea 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -70,6 +70,10 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) => MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), + supportedLocales: const [ + Locale('en'), + Locale('hi'), + ], localizationsDelegates: const [ GlobalStreamChatLocalizations.delegate, GlobalCupertinoLocalizations.delegate, diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index e9c2b7c9..4e860745 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -84,8 +84,8 @@ class ChannelInfo extends StatelessWidget { ); } else { alternativeWidget = Text( - context.translations.userLastOnlineText + - Jiffy(otherMember.user?.lastActive).fromNow(), + '${context.translations.userLastOnlineText} ' + '${Jiffy(otherMember.user?.lastActive).fromNow()}', style: textStyle, ); } diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 432f3354..4c0d2b42 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -198,7 +198,7 @@ class ChannelPreview extends StatelessWidget { size: 16, ), Text( - context.translations.channelIsMutedText, + ' ${context.translations.channelIsMutedText}', style: chatThemeData.channelPreviewTheme.subtitle, ), ], 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 13ec3b3b..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,7 +4,6 @@ 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'; @@ -183,7 +182,6 @@ class _FullScreenMediaState extends State children: [ GalleryHeader( userName: widget.userName, - // TODO: Fix this sentAt: context.translations.sentAtText( date: widget.message.createdAt, time: widget.message.createdAt, @@ -225,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/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 9b270bd5..4344dc8a 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -1,3 +1,4 @@ +import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart' show User; @@ -146,11 +147,6 @@ abstract class Translations { String get loadingChannelsError; - // title: 'Delete Conversation', -// okText: 'DELETE', -// question: -// 'Are you sure you want to delete this conversation?', - String get deleteConversationLabel; String get deleteConversationQuestion; @@ -274,7 +270,7 @@ class DefaultTranslations implements Translations { String resultCountText(int count) => '$count results'; @override - String get messageDeletedText => 'This message was deleted.'; + String get messageDeletedText => 'This message is deleted.'; @override String get messageDeletedLabel => 'Message deleted'; @@ -428,9 +424,22 @@ class DefaultTranslations implements Translations { @override String get photosLabel => 'Photos'; + String _getDay(DateTime dateTime) { + final now = Jiffy(DateTime.now()); + final date = Jiffy(dateTime); + + if (date.isSame(now, Units.DAY)) { + return 'today'; + } else if (now.diff(date, Units.HOUR) < 24) { + return 'yesterday'; + } else { + return 'on ${date.MMMd}'; + } + } + @override String sentAtText({required DateTime date, required DateTime time}) => - 'Sent $date at $time'; + 'Sent ${_getDay(date)} at ${Jiffy(time.toLocal()).format('HH:mm')}'; @override String get todayLabel => 'Today'; @@ -439,7 +448,7 @@ class DefaultTranslations implements Translations { String get yesterdayLabel => 'Yesterday'; @override - String get channelIsMutedText => ' Channel is muted'; + String get channelIsMutedText => 'Channel is muted'; @override String get noTitleText => 'No title'; diff --git a/packages/stream_chat_flutter/lib/src/user_item.dart b/packages/stream_chat_flutter/lib/src/user_item.dart index 99b35df1..a55693aa 100644 --- a/packages/stream_chat_flutter/lib/src/user_item.dart +++ b/packages/stream_chat_flutter/lib/src/user_item.dart @@ -92,8 +92,8 @@ class UserItem extends StatelessWidget { return Text( user.online == true ? context.translations.userOnlineText - : context.translations.userLastOnlineText + - Jiffy(user.lastActive).fromNow(), + : '${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/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 4b4fd9eb..eb690a9b 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'; @@ -16,6 +17,7 @@ export 'src/gallery_footer.dart'; export 'src/gallery_header.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'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart index 4dba5948..82662134 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -1,11 +1,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart' show StreamChatLocalizations, User; part 'stream_chat_localizations_en.dart'; +part 'stream_chat_localizations_hi.dart'; + /// The set of supported languages, as language code strings. /// /// The [GlobalStreamChatLocalizations.delegate] can generate localizations for @@ -14,7 +17,7 @@ part 'stream_chat_localizations_en.dart'; /// See also: /// /// * [getStreamChatTranslation], whose documentation describes these values. -const kStreamChatSupportedLanguages = {'en'}; +const kStreamChatSupportedLanguages = {'en', 'hi'}; /// Creates a [GlobalStreamChatLocalizations] instance for the given `locale`. /// @@ -38,6 +41,8 @@ GlobalStreamChatLocalizations? getStreamChatTranslation(Locale locale) { switch (locale.languageCode) { case 'en': return const StreamChatLocalizationsEn(); + case 'hi': + return const StreamChatLocalizationsHi(); } } 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 index 91f41b64..88b88840 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -73,7 +73,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String resultCountText(int count) => '$count results'; @override - String get messageDeletedText => 'This message was deleted.'; + String get messageDeletedText => 'This message is deleted.'; @override String get messageDeletedLabel => 'Message deleted'; @@ -227,9 +227,22 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { @override String get photosLabel => 'Photos'; + String _getDay(DateTime dateTime) { + final now = Jiffy(DateTime.now()); + final date = Jiffy(dateTime); + + if (date.isSame(now, Units.DAY)) { + return 'today'; + } else if (now.diff(date, Units.HOUR) < 24) { + return 'yesterday'; + } else { + return 'on ${date.MMMd}'; + } + } + @override String sentAtText({required DateTime date, required DateTime time}) => - 'Sent $date at $time'; + 'Sent ${_getDay(date)} at ${Jiffy(time.toLocal()).format('HH:mm')}'; @override String get todayLabel => 'Today'; @@ -238,7 +251,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get yesterdayLabel => 'Yesterday'; @override - String get channelIsMutedText => ' Channel is muted'; + String get channelIsMutedText => 'Channel is muted'; @override String get noTitleText => 'No title'; 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..5666b77d --- /dev/null +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -0,0 +1,353 @@ +part of 'stream_chat_localizations.dart'; + +/// The translations for English (`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 messageReactionsText => 'संदेश प्रतिक्रियाएं'; + + @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 get fileTooLargeAfterCompressionError => + 'फ़ाइल अपलोड करने के लिए बहुत बड़ी है। ' + 'फ़ाइल आकार सीमा 20MB है। ' + 'हमने इसे कंप्रेस करने की कोशिश की, लेकिन यह काफी नहीं था।'; + + @override + String get fileTooLargeError => + 'फ़ाइल अपलोड करने के लिए बहुत बड़ी है। फ़ाइल आकार सीमा 20MB है।'; + + @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 somethingWentWrongLabel => 'लोड करने में समस्या'; + + @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 = Jiffy(DateTime.now()); + final date = Jiffy(dateTime); + + if (date.isSame(now, Units.DAY)) { + return 'आज'; + } else if (now.diff(date, Units.HOUR) < 24) { + return 'कल'; + } else { + return '${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 searchingForNetworkLabel => 'नेटवर्क खोज रहे हैं'; + + @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 => 'फ़ाइल'; +} From 0691f8066a3b3fa3c2de50da2bf1d31c90375842 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Jul 2021 16:03:11 +0200 Subject: [PATCH 009/113] add quick links to docs --- packages/stream_chat/README.md | 1 + packages/stream_chat_flutter/README.md | 3 ++- packages/stream_chat_flutter_core/README.md | 1 + packages/stream_chat_persistence/README.md | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) 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_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_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_persistence/README.md b/packages/stream_chat_persistence/README.md index 7f94eda1..0851335b 100644 --- a/packages/stream_chat_persistence/README.md +++ b/packages/stream_chat_persistence/README.md @@ -13,6 +13,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/) +- [Flutter Offline Docs](https://getstream.io/chat/docs/flutter-dart/flutter_offline/) This package provides a persistence client for fetching and saving chat data locally. Stream Chat Persistence uses [Moor](https://github.com/simolus3/moor) as a disk cache. From cdee8c9ef29fbcac6c3a9d52c7bee1b58883a95e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 20 Jul 2021 19:47:56 +0530 Subject: [PATCH 010/113] fix(Jiffy): check if locale is supported before setting it Signed-off-by: xsahil03x --- packages/stream_chat_flutter/lib/src/stream_chat.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index f55f565d..5dab2fd5 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -136,7 +136,11 @@ class StreamChatState extends State { @override void didChangeDependencies() { final locale = ui.window.locale; - Jiffy.locale(locale.languageCode); + final languageCode = locale.languageCode; + final availableLocales = Jiffy.getAllAvailableLocales(); + if (availableLocales.contains(languageCode)) { + Jiffy.locale(languageCode); + } super.didChangeDependencies(); } } From 5831a21f1e2a782fe3519a0656899e817ce70869 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Jul 2021 18:05:18 +0200 Subject: [PATCH 011/113] update chat basics image --- .../docs/Flutter/assets/chat_basics.png | Bin 28913 -> 63987 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docusaurus/docs/Flutter/assets/chat_basics.png b/docusaurus/docs/Flutter/assets/chat_basics.png index 4eb0aa67639ff65c4331c28c45737a04b88be8f1..82e27cfa49ceac64fa7d27c37f280058397d9693 100644 GIT binary patch literal 63987 zcmdqI1#Fy6vnD#`IHnk4W*9RwGqdBE*&f?5#>^1M%*+glnHk0qGqYoknVESf-~G?M z=dR9b_pElM)wVQxds?r0s-CW{u6nvAOhri=0Kh5$0ML`~;2|U7 zE<4ST2jW*5Z6^Q#3G?p<3Xq7gGp)|QgG>*!h7CA8;UU#qS)^Ebfb0w!jKCcsN);>r2>vz8Fc27M4Fc4|~ zX{H;tsQ=ba{$>RIul@(9ZrFcyNudJ}|J4Rm-PfeQi@N_VlmhaY{75tOAEE#6Ci|aV*8c-w z`1}{&Tng{q`=`G)AcFIcsriqekDciqK?VS=04ml#5QIE#D`a=T%kEQGksC`zr@He?{FUWeQfw z{JLrL_)>2Kz6su0pl#e2^H!nGut#Om#)@h&$7T+paEQWIXmTU1Ow;yuV1AMMT8^7i z$>TP&cR7#%h|Pd&-3GWzVQNi{qA7xkgVCkls7%<3Q&(XW?{k{oSSh-sKTt zWc9f7VunbsJoU{fMi^Nk%hTJZFL0|G*U{;f5O7f;mEs8HUwbIlh3| zQQpG$+Q?6=E2M3Gw`&>W$`{_Q3DFg}=sl7j>K6hNQ+$1}y|-n`6h?8!LAcP{N9Ldq zm^it+R3v$^4K{rl-jDLCDhq4LEr>OSo@7kp&okV>&6}+ggnl_Nxa@QYi*1}V)CclFQ4%e;4kzW8bSHwlis$tp)B(;t1F~Ic z%lELD{=x53mSiLsC~-8M7_K{|Pf=^RHfb;!4tQNk_$Nyr?;C%qfG+J1JD~mjgwCDT z-2m0`eDY4-qa6-3*RJdFUa9|d)N$GQ-l*Me-q7g0%z;u6eUZ2;@J^*bpI(YAr_5S z9waEVx%Pc@7gezC+H=!Nqd}bfw1Yy18&x=1h{Olp18B_dJL84&(`Csks_YZflL5qg zEOp5_m8Q2?Wg`!~oAS6CMe26y9qSx+^|(Jxs<5#gPjRSt=IMzk8$cX@b;0ym#dbP) zP!_7LeKuVf_`YDlnzFVv6xj@n+(h&tW($g1qh>^%^1-t1&9mdd=9YzRJ&New(n`r> zp(9(u4F9w#pnAQs)IQ^-Ia8x4r^;^FD3`k84&_;@>F2H zLOc(vsHm#@z%N(Xp7QXLhgk`C7LeLB?9T|Gd|I|-P+DQDwigN)Z!kphL)!vkQrVcY zJK_bX!N|pF9qJ=iDo@IWkI0>xKFLqY|BF}Fy!+s)n>LAu!sNr&b$z6pg?yc$hha;d(KRB%t%&+9MzL9 z>gg2gQEV$MtgCuLk-dPiDUapbHJ&jBS>5Aw3z-Z;54$3xg1g6@7cd_wE>3&9Y(B<8=>lhhT0aYlz=8~s&CYAhysdYjB{vr%3CB(wKKM)vFP z)~N=uH4zHCX+|~&0*p|uln?n3Z`K;{V234I#3mbYL%F4bsdzb>;jlyFP+VR-=iyEU z?rr?*X{edFruK^6>1lV>R%ZI2n$*$-nj-qm9}Qy8=&~PJo1#*Y9?>A~nuv_)WojDp z>PMxOS~!wM{y|bg{qI4uVh#nk55vx^hH*$}72XU<1DUDt_(Ez{H?P;3^QFpXWOiaP z@N64P%PgKbbPm%Kh4rRzJU>ZnUOONFC5nRaraZjP8jXng6AH4*5lI}behdRgJ5ANN z9PciJYLcKp4HXUUJCn0dW^;4&9Ye@t2eysfQ5iE4;ZRLrw;h*Dd{u67Ou6gD5((Pl zyZj?crh^v}!$?-8RgyE_e720QPk%}sLOVLYjux5HDbY+HA*BS`%YXQFt$*YyI}p99 zIZ@(%c>OYz?Yc~?za&7p8^8n!vnCzF-vPhGpi3p_s^=oUWoh74ENspo>QB&%4pcvG z6kD1!RNbyPFYXl~@{N;7iG}-ADuVxdyL?OZ%AkWsg5y3&^UXtG_TcYNh zBi?9!J`X)wPSv|hmb-zVpu#Xhoj=y&g6P9*?vE>S-_<%!-=rmM-aTm+ZWIKa21NKp z=Hrz)RB=BxtFmkS5|Z{3uBqwo84iZ}JatEK+Ev`SC6`e^KHR~&*yX9e@dF0CQ&K*M z3VbY-{IaSK+zp5hFoI?wjR)c@2ER8dT(OQ%mbpl-(H`k-xR|jXlOu?dzfc#!$cnVp zgn?muAQ?(22UlY(AG58gj&V>@`|oVYrf*JzwySB95~AFaGgWHmGfuW(W#sKgw? zOrq-@aS&;Sy~jQ=IUX+G)PKg73`%WIm49X1LuK%o$?_}GW!2ORA?Z{40%CtW-Rv#w zy|3sSCG+Ql!~^^9WRV`pt=jC$5cw*$M!|!&TRKSGLp!MeDtb%V)>8S-C+C~c(6FudrufxPt!{z&q6oExD?Mz>XoxSZJ zF}AEJ#;lszjU6tRQemm|v_gt}u07hn0OA0Mm!{}F0Vf`a+EdYJmlQpA9x5j2Al=1{ z%(g>cXHO6^PnAL&QCcDhk55oqk1Ok3N6*ol_$Z8iRv%O;nbnVhht`NorC-u%KDz%s z3A4}cJOok3p`i~1i>YYDMwkGIcVnA@6$*51lRS!n%JuTT;$Nt228j3%TP}^O?elXR zkmQ6tJu$cTWM({86Zvqxx#iFINb(c9pvz?$GyFaHpyD%+lrvq5CZppB&s?Uq=)5Fx z*drq{wiEdde;G9*#v7Q%8|5$Ue~!Bo2#mxB0B}iC){y0#l!Rj6geE%iopweY*U)d@4Xu4Js8%Ok z>Wp^0OfuME3LtStd)je&pvuPpiTiNJzBv@}%F_IpT*0%&c`k{myk=c)G~Ri^(QEUy z5YlpTnQY}gW;EIF_&Byy<4*sacF5Ypy3pFUEZ}`Nym$P`RcfV%qH(=Z%}h;?i-7fP z&c}_%{}p1@lOGNhNyaU!%VQ{qj|FJ@*zpApco$YXpIZ_n;sm+_=3W}Jq8x!zEUM@E z6N|kcI{LTTG}biFgeR;u(-H%|fz8$cfG(cuX~G7Zu}{&D{Ygfr5#t$ko%<_qk5;j9 zboE^^N3B{9EghE0Y^BpC_qv%d~!Q!3PeRAY5Brn zD`^V8ye~CX*lrqzW&UK9E;%Qpfnm$4DqN9FkL&3Zt@AuQtJ)D>KJQtsA2H;eO_mP( zcvmw-GbX(xIiLXkmATWIZQ%Wt#h1mVq%KDz*2dq2v|BYnoqcc)`8^VuX>J`RT+$IH zTV39WzZUvNW-K)OU3KlWv1EyLh@JXCP?GYM37bfW7_E9*Smkz)nbs(MihNS}tP3lR zMX4HMs&{yO zq$=80Q;tbOx%Z+MB1w)o*7SOm{E}(pbcku2=X{q0RVm z&1>yA?IOfz2G`@kF?bdIPNaKZFFvWEF1paMhWAiYD@b|8lWIr-LmX0!gnqN1?l7 zS&P<|5SEJGZD0^8`a5J(>v!qgM{WECUA@l)cFc#%f^9E}f>1cs*n5tr7aYShp!YeN z$b@O12kBo~@vYBsPx#TTpKaFP?9?*#Z}I}!xfb_Dy5OcwzJK@xOBjLw1cC6MN?|J< zA-@J7GJX>_-22;W?fQRH=)YgPv~nr`Ch>pU|0bT}zXAaNAC32sJDo@FH1tA@AW#Bq z|Mw9F|H_n~w}jne{-Fn;|10|ETl~MVDF4%p&VRfhfo1=s=F;~`ov#BBo@bb(ec&%6 z!GZD*o#Y&BDEXHk@{j(%TKS@k&Zf=Qb+3)pe<+JD|1f2_?8i@j`dSV|{~%Eh^*_Q@ z{IyU>`}a)Re~kOTr|2~Qk|6%k=U(f+P5<51PuvD{y|-((+c_(%LS>S4f4C`HUw3zU zfDoCGeG_ETp=1GTA-^yW;AjiWiTKH zFSo3^HOooViIpY?(4V$mO>NsG7;4>#-!%!H@15E|ulw!$9%lK~9Q#!o~WAc*Q=gI$V3fhO+12OvcOvt|Y91@wMHR`KTS( z#Ab}F9M8P~ug$kNi_v3sMZ2u}pLD9D?>fNxuij+U;iS%Z9DZO#*pG5yW&7yS0t%bQQd z4Igu10RGQ147?8_q*hpxtP{}#0g8nJGzR?OvS%k*jyRgDHBMpIw{1-@_k(tz@bgyA z*S81FwUgIXH1icLRjHvBt~Uqv;Eun7d^ojOH^UBlaMt>n8d1fJBl{SNNDcf^-o`{@ zXvN1bogbFy3d)Jif2TqMdA%>}^pv{=3VO95T#Njc5NQbtN0fE31!jj$(Yf6Bu*vuM zHRG+kSHd|+SP-cQtdSb>AzT_mpXRY<*< zgJJ_YQJ9i_0$FeGzxJz(M;bv_V?C1fk^tdd*3gU`o9>J- zJKGqz7vHl!ZjrqG_PJ|YSL_%oLwcL{*&Z89SbrPhfAhLSvVS`dUw&>QX&-yJVSc{0 zlygd)N}!IA*=v<6kLNNDPgMVmKI{2D08s=FtJ#vRlp;eEiE>$+*m_rnD6kJx|8P)j zsz*aqYAxLmmy1j39^<&*1RM+zF7V8O5;-{fm+3%$?rpD0d!)B3A}ThBfxpI!ZA1>fC$sBDB#zu|MAp@N*F_l4inB1sEb!CAA+ zP?DU(rJC7{MwRHr32yBlbOnG(tV;f^9=KMv+$$={TE%2WuxB&rA;)G_OYo^T5SP($ zh!2lzSSMa9{?2{sBKP9Y=8?Rw%se_B5~lg7cVan*e_k%~x1Vy2<~J8BA#U)dNO(3c zzuId{H%*0D&pO&S$HW5odm)JA=-l4z1o_>~pS@iR`_6T=o@asH-rn?G7Pc)GnfQH- zD|1;iD0dmiUA4JRaOIpJobS60cDVUbe5&|A;?wr!vr_zDh1d{Ai!zV%H*(>kt^BTX z|42ef#V;tvF?@`oALhV|w+skDj6_GorY50TkH4|B{HYnE3cB$2cCcyPW8F*!t2EFn zrMlcbEza_O{&Pc#2Yy{idh7kT!N2fKl90pZ+u`vx(M*=_r2C^)lIAgsiO?aa_|Rhp z1|rM{Iq4f!S4?bKEo3wVpP!A*bFBGWpriiFG-~6j1YWgVuNXu^k14lobL8?ZF>)E= zsa~D;s3ih>8S5?cP?3mXR2FE7NBLA2`a;lCa3iOt^0wj;$yO^ZJi@E@9(#6g(D||? zzrE`^YaI;N-Qot)(k_;#cIV{n-NZqafzpa`XJT7{c7|pQYFw`?U3JrgEZ?R!%Z~ep zZ4-OsfJ4nJegrS&0f^M~z*7=Qy`3Pi;mPf`sA+=U`BiR>bUxx1nn)*2hPLBIgUI9{ zyG5c;^rv;yY?#)FLii+y&r#xQp6%LuLHSvZ27WItcLgLxUNPGz@mIp$SNe8$!rCXn zp|$einFAmlKn`7MMPakJHW0=fa$RU-rZtuYPej`Q>cc=0Tk4+Ll>Q#~YF@*}lt@L2 zhDZd%Hb+=y&FTDPzhU_F5gI^giNel`?K5;(HnP9Fq z)M{Tnp@N-bY=VWapVYT&U(3;&2A;VO@Mkjn-jxq%BomojjjcwaF(%j!lBRZ9o|7eN zJ(gh$wi0^6$H4~VepcEbxP{*gld~B%3%4ZC%pfi>2P3RXl1s)BaS&ljMkB2@4&LWS zCUT9EL*?>(ZcT50*11p{Z?BLnI0hFiMhmJ$j-5HJo#Rbl`;=Q(w)A(L*v_RC(G5JL zFuxQMkb@);`(P#eajiX7c0GcT0}zpkNyKD%G9-jAsJ~HSC5{Ve2p&lY(Y~Wz-Hn5H zHHdg`A5%?$Jc%^QUqM4x$ovUdP@Z^2wM&7>@72VH=7R3SopU*fH*o%i*ueQTUh6XD zb=uJJ^TR~vr5(5We98x^`rV=Bj;dl3U0gN=fM+;bo`g1xS%Qc*!;wTvZGYwaQrfu# zVj;y_@9DV(IDmXHmWBNg1|Or80Wa)#TNu3(;H6Tfu}2CxCAzWh+#I5 zg|@XM&y2hA=eZ%OXutCYYXsYS9WMCUmrJv-p@o`br*i6Fu(^inALS^+2fIGQOAUV@ z&44Y<2Q1VoIoo_hajj67?iq58X_HmSUc^`zPpc_Z0PqKTy87p-6=6xMFE-Qa?BbYT zF^1OY|FYnXR%xO#q&cN!->Wd_@FdUdCCl43)c5gp48sSXK6T_cLZSxE36%r3&s<}* z{e&;}jbh#{o~ezj_7IZvyBKvFpGG3O9F~mMViaF;tRghH#*Rm$@{<%#9B+BANMOS4 zxk0m+j@2Ktl zyj&XxJKYbcR|h)Xvz~^X7Mk-ueNN;_GGfs$w4n~mLjr2@`a|XFqm}#wxeC{lPD!X1 zQoxCPRdA%M;cq=*H^?Ujo&0H^^5tesb>&xIy^=A$D+IlBx5@+-tkfw@MP4zM-{9>cs zPd_j6Wxo*5Q2(fus9N-tLVNSgG4Z2=bR0?j)Uo9|$g3{t-6I#}o| z<;IICj|(%0q_z?kleSN zRW+afb)ntowDg3_xb9XC2Az8~r%a{h!a5FdZ7Pheaeo|lgRBfFmvURrWL*o}{Ou{+ z1+(RRPoAgT${FSb_CC2^sa+wW8_|im`lK%foC|DEl0Uu@Cb(5c32%^{ZMVJ(}64hH*`?Grizys+jL| z6HeAKa0%<}P9XXiG@pa-sKF7f{Q@<0ttI=(eNUa&Dl7|biUJz0{XfFbl1xl;JzCaAd+Vsz(IVhV*5$M-poLoT`WF*C4 zwfhlT zX7=t~77+4fahfky$C0cn9ZrkZX}~h84!SHP_qh{Pez7F%$R9BjvE*frHQ~#4=`inR z#y_2hNQ4z$FxlJSo-q6v7|kGxpH-ZVYzl##4$jykbhNbu`Iy*wu)*O#s+0OdT;_OA zkd)mMzFYFSJH7Eugi5|N73J8_93Dr$DN5^U^`anhZK zY93p1-Y-75pmc#_S(@SiWeRg2U&&eom#2>G0Y8cR+YnHi|y zXvV^ivzB*U%KkQ}cj?{-lu0@DU1PPPmqJSQeTHiayiwmWNaTpQC$_x_{iSFpb?)N# zB>j8Sm3`_vY-S@kk9(}1e_up=!*y03v=-Y6Frl};E4_f5GSkxHUFx9X+xQa4ulY5P z{(@quq;{~yjHr@Bn0Wp3NZJBEnKWWL#-LY*(y8(gB znrUh9LzXoWa)K?vCG(2mg8_Is?RTmL)DO6IALU!W%(|dGd*Qv6oR?pu36v|w^v`(o zcJXK5b^{f;pD>kZVk~n6b}UtgL#Yz>!qe`C6NC31)D5f#tmB@#G5CH~v}F%_WSr_1 zo%t#d*uDc!zUQkcV%f`T=43r{kA%V`MN78L_t#m~yv(%N^^9?F6vATYePF>F@dks+^a+qS}5o5Dloix_&s z7Ni`gqd(vDJ7Ot5PP{=?5=e$+vDKJX{_B9PXe8AJt*0S<)CY5(j+e%Oh{_=2WSP{| zdKc46sqaZj>F#sD+n2qU)6(z*1n~gbIb2AE%#-cIJoG#-s)orTpJG@}G8qRKi%(Gh zWNxu%Te~mSyqyy_2pUi{)l%RjgQw#GgPQeoSbAq~huQF|KtvzM7u7uR*J&6WIyRTsw`bWKlV@U}DvEgHp6m%9-m_WItP= zPh;u(t7Oh31g@{~kYa?I{IGFe^G*JKPCN*@0YH)lr12w7@||prZoq4GE{}azAqG)a z9WERYGv#64j1+yr*PS8yW>PsklDf6Rwax`n0kiX#k%oxf^wju~yF>4WY2p1uXZo2d zhP>UInD53_1zTKD5d-#%`fc~jo}L}C2aqLt?3YTdybXo&fLdB@u*)a3>J;CCj+V=v zh0DHSFdJHf+rcm9(7q+Nzb4uaqMEdCXi5 z{qNP30*3KGrB<~*Q|La5ygt1IQK##c{%tL^EivdyZ(c~D4`tysBh0w#?2OtTmE&;^ zvpX>}EYgHNi6I7CxkO2Ebzac8FB340@1WiRRp_4@ckEq2R6NL$ zfE#jAHRw>+xmxV|_ZXnba>Rt=prrje^^P`1mTjmRc;jEv{*`L=9VbBx-^)W#xM8({ z*vf+$TR$e?#D>AaOJ6iV#&kA?h`?cN8c$+tRzl7{neJVBv|yR>x_eiomxSS({3#my(P7iB$tsyjijIO!EncWhAwvYG-YP5$7%y; zVL|vg^LsWGX-)bLL}p3Yi66wghJ`=Yg6=xomBr-)|PFV^) zydreAK5OaS{*Flbm1B^8mq8vg>C*E3%5~md#8uWKKdZ)Bnr zNR03u@DEW|=z!Y}l3qE}={x>{Ne7M=nm&i+e(#2H>`2??W@mkO^vNVr5zZ1%{g`bQ z(|NC!8$g!2IY-R${G7P5^|*JU#f-m(_uK&(@s+x6Dc$8W3bz`mC`|# z7OdX2Gkz}`pJBpnjf^%Wns8}CR#z!wC}V$?C%b)Y>pVcAuV zDQRGIt>>X*4)>>FYQ7N4%z6+ZzSN%Onp#X@)?iHm5nGholVH(D-H{skaoJhR-gR25aqY8rBA;zezSkH1~DTdTzjCnS0-vz_#-Qlc3T zERPdo%^6j;0$6>b=aU^Hb9Opk`QOa@Tu2_L5r~ixTVDzhW?>Vl?zQAPb zuH`^oZx=n}zFq|vg;+;#cQI_?w9V=yByg=0JH{G^I+s%uLAY;c(nK1Dj!5EB6t702 zysc9U;BhK)ahy~c3KRp$-R%|+{BS~2hwC;Ytbta$}NtXaac<$=Ets4Q@I0T`zazK20v>q9DU&z z_Nb<$RE~bv(r=;o8hRU3xO_xFp>YPCOBhh6TIk}yE+tVWmF4YcC4=QM6V(Be^Tjo^`q~u}Me=k}|-tZ0*DL8J4 z3C*l`VeVGOLHI@dsh4EPf#?7>)__Ff%~MxcyBitmB{c5 zfkg-gmv6<25GtazZHkeoAS4;2lkAHZT4+5K%(kY3lI%!HW?A7G$FI(X* zL;msQa-NtXT}1VD`{_)};oC+*jYQixzGnD)>PFm|QxBdX1L20^xi|G}iiE|=i8h^b z`Fcyf>~w)M@V8`DCQ83-j4*}g&_;XV7nAd*-h_e)#?BmKT5_(TnrR=q_Os#^%H{}A&ZVdOP74@5zkmxJY)AA2zB*CrGIcf z>RYQGrI!TeJC~TTyfdvLRZz+FW96Zf8|!y#g)LYBM@T;wXr6Z2=>>&;0C%iIr`a+t z!$Pm!$-%(aq!q^dFsQ4b;Akt(wX31HO!q6;8PsJml-OLUpaUp*reWL?EyB)uwDi=q z!s`KF6VP}5B)1J;mfF+7!+hq!!aQO>aXixRJMiq3wUI$*oY9dx>Zp1s|86XrLKuor zLy6a+$(UY{;15K|aQ^sb=Z3BZ+uRF&-nXT!ei2-AI35n%pFwL?GBy>N=M6+>0APu3 z^^R0M$*&88?|CN9P$C7IRtWFYofnZcWHJ{dd%|{^a*EO#4@`G&&7=9%qXnAOSUesY zj<@7<-cZhzI-?r1sZaxPrZ7xVedcb0b${j;_B8{wrsFLCZmOux$WW9ot5!0S^pBpQ&0|K!>(^t(~vXV<%@`#HqrAz z-AHYmxN6?Ks&|qROLM7uU+_dTFB@_4gl z;XKU2dj(zUoMkDUXUiOwUlCM5qm_M>TWv1oD0}jN%%%v)RUEB{?2v}R)%64vq(0KN zpNqxm(hM*nnRdK$ zJcMHQZBa+>ETZdbnLF0%aTlV?$YQB@=;p)eX@^aZTfx7GT5=&#>p3<);BuB3Rgz)K zC{=TmWjogZ7LH~aX~h7h20Kd}S)_(%@yoNxGWqu7%*~8^Gnq=iXZi?5KIC0$FSLHu{Yw$Hmdto{V%*R4@(>_u2qx7RG;4*!uld@En@BYAsTOg3A@;3G22j1DNQ zOkFlj7F)PfL3(d!7uDY2o1Y~;GA8QTsL(^mwxCK;g$??!w}#r|G7|wFxezo3Nf8T( z|2Xg>j`kBZFx`yk%{&?zBF3J9w%#A5fiei6X5pcDPfX185~2wIi5{u&B> z#aMT^F)R9l;%1+fqodkQL(-U`0rf$(FlNQAe(4~xoQRqz`A1|kKv^^BZXhJ<3&l6_ zS1chm`g)PI<|adsLR}LhUPp9IfxI&e!_cwD`j=)~$3&ijl`Bp&X3;j%)JR;$rG8*? z=m}Mw{ID5ZDy^<+eYG7Ok~zaYjnlMgarWmnwpIa0w_6|Q_QewyLz1>+AG$?4@^qWM z)DX!vrxP?yJwUoxQ3~B+ELnMfQQgzPXJDBP0f=yvp;QXZZyJTzXZ`4OVjYS#Qi#4` zqoc#Z!SVM%RfPUsI_VM$9jyj;HzlJJ4MX7{ICcdVA0k?OhPrkbj@V-Q(O4z#j4nQVG?U#N57ievZ2k`_K!6ikX;L}tMuh`R~I_YZ@*@6ON0fF44<+N zn@Oy}TU@tUU6y$+SErtC1=ZC%^|V6kC8E%Wer2O<=Oln!5Cu&UypSCf{3JWIhAS~Sdy&ECE$vb zdwGOcAN~>tYKpW4XyvDIhSsA%n0_!~z|i0%X&#*XJZi(z5-KZA@ie@`-DgCpwdBr5 zBc=#2eHn9*UBQ_)W>Fv}+$W4(=km!DOWsFs%HAKKrbUl}rAsiv55S~7lS18=%|-bw zgoqxfo}6`O+z?rOcbHy)oKZuHM$l(;={&W^QiwgGAtx~3*Wt4FsXPx?eBj;{ZTAXe zzF%jJ1OA)^4-dhKVUr^imGi~XM-qm;zxk0gPO!cwzE3dud8y0AC4Aqhia!##l z<7?d3KQESJCEz@^4!j5prpo%hG=5<~S0G^LYbrBupzHr6i-U{@p zz9a@f9rNC{74B$Rv=B9s83a+I7@zkAeGuUI^%g(yoof3&Onzydn9iqtll!$BmPF?9=2JNv^%Vj8Oc1Yk7+QP!*oEvmSm73w zR9N29-_l`!F=ef$A*<&aWox&Cu4J>Nz)CcR*AGPuq##w6 zCOk5MdSJPr)zLBfel-bGETq=orIk?Tkx)p+N|~;jm{{Vny-0OsL`1!GL6aWEkqA&S zjdt~!cKyQXD%gp`Oaj=4hgj>ut5Ic2XUM2g$gy?)G$AN0GwXCx*vC~Xn^@M;KnGVQ zIILGNLU#b;NV-GratS3>q*#%t&yp^xi772yJo{(DI*lj`9*_BDdU8qbx4=rQhbfU# zjoNf?4pMsEKbIMz9c2NCKJ(_}lEE@=G@jnpj$^vn=H^u!XHN>z{%Q%8t{l#9465v& zon%I)F%k)dPJR6>3Pw=5^fD5Y0dl7CuBRW-%3Jx)dGW|G!ca5c@xhmH?ERQ%5v}8^ z7dhpp`?=J_dH*t4_bcY+X7wPmUP;89%kbJq*ZcyvS9CBqKJ!~0bI3)pvVBmVNyTn` z^Gu(*70Reu?i{q%X4oqiNC!!PzLTbH()8OtTE_F`*TTSMkKYNVV56a^Y-anrBJ#5W zwCy3gU$EGa%K6=}HsUN#ycJd(L(D$Gbg!Pp_u?R#9iDK-hHpRb*sf2N!UtyB}Br9q+jH0c&feYWI4TPTDi@+)QaBH6#LZQEk{Mv&QKmG|v5=oib4VJ*P6`_R@ z_azbH+i{6Rl528-JB6o@nfnRRx)cisOu!Ie=#r7OppW&@)Qr&_O(;RQtqZ&9me;_$Beg3} zRPcmfgrOv){5YEqDjr-%$JqmHrDDC-@b~BkM%jw%1p>NQCRdoQG}COY7M@DJg|%Y@ z&vHti*`LjCApZ6=yv=^p!4_oo%<o_V#fvy7M$#!fjOW40AV263HS>MUE73dDU)f z7MD8XSLEPT&s|fExw$+VVOI=KG_h1oZ3GCho%cEDnzdwWrhUE1(hzKl?}fhV#pCm+ zT_3JDfd^QIOJ^hMpeZXd^v%0kX7%=)(FGqR z5D$p7sv9Wp{S-lQKv2Nu>2tUcuuheXOl9}ZB5D7rrW|AGRSZ*a$B3oafXfytPt@t| zosWFJ%yz68ks^hlA$c7sgO%kZ6JHAukwIFjp2rTxM^jFf^Vb@SErPGZ!5ANQzeY;g zU772uEK=m$rx?Hq6BNK1o&Eb~UFSX`w(m(s!hyGyDU}RNgE;F0zuXYCfdqFbW+{F@ zmTiMR+g*jB3;}Q1Jm=ayHX1g1QTW`{*M4{yu7~k`B2N@afGi9wD7ldFdXxYVFd;VV z%t7#Tv=Nqj>>ZvM%bW|x#+s*5%b+DcuD)sH-YLN+7|EhF;M}0~=!_CU1q@Vf z84bZNIe08L%Z;YP!>+x;Gt1+b-hLy7EoxS1kzbf zsQv4RZe>JrPvxlv>N;a~&sAZ6H9{Y>CES{uD3d#eH{rkk9K-|QtL(DLIozVPzj>-| z)+#LF`fp_?9LJkaS};!)MnKD^vgWjTu6w@46xPcuKuho|rglI<_NkciR$_mhV8q41 z?j5h&0?v~l*xHy0Mhi54{Mzi)`C?ONT4Tb)_+y8c9KAabmSiXW(xT;h=g$j`feLo5 zFY_ng6QrZ;X5UAH0&VPY>zlP0zq+@bJBTT#K>m*dLWK3%?GZmn_>TPt-_nS8DGimm zx1PnC!pC7_aQe*LD9C7sb{9@4 zxKb0#b)q1hJCD}oBdv02oj1s*NMQEHC&_4y(&gy532&Z%`2W?DRPizlWM!~?JZ_Wi9TRd+v!H@BP|j=Mw1%1``F+y#v;a`@cC$Xd#iK+!B>$ zkAHq>XMUs0RI%Ka!$g9Qua4oRN740FP);d2%BcYi7=iLVC3<^%bl=H`EUJ|Kj!*Ak zfB8-SD~yNbm<}k)qyfU@*+y$IE>U;okB)jeZ&Lgszom@nhrw03j>CcW(c>5<__-DK zh?Dh78;a3&swHa~2s+}%o3{0`tT|H#Xe8HB66h;%$V zabcCqEvXyG%LRN~Z8Ue$gYs(VTJNw;V?>?R3A+6ooq=8~cA1fHCK+!H)p=M+aeDb~ zXx-kXx_p|SGn1n|5X&+lkWAU>vBbVYM1*ycOA1vg=2r3eP3={PcEsA@xczzzhQjzt zT;zw!y8f}|pRRxDz?L0JD6x(0WEWcZ0~9%CGMSOhVyDY-2mh_$4Q@tN zqkJzyr9OZEL9v29p)R2y?XR7d1UR??yw8dX@fGKcNZRBGSNbb~lNyA~`0JL)tYDuH zqWGD;kzrM1Q%1iA91f4~`)c*w9)*>?Jhl_zw;7;N(C?wjb$tb@K~QD&a?+SsCJkIp zs+ezV3oRyzCj@=mS&(mk4>j5-c3>r$TKG=>m8Z&Ip}*0}n*JFRRArg0txp~@!`u8`RRSt};FHQ6o&HtMH>K3-lG&s2%Q1iSfBg^3N zt!zsUKAaoKQI9HADY^VPz*l!uTOG{njB6o5*H!*+R`W!q(3`1TX6kAV@jebEvzqg7 zB-y#3;Lw~S{KM2)Q^fxTF9KWavA-Obv&al1@Ru87!?OrW-yrc8$mZ@u*3=HGbZ?};CvsIF0-3YJOcf#2Ay zao0{9vNN5hbQ(%?6jbCHH83B-)!h}Cx}HqNiduWRp1iw@$_=pV{2qcQvNaiN&=`a19Dh(Vq$@^)CO+znyrl7d}#qrs&>ue`$2)emzpv9R8X!;?AHYn%n<+ zVT81&gcsZOd)8U!JZwVBbjU>#ig2D^q6J7U(*#tC4y7J> zh=>(tv(Z2B0TyEf~d5#OlD1N3p%z89bR)F)(UW~)} zSw5{Pr?3_O3J%18F~O1z3c3&6JOAblTASYgjSFJHK@!HOIyT1X>cmuVay^X>@V{E^ zIHXBL@L)7|sd9{k3s9mWd5Y?4Qe{YpICp=Q+VeY{n{m~Z{sfa~dLA;;^LxxSIQ|p< zRrkGCj&sX>$-3@{z?8-B&8?UEuPwT@iDc~8A-g)xG0w03DS7N$T@RECJoXmJ`|X|= zU-y_jZP7av^}4djckvNXA;kT--)!27{U2%Dip51>*)w-Z1CMR%8~pYN$Lh3;e>$3R z?qYC*FwcVBR)p%Qc?GrLUcP3FVIhEofpFCaD@>kSElmJ>L#Ty|muM8-mD{)4b^SMa zVco;`T(U-DW8I&ecKY7Gt7|Rst;LKqQKWa*6a#qVrQYUVOD)RjFu?4xg3CepJ}%4q zuaf@pev3xX7Ja$Q-uTlJ8DvW7`MD(Z`Ay)BF1EtM)HRFv$RF z1|H7SamhLGnKdP>uktHI3W5hVORTG})f>=5?~xSD>}Lc1@x9ho^#X;*>HH>gN(nBW zd6O^?qoDrbpW(@p!FY?Q*!v}fv^F<1V&=!j{#1|q&yI;BhSc!;Jg|gTuJnw>?b%wN z5j9PiVReSZ;$bRY5mO|wOUKBT>-ld3C4=CI<3~&a2(bi0uv`0Vo!tHgE5lXx$df`b zuYIw0><0yNS^DSD*Oa3i@;s1Kpzq9`ObQz4PGpJt;#_*9QMqyR^hNZ-SNN5C>-Bab z7sIVJY5*>y%g@{K@2$|d)vHX+FAai%I1e$gOX1~{ykKpa`DJ&0ZvP3-!4YGUyUcN` zWK(jVQL1^*qNq(L`+}uCJtSL3L-!VP=ZWCWqArrQNH_*3L@7dCS2PvE7ahZ^Gwt3wI zx($&Y)O14-kM8;<;ZA2K=p*Cie8hy;(@69bERs>j7wF(Fs8f(sjl}M>Do5YhbGoPeKf!`}44N_4@VI zx-PHoYq^~NQDiZS?snOn7D0LBr`%-?hq26+0g7s+JFo)7J3%2q2gR(nBkEW6GA>8= zw7n`>?q>vAAN$`6ZAxS-0m!Z?aV&Yh0sVnK3Af>zX$mErL^e|E1uf=`2pV{5Kat=N z2H4BR)y>z+W{^wFH32%WE1TyXjK12)-M0}npY_MTs#e?x!du0*G&`4*qZiY-G9JX* zB7gM{3h%u~$|anO?6*JF7ab&8*xTJ@l=${}^5y#YAlK=i9Wx^Wn#dV)7kGPF9V(as z+q)ZiHRRb=Dp5`bSd{aZW@#U)(W2a^qyKavaQk4o%wcvvyH1OieHNSHN6!U(JVVSe zV~a0L`JayiF6{=Sjk`+D( zVwFE3$vkZSTr6hE9(4}}q6{VAaL(MfxWzKH*-E04)mmYxUiP~3wzW6k#@=-zCfE7V zOfkxH$Xt#|kIT2=n(Hw zfYpl_#XKD(LM(x&gpS^A%Kk5v#h>e5NH_0GmGITr$Y2#Ef@Jmajm_?_9R8HYjMDOn zVHEAYw`fpEoDZAd79f_S4x1eNvc9Lhj4ZG6-alS)jTBf)Jwkk2B8c?k(?tUX6PS(8 zwWNg$(;~oLp6foqOJyG7nCKg)A7Q$G#tWKBE19f(>Z%jInPl#o*i&>%Qe$RdjOMO* zE1M8=^Y=RK`3b zV$#VA?|>rK`vVuo@;-dC0VMmdN4Ovv6tXtN#F`Wb-MPQoMlmhQgeLfSGW=Zw3~=b6 zq>U-9U{75g3Y)gcn=XN-;g8im`D3YVBnCR~5PSP%zfon-2A9$6P=6*~qVBAhIy`$n z_XrvN`KZ5>r@;}BU;uAJ>!B)AI0NUo%3-1YV@nkljO_GZP4C9-8FeI*=bI<<2rK)= z9^n;{Y9qSN>KmsiVV8H*_i#2u^Zi!7Y#@wW0L{U!MNsO{+Opg;c+$25B(c0`hoZ~9 zJ1Ke{d}v391uj}8h|_U9JN{MlEw~fJu=#19x)LJ7Qu5i~ZHpq5L2!UDYv+2`8JVak zP5@$}f*N+U{6^|ee&ZgfZoSRS^r^y8kIamx z6LnM;+>x1R!H2c}CR!qhmUBg;VMFY1GWW5|dE;$wZ`^OfTKe_hBBILEzsH{rJ%dG; zWN8f%$6hp|qiQS_98&21NXi;`FBuH3M>1q80SoGUc{zh_m#5kDa&jEglG%v=cy_Hy zPSwyYqYVe6Cde5-C(4pBuEd!Y`8JaHb|LJe(+y5PRj}@^B?^(z#kHJjvnZ84SMM^$ zXt=(i@p|VYHxPc`;%b9nqo<&w=o%sBDgZf`y7g>pb9I;yr6+3V?;tsWy_V{|VQCme z_XlJA0IaXQiKJ4;6z%i-E&>nFr(L(bmx0INtki~*HR5LJ0QtdPa9HI zV6RD~mt*Q~=%vjc>9ohOlx?$@i_D}Qm%!84@N!LkQ0s*7vu6BH6&FK6_vx0lPW(UX zQ`*#lT9sVNHRz)I??a^DB6GV0#(wWVMO=L{+?Lz>i z6=Qxkxr{=Cn-NH+lrswvj+o|eo^7H+t$nla>b2p|trRQZ4*K>$+6A!2XA9pt%}>WNJ`uq8jDkW zH$tm1m?*D5_#RQGDSHVKLBN(W+L5h~9ASsya-jx}5@2+yFyuwcS)^G1E#nb!E_Cfu zLyI2|9AWbOf+wU9gP>{p zzG=%888!HviEy^b-|B8qk+CBtRLI-He*5JR*9a$~A(6qjpuCopS=zP`g7zO0GO*0_ zK6oleieiRAHUKO85aPO)fG4KT9?K}&Z6o0tOfJ= z*%ST?_m=7Vf(wVbIuYPdWp$Y;F7`6Jc_XvGsB(iSCL_dgUjDq`S0_(~l->o&R%OuT)yt=}#Yjg7pDGw|dV%kzs*Ax1+`F0>4$)h}W!(*j+4+QyLr!q?0&$)L?%Iiq)evQ*QjCeGTP zf9)6LaNtB(Y%mx-X4w#9OYpIJcvR5d4OIP}ZYhz6wY!$HFwM+3RWhJ}^5XuqrW~o$ z4k(*aTfnRVMJUB|uN`I&wbX+Fi7;6F%3B?umwjA%$(P{_p^i!26-tALZNZ050(qOj zWn%HOImA))*j#YCOfU32Kbt0|Tc#(%?ax^%JiZWkf%Ei-T7Sln$G3xVW7fh@Rim-{ zWCN$uL1Gc|IsLq0reMAjA9EF360KYQ>|80e5r8bo2o}fai1jWBO6W(Peq4U$)Epv| zPN(aXkP(<|MGuk@FG}9Z0`B1N+M~v@-~M&8xnU{%V(ETZ3o+nK-T^2e7?{wa$nl1a z=}nriT-cEufE+`NR`MaownyG z=O5LY;mf3>mpm2SiWu21?>lvoJ^pWf$7{rjyP=`>C0eFsDq8cCID>E+&Sb#y`zZ)4 z3@(bcCTeb4i?~uN-%Uft3V#|OW_+Wl*OGmckCB1tiwfa;p^C5QBG*Y{$TTV7)#;>? zlC8+!6?g75Ozl`u$Y;$TvTQVrG7M*mWF2j^{>*alzahCk>8%n)69Qw_p3p=<$i{ec z>xcsW)|oig-khzz2nuot?axsEmDu6Q1?EFm)vfRsA{dGVmf1uEk2mcZ>Ck;;&21?d zQYxzWh1F?v=kxm-H$2tXj^2unE@R)dhIQ5eSbJoZL@2e0SaQp7(}+FF!0X6Ukx0{) zt&FKUM{Wv2A@-@hYaY+4$QU9j3{iCS-n|Z17F;T)g7ilG>>s2yeMIYg`ZlNTHK+;2 zmng%j0{ID-bk}QBG6YuN@EKz48cNbY!;Ig1S$qmF1v{R%s1jFl>J$Ph*EO#m;XsP`Vk@rp>Oud&Nnd^HXI&7M>wEh4n-1Opa#Kdp7G9*;G+aK^; z6g4=De;>{{f6+(HExdUoz|35Vq~{inf4y;$I|lTloUq@-RyjE4b7UzEn4fFN>fBl` zjv50=g`Bf~lc-Bk^8k1Q%UwEz!F|Q%WCCqunW{a*w!1&M?k6|Q9LyQYu?RveDt)rH z94G_#af|l_*YZnd2Djt%X?G)IskDhyX?Gs6v&`==t$4Q%eE$69xtNDXTN+Xtx8&*G{C-(EgP_ zYQJv3jB|>9mw`D)jX3TDFIrPk&QEF}kRseh3%}Nd-=@evd#AsVNt?ds*4ZZ|xZXeH zeYP4wd1_WmFzbsY|3zX6!3IV}0NIil?AISoF;gaO%oNMxHJlMBu6rs4<@t`)7Y*_F zJ*@OUZ8NM;F)KttGVx1(;efHI;`g6QS9DEyyu%^#&~LAvGRHKMA^)IHXFk{$)LrzsanlGMJ4r`uNvbKK zmUKsTi`w4Kk{o`@;KrVjwTme!YEUuDIdsWK4#64yg?b|$ve!`oIGcj+aqAFqKEAs` zK!j!FWwS_p+N{AmuER93Z7{b;Q?5gr5=`o@t(GV&cXJy(I!S5C1$-8VN_UL>Y?eDq zx!@p~6mHp73sKkj8$TcKZV_MLO(k@1VePx0S%@0$*22Had{q|l_9zL)E~w>Yq%Exf zAkq|%1-vI)l3@iy(xqB_?r?9e%g^pR#7-NpOKYjfGAB|b7!}7&yul3M?R*Jx>%j_B z&3ehVgO_z4KwEVJ;x;i0sgr9Jb#2})+nWkY-(AhqrFnGavfy%TTHpUHEpswT=NEO8 z$!#7HWvozAxgTaCz)RY^!m~j7WZ@*;_Ax$2;+V<=krXH3M47s+h|ye;p4G1^Y<1__ zJT1VA>f~U?oT!Ka=8U5GChEZ(H8U=fK5j;~SqpeKkrd!-3^(V!FX`IXpTBL|OAE#( zk1H#3keTNMVOI+IT~6R>>EEVrrLq5#)ihGG7vH?dZJSXiKydSINA5NcOC%~qA=Xl; zGDz|ypQc*YP`z|o=O<8&oK>P7E(>IM1?T$q<*yi%Ec-X$NG}9B<_92?!fx(u;f!y84u7g7c?8gnm<>`0t>f{GP zgX*bBabnIRAfRK_Vnj)5RyM=RIxPJrkM(f+kID~Vc}x8+Hu*J=t+%&Nmy>O?B}z(6 z5B#yIMt=8|qw)JIk6-xpmL+iRGp^qDC7Ot&5=|DprHi26f{P0_DPVvBt{y%bSRqk@ z4Gm(B0pB2{5oo-6{U7yRJH$p5g0oN&8p9Fid2mg=om|NSl}b_r*K zI)XW=+1y&ti@DVH!`Kz!YxHWV?tZY)0b=uwAqamOf=~PCJWK&3- zI<7dw8i47d_;+JD+G2Z z*WGM%cmtd4gmb`O*80+nEFUr!do6*IOZ|eds&cbb1OBOxRzCWzS@*R0+xydnuyre3 z;cY1rPjl6jLN+3c6oKnGwXdt1K{7;p6WPv$Tz21V=PS|$24ac%HIuIQJL#OK?T-pp z(v>OyT8^eL!XCSh?Z0M5aNaaI5KaU@*EA<{UG&6tINID#{SI4P^;V@OXWd&hFN&R| zbcpD@55yqh6WwpUvE{AvG{nC$>PUB3(tKoi&ne!PTwV0$0iM}+~$=ZSwnqzICAO^ zRB)2%>|V@}W)a{0Kl<+e@s2Zn8FE{J66yw6a$!IyokWIyu=~vQPTg%Rd@bPq(Bs$L zhrCRI*B|tu$=z9=CS~HW=K~-1VujjN-{%VBwT@?sE_dKseK$;tphsU$o^C)mlNLQ_Ga@*rPnt_q3C|_c8Zr<&~-;W?=)Un_Z59 z#+wNoy;?Jk;h2>ngTZ}Zg`UfXC_(`n^p8IBpk>NRbEa75sJ#o{!5R19pWE}zB)IYo z?}V@9pDtH6NQsmDA+P1KcR%<%+3)$G$&Xu#tvgs=#-{$IU)lr}h2LBloq{1bO%h1Ntxj7c+YfRSa`a3lch z<=^PBomjo@e?li0Jejph^%H)`8XYh0-)lacKH8&uXijHv;fLwKhgp0Z`buRN`=jf$ zWiMxVUDHsl!ESKGEM&w^F`JL`pbuxlu8h#zKb6@iC+Q@OG%AD;NOrLmarKIe?!^FW ziVSEIpi~5rsdJ3y3Zu+`YZyGho1IkB5EgjQ z(85pe`sX<6(#1lW#4!@VqoaRx7od(fL#F)l4TJ39g&%$f8|&WwXb_2rciS1`nH;W#wCB8-&Ahu>~Yzw@K>P+8N8V1+pm&4sVa4F{Ej3{HBNR@2vX2s5v9AEdI~Tu7gds=1$d zWKPS=;|<`=tOo1{eTPt_+?eS8H1)rJon*z;5dR#w+~Q}YD<<mVv{-t8uBXtt{ zn#*T?H%lrHl>`xYB+X<6IWo8f2#hy6D-czSdzWfy1LUA{N5WW`KV9B zYJHHYqS@hfVV*aDhsgkK`DFl#l)CaUvjkG1(rupDq6(}0yddY4*M+t93&4P!OX%uy z!4_-Csr~;9b%ovZJWEjwceo>NN@HWsos>kyTNhN|5aUG9?EHUzv9vfvNS#mN<#{nK zo|j{-5dqFpdrfI*>n~s}yaU$44DLAMc(0}43w5C-aga`R0#8ASo;6y%GFw81yRziLDx=SRMOf{(+;WV`ddziy|||$IP;k%5=`JJ zff6Rncmp=Wq*zib4wNmEx9k4ZQ};ij69LTVKs=fAU-G?OMZ|1wP#%eS4Kp_Xa7Y(p zl8t85eHS8(XZ?M^j|LOQyq0BIb1j38r)EuPyhIi|bInAMr3lNp$Z`-tzyBgu8i)Y$ zEV^f#xkU2Ml(nrne&vEplthG>7ABsJ#>wPP{dhUF5WJwyj~*Bfc0{||y+>x3r;1Ml zaBmNESs#8RYTbwZ-g%2iMmu~CJZh`B@+gse&7+r?&t+)Ub1&Orwbj+3H|#BJJ&r`M zb+Zw;*xmFR?b7uP{q8Ne-@6?xp3gsez1;5^=*64NVXBi9-mKd*L$02@h`j_s8>9ps zYcIy!?ZH$jM;?DTkrj}CaVtQnBa2B|_!sAFt>XKk+NiFgVNjGA#V?R* zli7g{hG|O0r+Djcn|)~R+e6syPj`+@Uf2JIulFW%-Aj~m-NeODhlLQ(r0q>Cbj)F{ zc*mN9OO>8K1zMBj@o$lOBjshMvA`4a*xZ&X(U&nGMIzI z*pMmwT!3&UyPB>GmP^t|bn*LlPc5{3sC)xWKJLr+)X1T^3k?L6G6FSRk}NHK6knz| z7}Lha$2$OdFDu#*VAa%k!P9xVSiyzW@RyAvHpHF|HsSWYy#nYQY=Ov~R zl8CO{iR;m?bp&uPA}nR;qX0+&8|PC<9}bRONV_bRc`4$c1K|gJDzSaPun~6`QZ!~U zvCwwq0%`#;50y(6pX(Qj*!|jLM_gRoHLx58lpC;8GyiB7gs*CDMw}3KRvHGAa5L1< zMnUu~3s?4EM>3`l_djM_4MPrb9~9r6x`dWdq~Ld5@^`ThbKUpR2qhfecx6wX?%=cC zyk5G#`$fao1oTxHBTu&9Y)kfV!E5IPlz3T${PWbP4|3TSgEpqnW`GO)dZ4e?qMX=L z&qaB}1=0XcF`)a^6e1X_M=ZFGr+}88>Ma_Qpf1)_2#H3sIycnjT>5}8PUmO%lIqdYx_Ua(KQC+6?I}LT`58#`E z|92G}Xj~Ahk?Fvc8kP+^mV%Pty893sK9DdgeDC-iqc9qQ3?@rbC>VU>p8B70w_O6+KSoSHfcSlUXUTB}>ucUm8`jy-zz9;HVNl zL=ZFuP>?16`2uCLAKpKKiZ}g`@bUwEA2Pm^AtNXua#TatusaMHq8pwi)Bc zjZJmZ=%q;Cp%NCoI*)2KBrT9oisM_srH3khTlO(RW*-YI8yP`8F!?k#S>aL9e$+{c zdeqvAg@8|Ca^6?NhM3P&rBs{6Xgce*26s_IvprbY@>>FSJ)7G)fhg4*D~$7DOCo8B zh(Nh>S2R-#?ni*JDt2F@r6|PEnsBJ{eY8c?5EbXcD+5(xh@j$;=Vjxu z#=ZPU$vRW4&v_UYK60%gb@GZZF^nlXUmTep1_nfQ2>`SbWD$-3AoxYuVTlH;%(dQ5 z^&>3;d}3ZWSdURDKy8NS+_&IW;Y-j|yAUb~uMVh3C;(y;)yNb}95$5DL{wslEad}* z`h)6w9JvaFIE#Gk?^3Hn>^iapkqFED_Y4T|0Oa>IFVi$mx1khNQ0Y?mbxoJZv6P_# zK_?qNvV;ioX$knA?fVj9%bv8_FDgCM!XtDEJkuQW=~AZghT=S168qO>bEssIst!#% zf9(J9Wqz%yj-do$kS6aC`Qn~`7)e-Y|B0%ZsAgU@4~YoLw63VUuN|sgH4jF{hX9Qc zuP~CwrLmxv@zV0jxA=!*YGG%-)LHwi8Xsq=yo4;qB%T$o{L0&#_)KW3eSz9n{5F(C zMg-8Yh|aqti<6M@n1-0$<`t6lpQ>ldZN?H<)`z-eA2KB3il z@j)vIMx)f3eErjQf$6q3&UY+1@E!s5gX}e;9wm}d8LqGO@o)uIHCcLVM9RPcL5g*Nf1HGofeZMD3S1t&xKHfEZdE==p zpyn?3NYyPt%`S7Egb2n_bb*elvUXcsiqj+YsNLO*NV51Ml@49fE)<4An|*&YU_A8- zKZyg?DaOF*ue>jWS__H~L8Hba0W1fq#36sN@7ax|0#_O+@lkx^hHq{8i9Y+6*b5!J zLXwKo2{N_uk$@^(Ai4tjkWXdyNB{e^?I8x|s*fP>7c9}O>=8S+a9MlxM4(IHQ^92n zeV_>>2NE9^n_c)eIpCjXPkO!%HT$jwg(y|IIc+Pn%-Jm3(0Bt6@N?*#KJIwUojo^A zDzmem0Ub~%vs5Um!OxZbd2F)kk?{@U)tYJM=_0ra=BfC64!wi6nl|-_m~9I4_Ju*i zFUada736=95tEtl_}g}hEdJrYt^;IDg%ZViw*o2a;)nN9z`wI8Sf=88$MU-SXq|Z* z5Rj>uAm=gs@1qz;pC9t%Gr9Q;HC*?SzMSWc_`j?v+xixPoaVoaRcu89dK&|GP$WRV4nR4p#i!Z(G<<-l zK+syoSp%gOONY1ysSyA2h`6LxvC9w5WzyKf7-cij4`gv>v^3e*ZogFN+8K|aRz?BN z2M>|5&~V~tG=($yp;Zw$RUAK3x^94+Kl%CuXhN#^+srEl7ldDeZ+QY(^D8iMWJ2Pi z6aVw3>g1`$K(w(o^lg1p0yKf*gZ{sdXgWyISo$nC2!TE)nA%47-!eJyx$aV3P>^I4 zJW+j*ZH+QfAz-D>^O&#Ad<2JzXIfbD##Cc#xDj|4U zcm7`H4?R;OPW;t@=L|GfO=t*kdrSQ8b3XwhOvSNi$ACb4b5q0}MtcvB53xt39_SI9 z3cH&_c=tIvwyDa(l(uR{a&K@xX2O0Y&CffV*^)%Aj9?BeNJer-aY_RfQYeOSD+Cm< ziy||Iwu!^mLr1=u-+1kee9+=fdxL-x#Tb=Ir*vc|MT=Ir*9&rMrwu!B;tE5f5hF~X z)6nA=v|{RNc^^!kD53@=ay`W0P%U`3j9)5(-%S^V$ICiqe4&vKL;J;|tPj)yMqx5B zFx{PaRv7eppm8qumvC75(Ds%xPpQMTg8utOGfQbeSRZ)82H?;0^OE28;miNzavoo#})1lvBElh`8%kJJD6iOvFhT_Ejbn1Kbk{x zzWos)Qt5Xm9w;*SzYmmci3oFaig1eQF1XCjeswUl;FY`|k`mg?*eLOTD4|Ie7@`bk zfCM}wXggg50xZQ5EigcuD zgK>r4V~t|5(?y@Q^tObb8v132BtOO??J#hy)342bhwef<`~CiH1m_O3BzQ=DdM9E7 zoCE^j{^oCAjR9En{n&(jhCjy8)RMN?tjX2yr>4ariM1sPuOmjUTOSsDcQDtsKt8PX z9-d#clr3>SW1kznFl$8;|A&zUiia+?u61b9aA+_@yLev|fK+h-Lcj%s@x#BshT(*| zEJv}Zzklm{X!E#oQP*d<%xnkr0R2{YsZ zB9RW-Hgl~YGUd-#0OPjzQ%6qPFOuGJTU2R=mS^-uc+edPFs)P7kPA7@)1q-l*#ck0 z-DJAb4)f1M+N1))(qtxHPgdXTAz<4u|sJ+PT?x0h%*4|x_Di?ySzK3j-axSJ@6Ngx}JNdvl@t{@Gu;+FqoPJ zC&CNNH_!!$Djx%eV?}(0-(Z<%x;GnsJCKbpYxZ_j1h4}l90|rj z*6_;2t=k@Cj1~zPILdkXGg(sBzZdxTwP*a_DZVvs^LUYA;9XP;)(z;qxVYV{Zi-fh zZnLwC5}N!Zm=~y7N9v@}>Qw699i+-YmiGTAhPbZ|>UZ9!*-Pd;? zSE^-9taulQJ@nMVfqMHu+7S*O3`B;)frER2Pm3 zj{Dn}8AczQ8q!4=W`)2f7Y*-HtW7T`PWZF<+h^fKL@ocgX;_PCm}7i@nCEE}a*CWlsN1{$rDGL1?P#i_J9J)LZiiPp6Mf2TuZOHAR-Qy37JNxmifSin3RJFb5OA)`BzjQ@_lheNJza8K{}*ghctkdne_?ud;*9T*p`nAT}U^5sFNL z^jk)n#bghAQ~6u|du?kJ-1c~6GyY$9K#8*rHt$uaISFimBHms#hObU`bBeX<$RpX9 zJOe}(6b@y64={D`M?|7J-IjENmuN7;_0$APsT#;r0IgN_$_Ul8#Xh=pdixLGn$0<9 zyk`&3PwYMI3iy;!`Sy06haA%kV(T#i?Mb{JxI&Nu0Ew0dm3?X}bUt~?2umSDYPI}5 zD(o+U4L~SmKm_HThA0@GJ``L=ni5(#ei40lAe}YKXKJa^{h;1UtxzAw>%N~}X6;7i zyM};I3B>hUq{GnEjbm>LV~hqSS|Eo0J zc=|2Gzvk5Q_+og6vQ++U-??>vQ!5g>@7mYJ9O(rBe#yDCZrCt{O`Z%vA-1)~)_@d} zlEz};pJ^Qg$1EXZi|+p>upZa%VD}WLdI&Ju)?+(}sK0ykRhl>Q3i(!-RikXm3Y9G| zzxcscMkADM$7P6@a$K>efk!;%4F~^!wOt!XjGaYq2G1vQzn!4J)+0pp>$V@@KZL$= z4QW0Qp1N;fBiTOK8QsBl7G5``CZ6ZZ$UL6LQa0MsgbzUwK%I==wKbLY;&14-k^cIU z`s&_v|D8LfYsaSHPT*ePVirF-)aWe9KTD9 zC?ldGc^#^3DV`%KWbRxVaL2*FdOckgDZwBCbA&;hwzv^NzD0_#1vY7*x4IpRQl{t; z)+R{@GksfvGMiSO*%VOwI+oig>l|WO9a1rTde`1P1nh#b^dz#63pNo2Xwv+sZ*Vu= zJl}Iz-|i?Q9PfmM!Q)sQ4GmSGHr5whCgXZ!ZGi=U_crHu{S=IB+K}f)-n>PHKY1wfig`5m2rRr?i2nZthRKa zz-TbVqELkIq2k1+!6(mRGg;^>6k`4m4QpV1Bm&yjT}4bHEm>7tovX+{FAZ1|bI1=E+pLA!)^4pf;nwhGK&T6uqm&A11t3V8 zU+5wN!?WDpx4Mm8HG93YKqSwI4(FKs)CUHtwhh=l86y!%(U9p>WTJA%&0VdvZ6+#F za2If!5If!NEl_nd8vqp_&84Lb!WDtMkuBNyU9uwY)_}Yrb&@(GAzITmjCFaDC12vS z>tsU|J~;^fSU#ISAn+1@F^NqxSM&-wfW#P5jgG#`;W^$v#^Gj&f4b;>Fb-Hj9u4lc zZA<--jQx`ItyvYUuy_B-kv{#&#FGCU1;J(ayiOmm?t!XO55{-A$CZt2I4F<&iSqsp z7M4Po!t$+LigCx&TGM*RCOoJ=ez?4y83L(Ry5VM@c#`>dgMPr5;XcReIL>+OI?h^9 z0Od-g#SSND-)fX|N6xi2?z-C9ezi4`lf7-8k}#!f?|kKR_vG;W6lGyu%=ab3^c4Q*c^xnP&`*2i^H59IK}|J^%V$Zp$nownvCI4=i%@WSCaaO%fRSXT9e+(8 z9{(et?@X9?0(YD1Yj3FbZ~w$FiNitTR1k94V91ofsbvb!Ihd#5WXRZm)QI812(KiB zy@vY(4^H=0W54UnXlF9BqRe+$~sd;WUrFijR^X>*(l`S%bovuAcUX9>Hoxso54ep^t)p5WQ`wwx^KjF zS!^>#2;E@&v0!q5>92yg?`J&mT%1iJ2~n0ZG(8N+u{uQ>1|fS-oqB(j7fG72iM8sq z?ZSD>OIR_q>5bPZmkFk$7~8Z&H(Y_ttIR0VbW6~Gllqo1T1lwJ{DO}=fLB_GIP?1p zHO(2bPT-Txdt2H<>0;g{pnkPKF#t^Spj2m~H1}1#`p9@3=4B8WmBE14l;*lJkk`;? zXA>_(KYsgApvt4Q^#Em_W~rZdh^TEto$n zka1=}m-mGLJ=1Vp*W19+1V|n-1<;^bas~;IF^{zN)E~1>YHr_&w?XOYWWlD--q|b7 zaVLq6`)$JMELJ*1xZ@-aKQa|O-m1GxiS`%Rn5uk`7pP^U#ivF$Pn(NStno#At#A11 zMjtNq|9JcAuQ-CH-NhXeJUApQ?h+(OaQ8*CxN8U&+yV(saCdii4elCT65JPexs&(3 z=l%iTcg~$(Xl8c0rmMZH>Us3Ql`c7(afYesPN`K+&S%J!#Mx|GA#;3*wv{?x7;-QW zIWT4^=Cw6;JIk`1PW1{VQdr<0Hq4=oANZP3k$L9ZW^Sr!JzfUo56ag7b0kd&Z|i|f zYa?%=b-QlAidr&~XJInZIm?@z?xwy0MmB1xNNJYD%^K!+IeYh~XQt$@Htm=3aTVYB z#P8D=ZGKFRF&lAEQbrmEFkoEl@$VI!Rp*j=b2)XG05=)O2@YO)fZ0ZNEJ??FTDC$q zc_?cgm^Y);jFDcqP^D-m=I&t^=bzjw;K7(`8);vnTz`Rjq#aZG_rXH1e+9>oSkx7b zhQ1wTvaZ6_Cj$%w>dfRyB`EE|>5R^%??ZuU!bk9vB`r-wqZi3sFCyY!PQSA;ZHzBfT&=sEZ%7z7U8k9XPD~` zcK0CWUp#G!KfvhhTLVTs!|txKihNVmWgIrP$?R5wc3&0YFcW#d#pKN!Rnl4^n2%h( zoL@KXo*6~X3}eV|4Os@Gfrq$GNLHMmn~$PR5LG)k2^Yg>iZ5$u`9Oc< zC?PMqS^_?ULXY)CfH%H7;eGx}e@>wKuKQhuZRc1-z}Wjj6jNs_5HZDJfk`zhG_>ck zWj6S#7&RYe0x=d?ud%& zEpti{cY$2EM!rOcHBly~nb4#iP>nQW>{YBL)f`68mx!fh<1>bqPbs0GG%3j#(}xRd~|W@3~C2#sSij$`7mI`^|2A4lwbw%b;8 za&ea+WBPls;3icggGGK#ecZ8VOy3H-hZ9Ljygn|0@$wZQs$X+Rsl?-Gzdnjq+>IGP zj(LUN5{ob0vW+hu)_e7y8I8t$`f4<@8+nS0z%@egs(*`M7`NhY{B6OBoMa^J2xBp> z3jTJ#RZ7!%D|K_Xd{qt^is9|xi?|u9_e$2Z%^b~b*W;-q;s_((OGzL;c%a9atmV%C z_Ul|ln4ms>pye8qdd&tYp5o{NPl}ZqGUXvdpXrU}kLq{BnmRV3z!uL6m1#s)?qzmk z_c9P0cXAh8-|&M@JgG`6Jb&HhmHN&^x5YV*Wl~!J8?%hcXDVtjhIs+PTKYrc%ClXs zJkrQ<3_Qw57d)odlU9SyOAS;V6LDJ8vqNziLIZmra{#vqsMU})YquWTvL{9zm z8I7x2BTIcCGCKchKc+C95?uKw^>cKVq0jH%`~FpL`U%kndG-&#Dq;wSg|c4@+_R?a zdFSPisYT9UuRo60V|NiTS{3(vmo+(fM!bLs6$-S|7hEvp$f#7^aLP=XnhW5tM6V{I zpvkAK$MZu>H*>?@ObcjjRjU<s7E6Wu*aiY04o(K~}TW@CG`)woykS^-LI zNgX?M6V(;`t(K*$;O0{m7e`f?u)W>*b$HuHU^E^6Co*exj&q*p{UOayiA)nM zuEknj_VW$(tW)YgsC~E>8nD}hBWXeoF*dx>A zli|SpQP0HBUnWfAcro7?Y)B9x;qmr*P5>}jq~KBrB%UzWTN0-8h75`)-@8(W7t-;* z7q^I%$#F^yI*=hDf%IesKB@_O;%uez@OJr%I6L%d_z=<+1e}5@^Ojw)C0N?1l3SPL z1ic*N+~Xx~Tb-ZMJrSGDh4e4K%N~?V(_qZWblj!^b6?qI_Q9r-6$x(ZvnT78R5_y9$Q z%6O=PpHEKEm3rq++gk)n0}PJ!fTK}`(~MRU>HK&pS*j_pKX2aUo5S7oDCEU=s-vfb zQ>I@>*LDO#l+VnehQ<-ZmBd$Um zQmyy9b*wr7v$Li(y2U`JwvBN&oYVN8>ef13ae0iydPK>O+62RT-Oih>ZHKP4SLICv z44eJ1qEec^L@q^}#$-Nk6x!$PSk0L;#J;%YSi9XOo%<(@0ts&p=LraDL*hou3jym% zXNg%GL0oq~gbPc%(;X$vH7pq`6Dc+s!SxN$>($0Rw3M(e%bRa47I(|gaE`?8;fE&v`S5=bPk(xKZ~CQGm6)3vLx;8 z&Dzv40GDHKwF`UQCJ*xX4&d<3v72`|=4apvMO1f45~HDcHoBTTLXszkCg@&#W6WZy zN%BMFJSvsFmbyWw^&?SBYHB)eOM7wCWdj6Ao)Mt&j#VB)MN*zb-xfh4F zjPP~pKec4i$y>rfGUS2ZKe%S;PPAk<0?kTdPL|#&D;x0a6}mfTE>_de?XEij6~PK{ z%UOJ{@p)9wT?U?}h=w92IKh8(P+~W8zv6@#1@met*_0 z>i`dsHAJU>%yFkb02ZpdA9Ve_Mr zVLszf*}F)&s46;xh?EU^$owL#i;5!u;lz^M5xj4V(e9}}4!M&vx<;R5I~Zpz=0etk zY??GO=&s+d9W7gyjWNsW4yw}di?_}Rs}FFLT=fTi57@zN!5^~KvdUalj{2+4%mJ8( z03+>4-mxWieqYsEU?tjd)_|^<0YC$2xcC=ja7%AdZ0%7{H&-Go0+kmUI}bYJZvcrt zasPy>&979<>lbGF*C?FWS#i--(gks1#Ppj&D_<;x=E6lK?u& zs#f1?(T*<>q-)0@wIR-+xtD`Mu&f8`9^mIePQ|i4`91Svsx7Qa?jczEOcGBR>ePh- ziF#{bwPwSV(I^0Gjd72!*W1b+#Q~+5EeEFzBn5dahf}(bJCNZ@2B?wh3`=(4p)upE zllQ8S)wq8pj`d zBcub~eA%6%T@QJ&n%`J)8f_+z+V)Kw@jU^lO;zW8?QK&mJK7l>r<##{DJ>7;!we`T zbB3>PRzyFro0@5iI}=bC@Jh+M8%M3yoOD6fX1Db^PLLAhJLXR~nab|^`0<;=oGTUT z5XtvJR=7RNW`Szw^q~7qq(hn+(@!xF0hTD{zFO(R)Sz>{sfnS(HeBM*dg_Q_zOmun zhey1KSL!#8#U?TvsTsS|iGha-%pqTPbGw!Al4h;mMKpVfQ1|4$wdCNcW5!#r)CnAQ zj|!_4K(-dx5hao>UB6m97aUH(j@vRdOHAu-Ma*)u^~o$vUm8KTAfew*A9DD05cclp zBM{P1-YTMoXht3me2pF6=*LN z)JAfxDiM)8NOGoy{wlBr2FQI>0e|fLBM^~nISk&#G|{r^g5S?9UMxNv25x6ph&gb( zYOJ8NPAn{QoViJY2VLc=#eyY3X2P@0Xq?M_45h-Q1ttSCY!%_Q{#2W41no!D`-r?^e$7?HbYK{8PqEOUS!_ICQDR(Xykvfh()m~3rU zly{tyFaT+<1}iVfxso1@Qo1vPLQwVx2OIj^7W&|4qit<7Sm~HK`TNk_nO@XY@JaJ@ z%hvwNyyT54Z_*rZDx)oGEY@!W06*tj`mOjWXYt#l{SnrtHicITS(qJe6!;3dPbW)< z2@250AYDgBZIz`Gc+h%%YoZ>$0{%i?NVW@eKo`z`AQYmd)S>%xpEg&3dBu%h1UQMIZ|v{P|iv1remV}+jx~VzVuv;VJE}2j}vIVg_4sVvJ|G0 z8wE`~niMl`O#9Viv}PIcL-Zm5^Qa!6PGaEurnWvwQ&p{9*;guMsOGo+zK`jP_KtcI1_xAd~WGW<2sGQJ&og^AKcx_NGNF9WZ-CtwaFf2*7j;_BDB zf?Qi+*ni_$IpWY8zEPz9Ek|3(Vak?qRk}6PLSN>3n=gsILc&dvu9Y5#6)m(SPVu81 z)04&`tyN{BC}~#b%B}r8-@$P1SoB?}4w$plPWMz5EtWSeh3o>HK}#1fpnwF zh|QEr-gSsw1yil}tNwuF;)!blEiS7xtEc{GP$aA^+GyHfkbR#@M+fXgKEYW$W2hiB zt@+3w7LR0wS8g;{PoC zzk{sf|H^n}bB&+!CC%XUe(y|i!M@!7ozSQN3oqOabQNXarw25REAQGEx0Ec2Cl-mA z%lT1Va~IP9;5O2!(cOIG8QHA1(4@wzJ(y}XE%mud=xys3{TeMu1`%dKsIGG%c$;Q>T=*tw2g=rl7Yuc+qmEcqjtQWm2IwOK1h@ zvlWV6itQ93oZsCDTjY0u?yq^3hs5cOgM_V$XPeOIbc9jZzO z@&kMuuV!lQrNRzim~>HRSSL~MzMvSIyyZlpajIZA_E1Mrdzs>W3}&x)tNp@+}653tW5JL5Mvq=ZDb zz!ZL4OpiPz`NBo5_ojd(Rsp0E290R$e1XsBS3OTK?&4D^K5Ir8dV%HqNVrNF{Lg0y ziPA0apv1heafmX6ne-qtATgmUaZgIo>9g>$)JlXE6u=X>N@X!x?Rqb_5)|7_=%HfI z@-s!0*r8_Y>@@n?{9krCzuxcQ0THI3A6|2YwB5FgvZI-#YE?5M>bSEhdw*~iyeqbA zX>Ly1iih$0jpvRLn{|QB7RGre>$>Tjw@I;4!9t?3b>PE1S7*&uk%TxWuHN{8u(d{H z&3xw!531Wt*O*@%APY14u>c(@Zj%j=qwI;*lXd z=JyevC|?q`@wTeCeWJc$NqhsDF4f(Hy`&~Hj{dL4x=MDm;J*Wn0}$pM4*d;TC^_;K zNr-F~nRkok900 zC2Kv`*CFHN&$k)j?Yo>qfNw!1TLYyVo-Jc-|gz z;Af2r6V4JjEaT{o zPxs#D=I%%JPr2EotYs@z4Cf^8Pq23UN^Y&Q(I@c*o*$B^OXiO@(}L{%4X5Acyuo*r ziLet4Dz_a`55dP}t{Y^)?fMq@^Ydg4!@CUpGj(Y_Et)k zTng!z99N}Djr>$`*XV(0U}&EjM9veRRNE_yIp#Eta*ciJu6+d=`4`oAg)C{6GYhb3Ux&^`bpm< zrXEyUQC+CuDw)Lm^uOyX&#V%0H{{1})ENn5fX1ldvPau09sr>93$aU{-acx)7hL7y zyJ7C;LJ7}H8VW`bR8p)rBbZC)!g%rJ2W<~a5zt z=Q&;_8rQ@2!2lAC)Nz5}fyz)-x zCFLEip6}Eo0tqopy)ez)DaF?ZJ^+y)e&ghdbBboE+Mg#ik>fGbc?$BUT=iT|d*~uN z{-JY(BSsg7jn=!E>M9_GUsP7K>67y83e}5XtLL>9VE;tTvctD14eu%-&t z@&OP^jD@emXrq_U@1FkGo2BE@i^v2K&j4Vvh61=H{@^LcVq0JWborX9Jz^*1liKZH zBL@T^KJmtFfWpJxPHa=g9-I=!QoSbfWLC(mj2w#Mx_p(16F(@ zIoJSjXi)CtgzXWgqT1XrKb+i-as~ya zL@Dna**H~>0$SSqE`n~H8TD9yv@FMWyDIW|ZEB&`+Of_1OgX`z@=R;9XyaAp=qTS! zqi`D8MNqOc#@il5Tbjr8Q4!s-(Orp0|7;%V3tU&E+AK8i&H$!U|T>P?sDN^A#82$pDq)9;@_U-)J6E zg$J5YHzgdScw^_+RYp^~YKA71O{h6#4Tpb0usxz&#$@Gn=EX$DcXO4vPc0J#N|7uL z2Jeq7%>c5QZ_&buA+mpxN7yUP=TQvXw24~lB!9XhvCZ*G_4aYN^zmw*#0J&K6B8Mg zNwn%&Ukq|O#J_GZZ|N>`T}>3<8B61eMhqAdojd`6J%>I9B8P6auDnCYk^L#wf#Lym zTx1g`Nm9vydvrSRzZ~U{pmsuy2jxn_|MKtSFm#ei7)p{e1JK9}*WkMF1r|ji02SCP z9jH@Y74NFGc4Z;Pai>=__;fb@0E1n#MiMJ{vNxu$%poIhZ+;uLsKKv0ZdFa^O4Pci-j1G_~be%#-XE-;t79wu7 z>-zc4W=TX~PMI$jGle1M>tJ5wTQuXSCnAxM&MepHSL5&0Dc&`6eNAbQId9@Z>qSNS zQOKJ>B^!48b}+`q4Y!K+Bgllv9=3bps0$YDEMc^hc+m|^H>UVGjS$u zOJm*4mL(X;m#9P^Q^lmAXi%20WWp=^%4c>zvpBf$!deGOaVLs}qk;r@>4yxOXiSic zNqibwp6Is#JonYbD^aM^MD#9KbFrJGWg0y!?mNDcpGWvXJ4V;u3_7PS2b~@3dn~Ms zICo5EFDs#-sE(tRh#NtWXnP8x0yxei`7(uFwY)Cn#xx0gh2xR>V_=Rx*Jc|$8oM>V z%Bs8iv!}M$*K=>!1)FpG^;so!*H2%QIV1q={&8M5FPjfN;j;sx-x5!9kEvKOZT>uG zV+viC)^?t~9%XX4CdD>OH*S`2%2xgS(~r9V&=&vi7LgU+t>CP?0?mnKitRO2P@q_s zX@FJnU;WL;-#MxE_-9#`W?_!lDMi!$ZM34^&Nv4=nLj;x&)N|tN2X%OSA`^*=B-W$ z%kdFG#F$|3^v}Ao#;e1RC&k|zaNJn2^{_R>gSnk8(_S&d9prn?JPV$$tD7RRgV;TF zv81$^!#ye#IfviHN{jDbj~I zp8rg+*&rzw(ZC1G8WeuB?>~@@k$w+_CP)0*Z|X+j@yeSQ%;MU@9q>xygL0CuKyUiA)clPG~Hd@KRZ-BN@+QmPQRnC%|vK z-h&p%bq8J^qzH6Ag;2V+U!S^(Z22)>lEEO)Z|D%^`%^2#vBV)%l@-6!xZ9rGcqQ63 zBJ(f#nlJ8w81L0DPdv3$3)yt0*RWdof{y8i;sX4=b7qvj=b|m50Cjb^s>v}HB)D<4FvGw4SenTT8|B7 z2|B+lKG+{mXSRy)3mcawcSS3%Mw3apYOzEFAm3Mq#E09nmb4u@tlC~iLtp{2Iu&C( zn0q{n2VSm$^LLX7X2LtXQ|?+}4Lgj~D)6%o1Bz2ClJ3r(vdOesZLK=<|E$Cg~3C8VrSo7-ywA|%HG6kf#7rBQF|kT+ z;r3uxw|3X?H)ws?q4Rh=Q)K;YiS4PIvS~zDWcT{S1@^e(Fy3ATWS$ zv39$NWWRmlyXD8g(+r1)BsCjTjPAC|f`*=EiMh{5v4HzS{Y{pOLzbrK`C!JRON#wrB{+7xs=4M0xD`@jJVy ztwcqh`OE`8Yy9zC(GYmBmGEhz?|#K&#Jc6pJJqNdNkO-U@0;}6Ia#tP-!ofh$DX4` z$k~IyOoln%)8RoqX=qS_&tsax6(jnD&Dl|3h}=^H`!#RRFg~PE9X~El1zN=HWW)8# zP8p&xiGY%nFUa{+-$s&bXiIrN0{Thw7;7elv1w|u;Bvd)th%i#``*utt{Vso9?a@bK)E4?Xnu|+7ln481aD;pAm(*O-52~8WCyiIX56!-n2_Fv_)oS*XZhpp_wh$I z+;-2nnZY}c!*s%bNyfQEWFRdg6)rOzJEQGjMbW3iOc$oZ zL-!ocIKo#d(eKJJL(lTv9VuFKF9%k**b-qn&L`!20>7`4cLFe22cOz@*xg@3nO1_z z{m}fpOeon^RZO&o>>b*5xvtpN8)a61)R}usxpVgO7iF1!OJo_M95)>p_>u`aFY1%O z8P_Mkgb+Hz;QUmbZ@+RT{uoOw$b`rXen%`+<(~{j%=8huPi>w7Ck78`yqV5lqs0;4 zX5>#-pKsdB3HwA*9-2F(U*C3$@^Y!X`s+KT>&xjwXXf4Wi_^6jib82MMT~mBMF>3g zvK;}MKQ1S=RDOIH_EVX97;IXjg9D+KH!+gXs3$NQy^L=@$m}k3B?R1p4Zy9QT((BwFhb?YaJRVuBqI6uOUst6W zJRVdQ*xT&-Lbsl`ZfUWT#KtgyGb806HQl%j%iD@U_cfWS(c6=*?_D(<7wanBU`aIc zxClwYW&yN_P&iO*)@rM1E@_MY#H2i{EY4@*>wz|b%@!seE^?xeia z(BTwH8^qDx&uM5}`Zq3L%p)GvK0bdHe{xZsI*V&PROzGy{inxT_%NpzXeSYCk%@%a zi1i()>z%d_Rmg4O4KapUW2kM`H~bZcC2xm}^|i_#uWhj;P= zwfGnIK0ruK=|+tM#Dc0)U`GS58`~*lvjx9=5f70^12*(ejP(i`cvk<;#)_Hb_b__w^W5t^VwE zZ)AM+YiFs%gK$Tyn@L}@)9#8OtZj2xtx7^3ZKf4b-TpCwAcLiuCbBX!;C??e^EZ}; zkYc~%qC)AP%M#4$Eq3;F)CAC`>;=}MHCw}ZSoyv2>|`#i%tv2frg??c??;E=(`6k+ zzn5PaA?o`tH7wg>vLV3*=+E*L3Bdr(6QTbH63*F zC`i;K;(Zr*;`?+<15YbzXbbj+9QN}mv$j0~VEd#|TpYBKFv z7LBjBx#ln5trIpczBo2NjF(bUX~X9cm-@+y+_Ti(2&PFvclsUs$PALvqZ2eTCMZ6F z4hmtH{_wH(c+7A+iSQ{N*f=J%f=Ei(jf@UBwiM%Dm|gS?0`CeEuP7Pxl@~m`0per; zTbDA))C7-xk#7PnmT01>Bf_&wqKro4ok-ndExIkJKeJ<`6&>-Tivu8jl#-M}ub{AS zO!3Q~<}GP)n-Q-lSPO%0wMm*M0>Xvllfdjqp4OzJ;jc+G~S*a^S^eUz0P*| zQ<#oUJ^Le4sLyn@Jv=;kW5Q~hsip!xb*=XJHKR%w-?WhOMLa&*t!|Ym8C?@H_~X=2 zQbb2-%6AF884CukmylpWjpQgeV(EGuCmU~Hil)BiWeUX?Z^0t9i&Fu3u&9H{R8QV| z#v2dId5+YF6Ja{L^0%j4j2eTwPRi9oYtAb@dxV%-`g3Z~Es8Qv{}VJsdA~bV=VXUW z_%!!!TvdlyQh7u%$iBLA>~H5Q6uIT=A_#hV7_P>B2Vg~z2$EFI3)`V|6e-WT|k)Qk*T`z z#Q_&M`>h`;5w5+&B@FcS6zEBPsY6YJE! z?>K>LAp#A~l7;ar91!>Ca5UXl|MI74YH!Id0U1A3l*XH?4WOTd2C90ZkH&N|QDBtQ z^9RuzIxh^9v}j%Dz$!X=Vu?Zhk2HL0N>g`|UJ#G93{R%}PsyRl!Mm0La*#(q**utP zGJhaG8L;&H7*)r4%2g@h%ht*1q3pTV^;PC&u~EP?;gRa(E}!yAe_attxdH!c@WV1F z+JQNEYj!bTeR*2B5}S z&pR3$rsIPL^wo227UX%bj^V(I3(QQP-XpQrfTh*lE}=-@6SLHlX(h`8G{LyoSw4Dl zi0mWGEZBxs*Z5=tL>>iVU3pKMJ~6Ue;Y%>%epac<8y!|%DroTDP^iIq^1hX* zp0MFlo(f4(K4nJnNLlg=v8UmcO<}}1P|P8WMsSlbxp~8=mQH~hnnR?MEaT6#?fzSg z((k|)HL@d6(xY=LIJaBU&Rv?|SmI-U2jPx1hbb( zb4o;MAk)gXL8s!q3?MHGf|&t7W6BH*9taNj!o=`Am%TteWeI5IvbAzZ9a|L~#Il>W?P$BE;O4wuGv z0)+B{T+uK_tQP+(yvwA18T(}WM_!4nNZK?w=jofn$!p66qo34#yls&~Q2(i%f8#Pk z*yYT~*4!aEyXsExr0L^HrOzIt-Nhl`Y$a0q+a<#-ehW$WYXd@GxMbIs^I|@1W9@C2 z2oaL3t<*u?O@0JSFyTypReM%XtX)YXqzE@TeJ%vId;`Kg!Ogo&NrRLnV!a5djL9Kj z@zzSNL?l)ueLzjRd`LejObj^@iEY!RDu}(3Q&z8Ep&Uo}+Vl$@;vPF)qSSLiPH(>9 zk)++X61;keyY?p%|L9{5x7B{dDs4-##g%0;p@8dlaxfo((Z$Hoq5>R7(XfY|Svh)w z)Vk__B?+PXTwf@ZwTLO{akcWa|By^1Bt=IJ$3p_22&FxTsd`7Am?yEQENCMt3`;;p zlx9h!f^g!QQR&&$>rT5l(_Uja>Z%$?+@_y-VT5}l4_E8cO70@n_+6~Qa~&cCo{zEk zu3iO7bWZ$2dARl2#S)jAXM|XIYGz4}TtHYr3nc>X`!k-Auh{nu z#hv5ahF@q11jp=hyfh~$%x{oe_8(NiRhCY|Q>BQ+I8WRpMQ-0c(yfFW7_k(XbRT?< zBA-2)CwmZM4`vUiCeu)OrC%JlbArQE)pmz7yXuHj{X&pL}e zw=s!wvnS?S)bO`RGS0`a6bAi}>GseNC>-&s;Pj4QO>+B~|=2P@7WH?mT78`T3hP-h6RW}D4q{;n+xmO7LJ`Q}>6+5CnnZdFWri0W& z7{p4lCXp&yN?1PeGB68yvdcPWPBmGw;C(94>tOs~4W&^Da6E0F=$yP-8t8OQ7nf)s zKCmR7yl~X}aMqGhYbAcKQIWDZf3HIt?|85PTWm~w*j{((npI(&G{T|O>e|r}3mE*D4iOSHSi%8GqDrZ8L^2V%DT!gM;8{TjhLEwBb-TfB4A?^N?KCPKTdh z@J^LtNslvXSxW%1Je7B60c<#4M1og?$#wy)4w`SN5GD)!a&(n z(jH!ZfE^2&svs#TejB z<(?X|nGp{DJ4!@wTEfaOszN?m=z~Ceu%>i~%MSJ6!QUPei5Uof&_HY8CK-Ra#JND= zf5>z>;Z5K}0)e5b5q5v0UQj?ff zo9^zZF(otRK|IqIRLFz*vXltvr>=C&-3@y_(1OC@D|9BBlD(%Mks+356(!xKQ z_Gir2UY=~37fR=(E=NyUrZC!L0vR4D*mw2Pf6kd(iLfcvN{K+H zw01FCtTb&F_OwyTWkUXZ1n?E&iJWtrmkqH#Cr;f}&4X zuSI#AZcC{QQonhwrkM^@?@b2B+5u|X9!XUQZ!<--)J=j5Qz=gCYv5}_2X&3L#lMVC z^t6kQV0;(SkJf2=JNm{^>cU5F1Ey?YYjptVizY>_*`iiJrkP82{ zB?CYD*n*hK9~WeHyh?^8L=I`mT)k>S02z`gx^*GWD0buOP1k33g#abemx0g?_k5kc z$_)*DKCu|2^v9(-8%WjP_c(ULh8{*M-DKe1T)&W!B4YLF2#$DTf!G^RlR!BXbS{WW zwu6y70@)8|rU!2DT9g+waH}0RcSJ4v54ZCbPJ;y&iNQPJYLgP(F{H3U6fnrJ>@_=_ z*PMwc!G_Q456PJ5p_b=+EdlS0FaCUnf>u$JG;$x}0>CaSx%V#D9f zdDUpfJnk0cEUpJahV5uc7p9ks=HQ z-GM;i{J?ICjrXIiNUVD>{s+}RTgkDPoWukTEo1lbSD7}!tDwHu?E4d5$cn_l2%sc( zLPZJ=Y5}o-QVn56H|G= zNvwgu0TBB~Kq-&~FG8157u*_9s1Lqq_Ae+-1nkB$8q+wfb4)QOizpsBiv%8gHN|B; zvD1Cc%YW74)F||;79GU?6L|ZnDnI0qq6Y6gknZ3>fi2Fi8e9&ncZ2`MtJ$O;LdXRKRN$*?M|VnWUFGH7?^>wYaB zjYv!e4-U+wpu1nxcI32Kg&nO)M*(`prve;nez6DAb+AW&xxXrs4)y7;H?9#BjVS-jZjc3~cuy$QlKs5vCcAF-YJ~KcfJOG32zdsiMl>eU;>i;ak{26%r zt?^A}<-bI3*53y1RP6s|T`&~=Um<=VIK2Pd`X45`{#zn*5tov~`qg#)zd&Ze5q@YO z5b>LrleZ%K9{>3#2?ulNe^r+Mr-A>Eg*ticjcV2RyltiY0+gx;XbeE@gs+1Q;XxoN zMDc$&feRj}`2S7fUu}2&xK&<%ibepmhJ@9azv%qm9N86vxBi<#1V)hmT>rxaAs^73 zUpwy7`K(+m_55E^y#Lyzq3I*RsKN*6O!2dI?{ijE;ON0>$G+q>Jz3i^J+QVX3VS0}7PI?u}&%OsPyV%0J>{O+R z0`F^i?>fgr#uYQJm~ts=533M%`YMvQ)+f@!ef)g>)783E(j6eb=G_U7%@23`xY7yI zqz)|r8yohSOOPqShkw@rbC z<8A$0sZaBTE&71*IQcT%z3S@I%4?jlY6`lRLOmF6ZU#o9d%HY|ldp3J-RxP1Iernt zc`GyYVeIacF3HT=`stdO;}$Gm}#NlrwKS1rw2?gp1`U z3>`=yjbSZkbp959E|e-LQk%L)D=seSD6r>gA=K_1i3afIIF#@eDdc-1C zSwB?0NI80RzVyV+eH6f(j!&JaL_?Ao~A(jya2d;{v0-{Tys->J3ZH6?|8yx6FWf ze0hH4Hd^z#&ZakT##UZ7_TI(QTX9g8sG7MfX98dAj)E7ueieNoAYmhj9cy7LI zoELVJjyVpQF1v6)Nk3_u>iBi%8uxwpf<+(a?6gJYes!R9}G6BVEem7Y29+oUSwCNzRJ3;U3cJJ=h zbaA_TetW#vE@&?|o#&J?|EerwrUd2co^}7WewF3DvyU78q|lzXoAY?25C7dtf%nvn zOhm5SL@1jxMXN8xbjJ%Z4&VdhXHR&z$yTYDW8SXdoLfbj(moI4nWQJa8;vkcjJOAcWluv!qTEgD_~%O-}3XUSt{2}I6gv7%PNG?b4{(0<3<`lp<}wa9UBihf~9-$WBahR-51uIhdCy!W~=mvx4sY1yEw-Pf_8g2 z%nU}>k~rEK?#>Q~C=dH=nz7aD% zaoeevFcTts6Z6z4y^JkV>3ndNdPCd&1xY^A)2i;F0n7V0$9R7AUx8pvhbL2wH&{2> zoe+L`MT0{NS9OhMo(AQ3IGo;iWf7`8xje>`{&Tj{fr4|u@GKfI>YXw4KcQ$@|v8cPf}qtFVNW z-il;5DSfuDa=|U@fc1$uz+&DTn)v(<+!tRu{aSd|3HbQvvv7@#t&|gc>RiUkM3d?h zDWMNw3E$djwA%mxvrnp|^UCLULEzXTCNI-nh%YI^4&5D$JYY=-cg)oa`@0B5_DvY} zSB5wVy_f9V-={mB(^+Ch5LjF)&K*0nV|k@2yqd@Pu0)(l5Vcr41*SP^^`(|uul>d| zhy>*Tyonk#jS3=-)6r-mcSa=UjjX(Ufmv#+DDZn9Wsw9>P^b~fbMbiP;T;19v4nEz z36C*_VzCSRzmxC6_VFH5pS-PRahZ&WN$O51im*3UA3`+5kEXklthHeQbG3B@Ssl>f z{uWF)C};4X`#Bn&8=26wDY&59gXEt&l!row;gq+#Oio1|o>*P)UESZ5=+F|(9<9c0 zb&5<>knqiC7o08UHaPYkYP&(Bj%W9mD%{T24U!0@{rg#WO~RW6#ExgyL-rNZ4!I1v ziACw@gs*GE81?$KU=E6Whswz&mBpQa1Oa4Q>71~0BK^yGA}n497Fi#)$c_Iw=66Ok zLvKv>g%?nPP&Yzo=1Si~oPOZfG*;|w^F*JdaDEXO+ah1SJL5X)7a`JY;6bFOP!I45 zWg*h572#v2JXkb!BN)9O{cs?%G|2Jkall9;Yic8iHczSdHUvsz)BuaiZKsQX8FBy+ z@pCYpNE(-)!q}`%yjdK*DFx``_@<@sV$u5OM&rwnq*Eb<+3#PHI`Hfc!57{A z^u}6T2G_NYh3cfUI2Czvjqsc9nbbcgh<~0>Wn;VMo}){C{b`fgqrj@}E>0p&OuF>yBcScqrQbgvB`9(sl*Jw_w5|GxCL4d^`_;PHjWu!Mb}EdL zAmKKE)rvAuOmC8xQ`O1m{l)QXtXi{ER|j5yhv7s{K$E(tC=}%Am)CXPuJ11>o-93( zBnS;dos$>QsWG1Hyx%I?^1KDJD?B7QEe=G4Bt(u5gY%AYyd=_jR_Ietqb z!4aH@Oc2|bZwP?i;Ycw=SlF91>Nm4ynFh62$n$oiNWY24e8Ur{VUUrb<&JJEyQA!2 z-sW|DkigN}SDR4^pNn6OrqOP*CY0%*Mbn@bK)EZQ>1;*@yDY7Wi?F6KS*nyII(-{v zHCH6_K^?&BXr+F=zl?upir=n~ucoScqw%qq(ofSO*2>FGFw}$QIp)^_$Wu3=Z7SaT zgaIMCwR!9>j_RpCB9(}x2@=K53XipYdh#*EkkonoBSW32Y+U^Zj8V@dlg4llxeD^8 z#Z1pv;l#C~IE#1OxWV31u!&(4I)={pBnW27t;e5RN!@k3ivyinfsfIRrzv2kfa(4} z-))$k=a4un>(X%F>SL(>v?E_s531=|i|4aDBTlb>j?&xYTFy&RevWyTct)#mG_8#P z#~G3)xCiA8as?hYBW!18_hTW2qX!$(3K@>R|2&1CmQ9rkx!y%?p+YGzQqrv5j-KfB z+TMW{GizyYgLygoL+Gj4*LyUDg1)G&TEShRrntD9r9V?gjd-$7dM#c^3k76v+ciAD!|-R*OSi--!qrMKW^~49DdcXJ1YxoX-5zJ^2Rl>np&|PcY0OLwEN_o((<6C$FGTlqJ;D~2 zg9)b*FdmNNPsMkf&P_yzW8Qs^e#xCw4E0Q)JSKOidtFWoL&0?t=AGzMG<&jkb{_B6 z1j1*!)bxOlV`Q80Z9q!2#;L~70yYsJ;%RV(PD{3KzqNA;%TKa1G>m}RJ)t969tU(L zR|WEYGw0(D6)jR)&E8aF;M|GVa*`u2WSGXSjam{Pf8XF1ljfJR6|^bJ0h_36p`Pke zc1kZhMioXv%Q@k2&@`*jQETi0-jZ%6T(Tsdh2D*XoiKe|nEib#7kJi2tI+n>s!&!& zY=7io!P4j7uHQW+D5adSAN}VN7ewa*6j0jaOCfu}htRzkkqLS9QPDxbbb?b#E7N!R z(Q2_esJM0Qgfs)EsF$rR2TeF=8a%#We#T7fg0%NrGvM`#j*F?DKi^ z_3n4vAi;B?^Ap~^{)rQv61Bd18_UHzlbRC(_!3vQ6$oT_c1!gI$5fNqe7uFLKzYoavO{OVyKr{%KzGp6%0mb?S1pOo&JNPo}EY zSowfid?okHu}IVo%`QTT?OqSd@)p#Z2BRxwXNvm3v6FO-pOq^!;=5eQ#}2m-Sil?< zx(c7AdaVlcI;E=SLv|xMtaNn0YdjnU3GLmgIA(^$}nh0MySY zb-%2W(HL%g;b7iL^R|DwBilh0<2-|Xnp9hAHr^!h&E*XP$o=}6Yx(x8H>nNc)i}D3 zUr*VOoA5Cq7Vg2<`miy~{@AH?tq0r*(jVVnTs`SYarL9><%lV6XM7Q_x!n5au0m~c z$R2%s*Jru`o(km%&;^mHUel@_9_XWz3j%>ijERQZHEWll7c5qtO2x_4u{v(Z9`JDg zOV^bn7YL>gFs&gh3DOtvhEJG?^OxS7mV*BEN(o|@bV!tCs=FzX!RwCOHz0Lz!^YwC z4D=@66?Rc(x}GI^YOHP^^LiU`Yd^AVTx;*o+81CT#}dVr_zm>@!&dt`AgIqQ3j0b) zloF)sb~%PGbhOL)^`8MB>u=eP0H+dMwAPK;QioP2ckijeXOu7-MxH=XTKPTfs3Lb=Jaxu2 z~$TT*_lYFCgD?i66F z{Z-!Y$JmqSogL4TeB?U(k?}AN)Ncr%pSfl%H`s1k?$893TPv*nE5vuEvX`*$rgQri`2JTD zG{O^b@rA<|D_u~EZSg-g>D?4j6xQUtKNYNw%^Pz`P<>Q0$iJS}bl9bA z9{9bdMR@}CZln)s1}^Peyj!sTRYoR(7K8KE7mO;7ohvg49+)(V+fZ``&Gz!iK>0&8 z;_7tE7(d;@rq}X@np<$qqr#aFDBgRhU+s%wri8L(Q$^gK6PK+#puH%;JhM~@-tL(ef7yyDEDZ?{;}I~L=S?>3@#&rXtVh3f z;C-4%>xQ3{{k4B&_D64g{nqyrMQNpL2@OlZGq1m-RBlLX%=KL=mp_TGC7*4On6`ic ztKs2Ova1XHnS)k$@+ur9ko6aZSd&fEHGF^LJ*%QR+V0(Gf*sb$QF--bPwyh|-X(&z zUPEu-aTZpal&0Q_8Bdl7>I%!DRA1K8s+2LBdUKdio59s^kj|SUAdpOfen=hh6Y(C9 zQGUHR|6Fo^BHjUUh2h{C!P8t0WWK!U(C(PQjl>5i)bt!6qA~rtoPEANA?g1h?^Z;t zF@<_d@Oz3YchK>cSrI{1me}*n#KM#8R(s3uBoV-6SBh&{xou2wSDTKVUSQIJ-yd9y z@gR?yc!9jly=fa<=uXP-K+h9}(Z|jmzZCqjDLp>BEhz)tuxslmn|R(aCk<>Bbl;YR zbxG7@z_dlYKqZ&|qGdgEhB&W6JO(#N<9xppg z-ihcD&a5*{ksqlJx@#D?144vimPhl$%B}HVJ$=LJIX>V+5xq)*Lv0`%o)Xj@>8qz* z+}}OMPky7`o(W3+)iNU0{XSoecL-5cLyeR<^JMSKIb97^a2u;v z=mp{DiJ?W67~Lm*pKgRh@7_Q-$kaVXwE#cwTsB_ti#Q$ZG^ME-a4taw*ZN4M&>b*i zGXBI=KxZpMUbY}X@eYEQaWSn#{^cKj|7*3r*D}bricTUyVLh&YKo#@U^|LE$o&~0o zlH91-V%PYHc$_Gz?SjYj1l!asz5;lHAd+(*N&?2|n6E{)#`u-b&p)r~+!iSO3t&G{ ziCw9&C?2;E+IAE1y8248u{EZwj$F}`;&3lm?+8L6_#~>N=_T-GqnPkQ_>Ux+34gxh zk#h12@z68adVmO!rs!}`hRENXo<-&OB@K3>;mnH18mNICodEoo(*v2?tsS|$4CyeE zPLz_SxLR9s;^F&@115&s-lA4(&kcWmXyNOImY8npx>a&(Exu5a#pEXg???Ha zc_yH^ule^80&!{z2H45OAO|UzDazHkR(N$Nur)4MGxz*1Q1RBe$7x4qZe|?G+7hVq zy_WBN4a6u7{UM`R)^acPg0*w)dx)}yY}cxY(Ls)9kFok^n z2q!=Y)L-W9fq;X1f7cwzlIgElY#&6FtKOF^9s~Cb9Y23sM%44Q)>SA&7*?E7rKllN^b|a zk}QvhKz+4%4jcMPC0^HX8ePLb!EHr4O~ce1iN17& z_IZf^x^~)Ebbn;HXpa%#ie=L%Q~jRyIKQ3T!nRyt>;6JW%ER&8 zI^y!?^<`uC*B$A>b9&1!A%bUyW{BgNg>O<61q)uopA6T3<_~hg)%=`oyl>-}M1i%y z7XvyrbVh1yWjZUD8-clIu1%UD0OvW>17)W2A(H1i;u(|Vrg0M~^1^X!l#X5ApD!$- z{F$?homCD#qZ%(h)>=vW*#IV&0N5VpYpi+1t+>J^uf-8C^EK(uP%2O ztQiQ_E0)k)hZ1q5SkdGwD?x}C&&V#apXAQ0Fr66fo2Uwe<9k#kVzJfxEFV?w713^I z27ojM=8R_c=XDJdQu4EddG9p%#|aVVJdLGD3?Eyeu@q0~3}(6?13OZ^uK_b(EAa5i?|N zN6{yPwAB~rec>NlL8D3;6SuNW@OFIHKZO76hSQxs>#{mH6|un|lXh1Ke5d38T%kvgp4Fhy}HVA_3c zBhs?MRw%o2p>fTnE-XRLvkARCM%SVe$*5*@jm}ORzOBbMer|(-8!rud@GhwHhhM}7 z1hSPc_2Sc$7pz&3+7PMp2jQTe9?%f?FG)Mr6Bo0U42+?xX+;&(!inYXZ5$?fryZqS z&Q;g;R(NnI6s{rUr{@%9O?@*O&5Y~o2a8(I)oW=4Q?ie{9=Q!j{;bC!eUB5)rnp@z z8p7GV3I*BV1^WzmF8p-By`B-NbNDou=P~*t+8rv8Y@-(ISy5hXR%@RPX)ZZO(}}#j zIA*We+JSbc@ck-|d$)@#k}Vl!8YXtoOUzYLbv&1-)NX*OAH z^p_6&JJA0hgNB~JeAf$t9^(NfNP)4c^q1^q*DbL5y&rp}mazXJ4c(vcXUcV`c(g+W zp$(Cc@>@Y|pR@a2>odqy0?OFD$NIecZ35uWOh-_G_M}#~UES~FZ&d!nA?v4`)IvI0 zRG{%;%2G=+NRu{%B-mW#yg;4FWWQFWe3%$FlU8TU?Yh<}z0Z1zk5zC$CX|O;CrTgC zsMgxizxWHZ)t7qm$T3lp`>chak$Nckrz!ylSfOm+9q)yJf9QaheCT^a(%5IIlf<&8F875t(xw-|cE`njVF)rK+%& zqg$x!wB`o1g+yJdiC_7+sMS%)L1d@P@>|&(FAo!^OG9zhA~&NG@(S2N<@e)KG9=01 zb~?&B=O6t?>^sdAo177pJLeKsGo3w_8QzZR*d8qp#VxliJ(E9X>=Fh6%WFFRbTm)T z8@_njC5+=u$M+fW#s|^X=1goNBo4tR*G{`*9~1hIcoE&=>Onk~AgV<56K)AnNn-h} zX^CO9MWaNBfTSJX0dYq%6G0|K5S+KGSs&wYi5W#JTPDFZ9Twk& z9zVkse-+;xp6CNA{b2G9#XpQ{2XwXJ-Tk&(dr{AeOc7Kj>i1j8Vs??UfT^;FdAf}A ztMBw97i7GaeOi`&%t9gg##gbKSDCF#Rs4fBz)SuPwG#Ne>;7!+-H5MUv?x6`ea|3P zSA2KIrrq$je)D3*FlwE(p3_bns7hF2b?e6Yh3|;iWmxWBI=g#o^l(Qrv8quho?Sg_ zAy$j3bu2?vB6xOxObYCFjKuLfpXQXU4m8MeDqgkS(NQWZE zh9NYi#SK7Are?pF}R7~hKuXIkwS6Us0R_d9_Mf<=EY#ui}N`GctuO!{%7M*YX#1H@6m+$H6>qC*(h@^{r? zXhGVsgGYitaN3KUm&luq7&vvCIXfGEzbdRooKwmEW}C?|fc!G7_Mm%gNEy!8q%mwGZ!GDn2+{b(Ypx>Mb* zc)3gmxMXghN*^1MbwAwRpPt%!=cD9g{-NAp+D7l-OZMBOumi?=aa#km5;d(l!Y2?m zsv3*>z8MiDA>OGrz29D~3?S+R6`k08G2C|4jkxo;zZ%KB_X1JTQ@|Oqp$g8BWQ@0y zQE8`}J;JTSygtR4xv)Jf95|`5!a$ydCDo3i30JGM1z2cDj*T!^d);oI+W z`51xN8AQPew~EGMsEd429?Ttj(HLQ&h1Y|A*b;!TbIEP0XDV89D88ET%(LHg@S0@> ztAJ7*j3UMDmTj}a4mWuIK}cJDg{?(TB=`9tWZ=3XL74ALQ>6n0K8;CY%S>%khhMmf zi=#=v?=I<>G~T(xyLTId#mB-6)?Aw5;Tp}>D4*%n@_rsj73L+HK$FQSFiny(ri{h% zEiL=ma+ygfSm$s4{1Az2z%4s+X45BxGYa46Eq)MGRvjCQoS)hZ_mFfCkjr>8{5AH) z0kKVI)z5Z@+@MjqYygOn*IXcFBxrU@+*TiMpPQJcL!kNa%4YoP zcO)x)lgW+Mx_S>uU6K-(1a9B)cHn-e<{c`o`5>MqQX_w{DuifTYA#q*7zdUtq z;=H_OcNE53;p~OhrB#}6m`fPkJzlR?!cQ`EK=Y9#^Wy`*jxlKb0aVf};Xq!q!((EU zT?$jawh!ZYvRU2HA0+TJwB9cv(_V@=^@+ax?m_`77bHfClSJCguImP({SY$WJkDl-=hrWeV*P)sFH zxPycpeI(u^U{ZH%ztehFoOM0@1?Xk+A{K^rkOb@=dc%D1ext>mXkT6NUZwYixBFe_ zQ?c>nTy*R1s8k3xRp|AD*ncc{1lcrYa6Y$@3MEycHVhP#1F8Q>nOu#lU(ZGFUKWCs z_|>Ab1nmPOsKK^9J?J&-1X#S3D7G7gvPQ<0bu~G}M-)beRCj%XujZ?};df#Da3(w= z&?$4UqJ)UzoKYH_?SRyYR74bQ4bIj=B#U+_mNAK1H`<_JJ3E+-CH#Q@ww_wLcr?L zd~U-?Q9fS#hodCkhYhbDe4L;jW3@0|b%^PN%WH1mTw@8bq$jRkC9VX^Yns9Ig7}nU7^-MgGbpP7$@g<<+i{@ z2j+hcW4q?ORw~P5=Il&!`J5WU-jn;Ni{kz5ZGZIB&68Y=ha`Q`_^YC>X~>cgMPg-tN-G@s5zk?#o_Wqvd)(7%hkHKwl1) z$dGv56?+EH5##eWX7%^cIc32xlO6QcyjXi=FIef z2F7mtpZeH``7ydRHm~L~OU02%iP!uGMh*LTKC-PCnAUBU*JDmk6W z1K|WRAo;%LXjc~o;}p1KFAMH$p1#op$)7){lsJ*K0ubq#IFek}F7})91t~uKi*DcC zkl8BX`!Pg$5mDkH8Z?L zJHpEI_?4V^a%H~eZ0!|6U~y2uz{!%C1oQM~@|y08cfs(bJ5CEbS633uttJfZ_?+nl zhaP-onrye`{-5Pwt8m=Bnf!yt;#qc}CM^o@gg1aMO-8X7!BBNRL}Zz?&;`lQE|{i) zgV{5YiOmexiJ66%6b21Ae~h~kJTM9d{&j=dEveNrXw7#uh2k~(3LcZXZcU!Q=I7dU zGua_|$LWZ~YgYbAaR2&8MvQ!H_M=|n8uLWaP{LdTrTG*~qu1}Qhr*xzlCS{)^S=F> zC%?K)740pj`tLl$BiWvZvzw?C-!$2z#o2rKeq-MPilA;ZrRtM^XuS~s?iC=4m(vxU zlFgIMDriipkOQ9J7`$>X>8Yy-kEK8=B!7&POSYwT>GL0XP3o5LgM{1!*RbO)8{q)+ ze`-e#BCO2QhnIh;vNQTu71|;7xdxvkPW0IOUwi5&oPK}kSWx?KGbNzK=um$W$8-5} z(+SpBbNsFbRT;f=kJj-P9u2QU%$dMzUOI%SF5RWr?_i7d2FItI3^I*Xa834f z*&jQ4R1Nu4eerufRmSoWk4@f%+kK&fBWj>s@Fr+LA3PW_FNe^le)OCs7i?o+Ka;z`N0S(cofxHogPk%C;xoht+rg1$Hp*4cM|bG{qOAK!GS$qf(8|dP{$3boL%4cE!WNoIlaK6LYor&f;@i zB?>zzVb&JgT|-7})JW}>;7hada-=s&*K71?gVspD@+6X)Gjr{gZ?no^^lKhzPnBSM zn7qqJgo~$wTBW%MDo-Xtx9Z(j@?E@{ZYJOAHvZp6vEs|R;gO4HI{UW80RX$nX{o&R5Mb1Qh=SCYao}u6ghes zJ{|Xe)#RTjP&|@MTOC-^J?y&R0KWc=GyODfsYK?QvxFmQOotD_wgI(sIW2B=&bf?X zGyz|^qSV-aWpD|9$>lbCS9~#cAAQGDsJ+UdWl0JO^!SW%#o1CuHl#)~ud;2D)8%Ih z{T1i%Emx(LJI;2EZyitk-85=*O77&h96vsbBu+)v1R4&yn%Wrch}igAF7wP@pANVC zIs{knL?fOxpv&;=_BH0uU)62)VN+Mm1*l|>W8-pvdLRI=MjEAbb05zPS{+hzaa5UQ zKjlGw@Ptdkn&4ltw%Wp@7xH6uDj;bmwe4dH=um(5s(JF#{TNs7WZ>fj=U!Y^HlS4t z4TD)+u1-^f_=Xd{nbJymm))PLxUiO<+UH0Dd=m2P9YzFy=9}dhUchyWII0 zeoi0n5_V%Pzqa&^;>bC6RC{v&OLBjm#P#$BI>le)Aiwr&#)F{?kId7l3or@K2m!MP z=EIq*kwH%z_Z{xKhQ%)V0nM$(J=6kvkA53dg)jZM+hIt$A;pwwNr#MFwT55j1TP&Dvi&x=p6<9s2^T-Mhc;PC%BR8sSb)kxmYHla` z1)~P-X~A!XcyLs>0;hQ}*q72Q2=YXoVJV9vI<({U0Kbe;8KS9!dh4cGF)e0SII8>Z zje8~cQqb!t^@$r9sza5vYiLR$NXIijmh<_tWVu01p<{*3yc|i38&CwcRPXd03r8T5 z=DshrP>tX`#z(lJ6@0H)dC~iYWuIXz}FtDZVW4A@iVQCq_E@VjppSA zRY&%_1ruz1Vnm#_T*FkH2iU;4ayxw9n$A`*hX_ls`}3}(kY2INM|Qk6}hIW^QW2er7Fe_0mc)4J4U z!p3rTN)b&i1ico@_L4_nHjBG866r~6fn(xZ(H|}4VDWp)r6TG?!2Kjmci-XRNu@-Bi)m|^d>dU!L0heDcKcY<_!`SxqY}X zCoOt_aIe+E!51fjjO*ijqMsf+Zjl|97T)rsAwfkvIW+h%sJjl6CgX!mVd=4+)`q0g z;mp7*)-1`{AkP4~T-^^TkG1{g%+lq92L`57(WTyZeKlqZSERPv93=pJKmG-H%WHax z@tKBCkTx`UyU>kN?tziALOSQUyM8VkD+jhTP+TS417Nb^*+9#r4^ahG*^&yyWvLHw zkrgO$J)CyP0%?OLqbM!Tz+_)%msz><`y$m5!;f&SX!cGqXfECL0#}N>lP}VfL-7=_ zx~93!7ij#8g>dQgtkXa`jH*m>1xIf5&3V9R*}qT*`NzCv=Me{d_(N)~ZQnZ~&H4bQ z&coNV?+>;;zCb1n3bgI|Bb9y-f^G11o>9;9^5lJ6rbgMjOKSSN6lf@xP^*X)t5oxqu)bf!+Fvjnr}p zg_<|7{pWIyd%6E-^(ue*{;NF2bxpEw=ale&6cLJxjNV}j;jady;eSHQ{}WOE z|2O!5acFU5OymD;Mak%$80gMj|z5e@& Nsj-z&{jGaR{|{{gfH(jE literal 28913 zcmc$`1yEhh*X|h-5(vRvLxAA!4#C~s-8HxqEV#RSAh=5i?ryheuvQ>!-Xz4Z|6~)ay}kMOZkr*EUFex^or}qYZcL=z^Qb377k8r*rp52Sd)h zjG<@SoPqRrf`~{@FDw`=iBE!0FIe{HxBsX2t|)=$@Bgd!|Du`yH2+^U^Plhk51Ui` z)I$XkZ*&xqG=`PFB-aVQ{Y^ep-S5iq4lVpx0MTPPi|3$^MRkpoI|7m{*0w#)M?ylvuedq<>xQ8=Z*q@uD(Hr9;_3K_DIF|=%HR+`L!Ehbev?%W4FWHM zz(T|QT_2%N*%=7mF9qam7qm|4v0lh%JDU+laN|vH7mIRyh^BvrqnBNoqF4w6TY@1f z`l5`a)UO%73xMHG?>N^A3LbP}{>Xom`~%N`V|8-bwlwULGdt~VPpQe)(_O@&v|YXo zpgW5Uoy8+dYa)NM?p)wC6k|)icY*c9*yvJPbn#!x|Gk-(d>?&7q>r8(%Q?dWW;tFC zx1U~hI^?9$kIpMgB%-|Rj{R;DPn7PA%);nehnC5KU!I}`rf0&BHkpkO-G)Z+btuLg z0x*6;{l4rRhDx*9b+OW_x-E3w9)xpDUfI^ui+g|U$7+^UiofTdB83sZG-GII*g7Di zO&~nE75%2{csaXJn0czgx39{tGff=bE#xvzYb@Hv1tOc_zMFcc=2S7i|ESyf*_!Xk zuVC2~84O_vq=lMG{N6DlN30eE$nK4Q<#U`@7xbG_HV#RN~dt{sNgY&{EQ zX9{!5O3xB900zUL;@|^IhR3yh$(gfT-HYqYsaunY@*O7zB z?=~I3RBtK*3;3O-qmS~3QfA^v?CqnBQ;T}0+$Xraxe_wrU$8Nur7wNvk~g>1P;tZf za{Kn`5Z|NrmUzYzbM6hklMoSUQu3$VZa>I72W*|Afihd80aQZRC%oG*^mht1B>{N& zqD{MV_8tB-STe(st7<64BH3lHz8w!)k;|)MzoJzk7tHrm374&|LN)lo)OPhjuxq>e zacgkRk0$E=(Q*(=0?Xi9l&RDQfn~e()!||lmt*0F{M^>u%0DOX+$YA6c*7D1lOv*G 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 Nky5yqdFQG7{{c%u2zdYi From c3312c2da21c04b60b76299103dc3795f9b03956 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Tue, 20 Jul 2021 14:35:15 -0400 Subject: [PATCH 012/113] chore: remove the `TranslatedMessage` class and add the `i18n` field to the `Message` class Tests have been updated to reflect the removal of this class --- .../lib/src/core/api/responses.dart | 2 +- .../lib/src/core/api/responses.g.dart | 3 +- .../lib/src/core/models/message.dart | 41 +++++-------------- .../lib/src/core/models/message.g.dart | 17 ++------ .../lib/src/core/models/own_user.g.dart | 3 ++ .../test/src/api/channel_test.dart | 9 ++-- .../stream_chat/test/src/api/client_test.dart | 8 ++-- .../test/src/core/api/message_api_test.dart | 8 ++-- 8 files changed, 35 insertions(+), 56 deletions(-) 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/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index d394dbdd..0d2094cb 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,9 @@ class Message extends Equatable { @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime? deletedAt; + /// A Map of + final Map? i18n; + /// Known top level fields. /// Useful for [Serializer] methods. static const topLevelFields = [ @@ -248,6 +252,7 @@ class Message extends Equatable { 'pinned_at', 'pin_expires', 'pinned_by', + 'i18n', ]; /// Serialize to json @@ -285,6 +290,7 @@ class Message extends Equatable { User? pinnedBy, Map? extraData, MessageSendingStatus? status, + Map? i18n, }) { assert(() { if (pinExpires is! DateTime && @@ -324,6 +330,7 @@ class Message extends Equatable { pinnedBy: pinnedBy ?? this.pinnedBy, pinExpires: pinExpires == _pinExpires ? this.pinExpires : pinExpires as DateTime?, + i18n: i18n ?? this.i18n, ); } @@ -358,6 +365,7 @@ class Message extends Equatable { pinnedAt: other.pinnedAt, pinExpires: other.pinExpires, pinnedBy: other.pinnedBy, + i18n: other.i18n, ); @override @@ -390,35 +398,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..8ad1b3e1 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)); + val['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.g.dart b/packages/stream_chat/lib/src/core/models/own_user.g.dart index 26e4786e..465faff2 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 @@ -36,5 +36,8 @@ OwnUser _$OwnUserFromJson(Map json) { online: json['online'] as bool? ?? false, extraData: json['extra_data'] as Map? ?? {}, banned: json['banned'] as bool? ?? false, + teams: + (json['teams'] as List?)?.map((e) => e as String).toList() ?? + [], ); } 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..5df7406b 100644 --- a/packages/stream_chat/test/src/api/client_test.dart +++ b/packages/stream_chat/test/src/api/client_test.dart @@ -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, From 62b058e675bd7c44d3b20f528a9c10bb1538750a Mon Sep 17 00:00:00 2001 From: groovinchip Date: Tue, 20 Jul 2021 15:07:30 -0400 Subject: [PATCH 013/113] chore: add 'language' field to user.dart --- packages/stream_chat/lib/src/core/models/user.dart | 8 ++++++++ packages/stream_chat/lib/src/core/models/user.g.dart | 2 ++ 2 files changed, 10 insertions(+) diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index 1360d14e..87b4f9aa 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 = 'en', }) : createdAt = createdAt ?? DateTime.now(), updatedAt = updatedAt ?? DateTime.now(); @@ -82,6 +83,13 @@ class User extends Equatable { ) final Map extraData; + /// The language this user prefers. + /// + /// Defaults to 'en'. + @JsonKey( + includeIfNull: false, toJson: Serializer.readOnly, defaultValue: 'en') + final String? language; + @override int get hashCode => id.hashCode; 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..7b193f04 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? ?? 'en', ); } @@ -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', readonly(instance.language)); return val; } From c6dd3b0cd7aa254fbdd353e0aaed8ddc60830717 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Tue, 20 Jul 2021 15:13:58 -0400 Subject: [PATCH 014/113] test: add 'language' field test to user_test.dart --- packages/stream_chat/test/src/core/models/user_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/stream_chat/test/src/core/models/user_test.dart b/packages/stream_chat/test/src/core/models/user_test.dart index ce488cab..5319c02b 100644 --- a/packages/stream_chat/test/src/core/models/user_test.dart +++ b/packages/stream_chat/test/src/core/models/user_test.dart @@ -29,6 +29,7 @@ void main() { expect(newUser.id, user.id); expect(newUser.role, user.role); expect(newUser.name, user.name); + expect(newUser.language, user.language); newUser = user.copyWith( id: 'test', @@ -41,6 +42,7 @@ void main() { expect(newUser.id, 'test'); expect(newUser.role, 'test'); expect(newUser.name, 'test'); + expect(newUser.language, 'en'); }); }); } From 0943980b51ec265fbf4ef5de342295ace8eadda7 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Tue, 20 Jul 2021 15:15:09 -0400 Subject: [PATCH 015/113] test: add 'i18n' field test to message_test.dart --- packages/stream_chat/test/src/core/models/message_test.dart | 1 + 1 file changed, 1 insertion(+) 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', () { From 0210e2d49f5ce6309b2f02b8a5cababb776ac253 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Tue, 20 Jul 2021 15:19:37 -0400 Subject: [PATCH 016/113] chore: fill out message.i18n docs more --- packages/stream_chat/lib/src/core/models/message.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index 0d2094cb..7244f617 100644 --- a/packages/stream_chat/lib/src/core/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -219,7 +219,7 @@ class Message extends Equatable { @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final DateTime? deletedAt; - /// A Map of + /// A Map of translations. final Map? i18n; /// Known top level fields. From 247efb4e8f49b580036fd68a4a55c83011200cca Mon Sep 17 00:00:00 2001 From: groovinchip Date: Tue, 20 Jul 2021 15:22:15 -0400 Subject: [PATCH 017/113] chore: add missing JsonKey to Message.i18n --- packages/stream_chat/lib/src/core/models/message.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index 7244f617..d5435222 100644 --- a/packages/stream_chat/lib/src/core/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -220,6 +220,7 @@ class Message extends Equatable { final DateTime? deletedAt; /// A Map of translations. + @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final Map? i18n; /// Known top level fields. From 25aa6f2a905f60a54b893864772d7aec82329d9c Mon Sep 17 00:00:00 2001 From: groovinchip Date: Tue, 20 Jul 2021 15:23:25 -0400 Subject: [PATCH 018/113] chore: update CHANGELOG.md --- packages/stream_chat/CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index e05599e1..f4a6a136 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,13 @@ +## Upcoming + +🛑️ 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. + ## 2.0.0 🛑️ Breaking Changes from `1.5.3` From 9ea43f086985197f4d03691abdfde59fb401b274 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 21 Jul 2021 12:44:14 +0200 Subject: [PATCH 019/113] add localization scope in pr title --- .github/workflows/pr_title.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pr_title.yml b/.github/workflows/pr_title.yml index 732c4164..c6ab1635 100644 --- a/.github/workflows/pr_title.yml +++ b/.github/workflows/pr_title.yml @@ -20,6 +20,7 @@ jobs: core ui repo + localization requireScope: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From ad8ddba6a71ebb3c025edcff6d2b69bc321e9e7f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 21 Jul 2021 17:44:51 +0530 Subject: [PATCH 020/113] chore(ui,localization): add translations for `replyToMessageLabel` Signed-off-by: xsahil03x --- packages/stream_chat_flutter/example/lib/main.dart | 12 ++---------- .../lib/src/localization/translations.dart | 5 +++++ .../stream_chat_flutter/lib/src/message_input.dart | 6 +++--- .../lib/src/stream_chat_localizations_en.dart | 3 +++ .../lib/src/stream_chat_localizations_hi.dart | 3 +++ 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index d643dfea..f7609f6b 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -70,16 +70,8 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) => MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), - supportedLocales: const [ - Locale('en'), - Locale('hi'), - ], - localizationsDelegates: const [ - GlobalStreamChatLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], + supportedLocales: const [Locale('en'), Locale('hi')], + localizationsDelegates: GlobalStreamChatLocalizations.delegates, builder: (context, widget) => StreamChat( client: client, child: widget, diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 4344dc8a..7ce7a507 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -196,6 +196,8 @@ abstract class Translations { String get ofText; String get fileText; + + String get replyToMessageLabel; } class DefaultTranslations implements Translations { @@ -548,4 +550,7 @@ class DefaultTranslations implements Translations { @override String get fileText => 'File'; + + @override + String get replyToMessageLabel => 'Reply to Message'; } diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index e5e6ca48..9d9ada07 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -357,9 +357,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, 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 index 88b88840..2cd88989 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -351,4 +351,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { @override String get fileText => 'File'; + + @override + String get replyToMessageLabel => 'Reply to 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 index 5666b77d..c4c4a33a 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -350,4 +350,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { @override String get fileText => 'फ़ाइल'; + + @override + String get replyToMessageLabel => 'संदेश का जवाब'; } From b59b8a0050ff0454f508a1628613dc65a7429c0c Mon Sep 17 00:00:00 2001 From: Sacha Arbonel Date: Wed, 21 Jul 2021 09:51:39 -0400 Subject: [PATCH 021/113] add french translations --- .../lib/src/stream_chat_localizations.dart | 6 +- .../lib/src/stream_chat_localizations_fr.dart | 360 ++++++++++++++++++ 2 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart index 82662134..03147834 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -6,7 +6,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart' show StreamChatLocalizations, User; part 'stream_chat_localizations_en.dart'; - +part 'stream_chat_localizations_fr.dart'; part 'stream_chat_localizations_hi.dart'; /// The set of supported languages, as language code strings. @@ -17,7 +17,7 @@ part 'stream_chat_localizations_hi.dart'; /// See also: /// /// * [getStreamChatTranslation], whose documentation describes these values. -const kStreamChatSupportedLanguages = {'en', 'hi'}; +const kStreamChatSupportedLanguages = {'en', 'hi', 'fr'}; /// Creates a [GlobalStreamChatLocalizations] instance for the given `locale`. /// @@ -43,6 +43,8 @@ GlobalStreamChatLocalizations? getStreamChatTranslation(Locale locale) { return const StreamChatLocalizationsEn(); case 'hi': return const StreamChatLocalizationsHi(); + case 'fr': + return const StreamChatLocalizationsFr(); } } 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..c35fff27 --- /dev/null +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -0,0 +1,360 @@ +part of 'stream_chat_localizations.dart'; + +/// The translations for English (`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 d'actualité"; + + @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 messageReactionsText => '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 get fileTooLargeAfterCompressionError => + 'Le fichier est trop volumineux pour être téléchargé. ' + 'La taille maximale des fichiers est de 20 Mo. ' + "Nous avons essayé de le compresser, mais ce n'était pas suffisant."; + + @override + String get fileTooLargeError => + 'Le fichier est trop volumineux pour être téléchargé. La taille limite du fichier est de 20 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 somethingWentWrongLabel => '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 à une' + '\modé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 = Jiffy(DateTime.now()); + final date = Jiffy(dateTime); + + if (date.isSame(now, Units.DAY)) { + return "aujourd'hui"; + } else if (now.diff(date, Units.HOUR) < 24) { + return 'hier'; + } else { + return 'le ${date.MMMd}'; //hmm lookup french format d/MM/yyyy + } + } + + @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 searchingForNetworkLabel => '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'; +} From dbbd79b6f1d3549a30423c71e810e8ecde2c5e9c Mon Sep 17 00:00:00 2001 From: Sacha Arbonel Date: Wed, 21 Jul 2021 10:03:59 -0400 Subject: [PATCH 022/113] Update packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart typo Co-authored-by: Sahil Kumar --- .../lib/src/stream_chat_localizations_fr.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index c35fff27..98661a06 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -171,7 +171,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { @override String get flagMessageQuestion => 'Voulez-vous envoyer une copie de ce message à une' - '\modérateur pour une enquête plus approfondie ?'; + '\nmodérateur pour une enquête plus approfondie ?'; @override String get flagLabel => 'SIGNALER'; From f959a60dc426034fbe86c17d958021064e3b0750 Mon Sep 17 00:00:00 2001 From: Sacha Arbonel Date: Wed, 21 Jul 2021 10:05:10 -0400 Subject: [PATCH 023/113] more typos --- .../lib/src/stream_chat_localizations_fr.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 98661a06..e9576045 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -42,7 +42,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { @override String threadReplyCountText(int count) => - "$count Réponses au fil d'actualité"; + "$count Réponses au fil de discussion"; @override String attachmentsUploadProgressText({ @@ -170,7 +170,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { @override String get flagMessageQuestion => - 'Voulez-vous envoyer une copie de ce message à une' + 'Voulez-vous envoyer une copie de ce message à un' '\nmodérateur pour une enquête plus approfondie ?'; @override From 0abac8fc2788686d77105f20b6568b7ded640e54 Mon Sep 17 00:00:00 2001 From: Sacha Arbonel Date: Wed, 21 Jul 2021 10:06:35 -0400 Subject: [PATCH 024/113] clean up comment --- .../lib/src/stream_chat_localizations_fr.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index e9576045..3c26a071 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -239,7 +239,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { } else if (now.diff(date, Units.HOUR) < 24) { return 'hier'; } else { - return 'le ${date.MMMd}'; //hmm lookup french format d/MM/yyyy + return 'le ${date.MMMd}'; } } From 5097e4844b04ead4b0d2772190a67cfe66046087 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 21 Jul 2021 16:38:59 +0200 Subject: [PATCH 025/113] add italian translation --- .../lib/src/stream_chat_localizations.dart | 10 +- .../lib/src/stream_chat_localizations_hi.dart | 2 +- .../lib/src/stream_chat_localizations_it.dart | 358 ++++++++++++++++++ 3 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart index 03147834..d0b1f6dc 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -7,6 +7,7 @@ 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. @@ -17,7 +18,12 @@ part 'stream_chat_localizations_hi.dart'; /// See also: /// /// * [getStreamChatTranslation], whose documentation describes these values. -const kStreamChatSupportedLanguages = {'en', 'hi', 'fr'}; +const kStreamChatSupportedLanguages = { + 'en', + 'hi', + 'fr', + 'it', +}; /// Creates a [GlobalStreamChatLocalizations] instance for the given `locale`. /// @@ -45,6 +51,8 @@ GlobalStreamChatLocalizations? getStreamChatTranslation(Locale locale) { return const StreamChatLocalizationsHi(); case 'fr': return const StreamChatLocalizationsFr(); + case 'it': + return const StreamChatLocalizationsIt(); } } 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 index c4c4a33a..e32b4a4d 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -1,6 +1,6 @@ part of 'stream_chat_localizations.dart'; -/// The translations for English (`hi`). +/// The translations for Hindi (`hi`). class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { /// Create an instance of the translation bundle for Hindi. const StreamChatLocalizationsHi({String localeName = 'hi'}) 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..0edcbd0a --- /dev/null +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -0,0 +1,358 @@ +part of 'stream_chat_localizations.dart'; + +/// The translations for English (`hi`). +class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { + /// Create an instance of the translation bundle for Hindi. + 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 messageReactionsText => '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 get fileTooLargeAfterCompressionError => + 'Il file è troppo grande per essere caricato. ' + 'Il file eccede il limite di 20MB. ' + 'Abbiamo provato a comprimerlo, ma non è stato abbastanza.'; + + @override + String get fileTooLargeError => + 'Il file è troppo grande per essere caricato. Il limite è di 20MB.'; + + @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 somethingWentWrongLabel => '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 = Jiffy(DateTime.now()); + final date = Jiffy(dateTime); + + if (date.isSame(now, Units.DAY)) { + return 'oggi'; + } else if (now.diff(date, Units.HOUR) < 24) { + return 'ieri'; + } else { + return 'il ${date.MMMd}'; + } + } + + @override + String sentAtText({required DateTime date, required DateTime time}) => ''' +Inviato il ${_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 searchingForNetworkLabel => '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'; +} From f1df4389b59007c708d292715ace00b997129dbe Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 21 Jul 2021 16:49:40 +0200 Subject: [PATCH 026/113] add file size limit parameter --- .../stream_chat_flutter/example/lib/main.dart | 7 ++++++- .../lib/src/localization/translations.dart | 12 ++++++------ .../lib/src/message_input.dart | 16 ++++++++++++---- .../lib/src/stream_chat_localizations_en.dart | 8 ++++---- .../lib/src/stream_chat_localizations_fr.dart | 8 ++++---- .../lib/src/stream_chat_localizations_hi.dart | 8 ++++---- .../lib/src/stream_chat_localizations_it.dart | 8 ++++---- 7 files changed, 40 insertions(+), 27 deletions(-) diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index f7609f6b..b340ac28 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -70,7 +70,12 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) => MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), - supportedLocales: const [Locale('en'), Locale('hi')], + supportedLocales: const [ + Locale('en'), + Locale('hi'), + Locale('fr'), + Locale('it'), + ], localizationsDelegates: GlobalStreamChatLocalizations.delegates, builder: (context, widget) => StreamChat( client: client, diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 7ce7a507..6a7ac657 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -67,9 +67,9 @@ abstract class Translations { String get instantCommandsLabel; - String get fileTooLargeAfterCompressionError; + String fileTooLargeAfterCompressionError(double limitInMB); - String get fileTooLargeError; + String fileTooLargeError(double limitInMB); String emojiMatchingQueryText(String query); @@ -314,14 +314,14 @@ class DefaultTranslations implements Translations { String get instantCommandsLabel => 'Instant Commands'; @override - String get fileTooLargeAfterCompressionError => + String fileTooLargeAfterCompressionError(double limitInMB) => 'The file is too large to upload. ' - 'The file size limit is 20MB. ' + 'The file size limit is $limitInMB MB. ' 'We tried compressing it, but it was not enough.'; @override - String get fileTooLargeError => - 'The file is too large to upload. The file size limit is 20MB.'; + 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"'; diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 9d9ada07..7843e77e 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1144,7 +1144,9 @@ class MessageInputState extends State { if (mediaInfo.filesize! > widget.maxAttachmentSize) { _showErrorAlert( - context.translations.fileTooLargeAfterCompressionError, + context.translations.fileTooLargeAfterCompressionError( + widget.maxAttachmentSize / (1024 * 1024), + ), ); return; } @@ -1155,7 +1157,9 @@ class MessageInputState extends State { path: mediaInfo.path, ); } else { - _showErrorAlert(context.translations.fileTooLargeError); + _showErrorAlert(context.translations.fileTooLargeError( + widget.maxAttachmentSize / (1024 * 1024), + )); return; } } @@ -1922,7 +1926,9 @@ class MessageInputState extends State { if (mediaInfo.filesize! > widget.maxAttachmentSize) { _showErrorAlert( - context.translations.fileTooLargeAfterCompressionError, + context.translations.fileTooLargeAfterCompressionError( + widget.maxAttachmentSize / (1024 * 1024), + ), ); return; } @@ -1933,7 +1939,9 @@ class MessageInputState extends State { path: mediaInfo.path, ); } else { - _showErrorAlert(context.translations.fileTooLargeError); + _showErrorAlert(context.translations.fileTooLargeError( + widget.maxAttachmentSize / (1024 * 1024), + )); return; } } 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 index 2cd88989..ba515e2f 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -115,14 +115,14 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get instantCommandsLabel => 'Instant Commands'; @override - String get fileTooLargeAfterCompressionError => + String fileTooLargeAfterCompressionError(double limitInMB) => 'The file is too large to upload. ' - 'The file size limit is 20MB. ' + 'The file size limit is $limitInMB MB. ' 'We tried compressing it, but it was not enough.'; @override - String get fileTooLargeError => - 'The file is too large to upload. The file size limit is 20MB.'; + 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"'; 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 index 3c26a071..297fbc1a 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -117,14 +117,14 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get instantCommandsLabel => 'Commandes instantanées'; @override - String get fileTooLargeAfterCompressionError => + String fileTooLargeAfterCompressionError(double limitInMB) => 'Le fichier est trop volumineux pour être téléchargé. ' - 'La taille maximale des fichiers est de 20 Mo. ' + 'La taille maximale des fichiers est de $limitInMB Mo. ' "Nous avons essayé de le compresser, mais ce n'était pas suffisant."; @override - String get fileTooLargeError => - 'Le fichier est trop volumineux pour être téléchargé. La taille limite du fichier est de 20 Mo.'; + 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) => 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 index e32b4a4d..c6e94e32 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -115,14 +115,14 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get instantCommandsLabel => 'तत्काल आदेश'; @override - String get fileTooLargeAfterCompressionError => + String fileTooLargeAfterCompressionError(double limitInMB) => 'फ़ाइल अपलोड करने के लिए बहुत बड़ी है। ' - 'फ़ाइल आकार सीमा 20MB है। ' + 'फ़ाइल आकार सीमा $limitInMB MB है। ' 'हमने इसे कंप्रेस करने की कोशिश की, लेकिन यह काफी नहीं था।'; @override - String get fileTooLargeError => - 'फ़ाइल अपलोड करने के लिए बहुत बड़ी है। फ़ाइल आकार सीमा 20MB है।'; + String fileTooLargeError(double limitInMB) => + 'फ़ाइल अपलोड करने के लिए बहुत बड़ी है। फ़ाइल आकार सीमा $limitInMB MB है।'; @override String emojiMatchingQueryText(String query) => '"$query" से मिलते हुए इमोजी'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart index 0edcbd0a..8797de27 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -117,14 +117,14 @@ class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { String get instantCommandsLabel => 'Commandi istantanei'; @override - String get fileTooLargeAfterCompressionError => + String fileTooLargeAfterCompressionError(double limitInMB) => 'Il file è troppo grande per essere caricato. ' - 'Il file eccede il limite di 20MB. ' + 'Il file eccede il limite di $limitInMB MB. ' 'Abbiamo provato a comprimerlo, ma non è stato abbastanza.'; @override - String get fileTooLargeError => - 'Il file è troppo grande per essere caricato. Il limite è di 20MB.'; + String fileTooLargeError(double limitInMB) => ''' +Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; @override String emojiMatchingQueryText(String query) => 'Emoji per "$query"'; From c88a9a5c04eb557eae78ff79d98d983266b9a983 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 21 Jul 2021 16:53:26 +0200 Subject: [PATCH 027/113] use pub stream_chat_flutter dependency --- packages/stream_chat_localizations/pubspec.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index b755a6dd..ac999a9c 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -12,8 +12,7 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - stream_chat_flutter: - path: ../stream_chat_flutter + stream_chat_flutter: ^2.0.0 dev_dependencies: flutter_test: From df834f4c8b5209db02003209f26638bc36808df1 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 21 Jul 2021 21:11:08 +0530 Subject: [PATCH 028/113] test(localization): add unit tests Signed-off-by: xsahil03x --- .../test/basics_test.dart | 109 +++++++ .../test/override_test.dart | 276 ++++++++++++++++++ .../test/stream_chat_localization_test.dart | 20 -- .../test/translations_test.dart | 164 +++++++++++ 4 files changed, 549 insertions(+), 20 deletions(-) create mode 100644 packages/stream_chat_localizations/test/basics_test.dart create mode 100644 packages/stream_chat_localizations/test/override_test.dart delete mode 100644 packages/stream_chat_localizations/test/stream_chat_localization_test.dart create mode 100644 packages/stream_chat_localizations/test/translations_test.dart 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 [ + Locale('en', 'US'), + Locale('hi', 'IN'), + ], + localizationsDelegates: const [ + DummyLocalizations.delegate, + GlobalStreamChatLocalizations.delegate, + ], + home: PageView(), + )); + + await tester.binding.setLocale('hi', 'IN'); + await tester.pump(); + await tester.pumpWidget(Container()); + }, + ); + + testWidgets('Locale without countryCode', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/pull/16782 + await tester.pumpWidget(MaterialApp( + localizationsDelegates: const >[ + GlobalStreamChatLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en', 'US'), + Locale('hi'), + ], + home: Container(), + )); + + await tester.binding.setLocale('hi', ''); + await tester.pump(); + await tester.binding.setLocale('en', 'US'); + await tester.pump(); + }); +} + +/// A localizations delegate that does not contain any useful data, and is only +/// used to trigger didChangeDependencies upon locale change. +class _DummyLocalizationsDelegate + extends LocalizationsDelegate { + const _DummyLocalizationsDelegate(); + + @override + Future load(Locale locale) async => DummyLocalizations(); + + @override + bool isSupported(Locale locale) => true; + + @override + bool shouldReload(_DummyLocalizationsDelegate old) => true; +} + +class DummyLocalizations { + static const delegate = _DummyLocalizationsDelegate(); +} + +class LocalizationTracker extends StatefulWidget { + const LocalizationTracker({Key? key}) : super(key: key); + + @override + State createState() => LocalizationTrackerState(); +} + +class LocalizationTrackerState extends State { + late double captionFontSize; + + @override + Widget build(BuildContext context) { + captionFontSize = Theme.of(context).textTheme.caption!.fontSize!; + return Container(); + } +} diff --git a/packages/stream_chat_localizations/test/override_test.dart b/packages/stream_chat_localizations/test/override_test.dart new file mode 100644 index 00000000..3e134b6f --- /dev/null +++ b/packages/stream_chat_localizations/test/override_test.dart @@ -0,0 +1,276 @@ +// ignore_for_file: prefer_expression_function_bodies + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_localizations/src/stream_chat_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class FooStreamChatLocalizations extends StreamChatLocalizationsEn { + FooStreamChatLocalizations( + Locale localeName, + this.launchUrlError, + ) : super(localeName: localeName.toString()); + + @override + final String launchUrlError; +} + +class FooStreamChatLocalizationsDelegate + extends LocalizationsDelegate { + const FooStreamChatLocalizationsDelegate({ + this.supportedLanguage = 'en', + this.launchUrlError = 'foo', + }); + + final String supportedLanguage; + final String launchUrlError; + + @override + bool isSupported(Locale locale) => + supportedLanguage == 'allLanguages' || + locale.languageCode == supportedLanguage; + + @override + Future load(Locale locale) => + SynchronousFuture( + FooStreamChatLocalizations(locale, launchUrlError), + ); + + @override + bool shouldReload(FooStreamChatLocalizationsDelegate old) => false; +} + +Widget buildFrame({ + Locale? locale, + Iterable delegates = + GlobalStreamChatLocalizations.delegates, + required WidgetBuilder buildContent, + LocaleResolutionCallback? localeResolutionCallback, + Iterable supportedLocales = const [ + Locale('en', 'US'), + Locale('hi', 'IN'), + ], +}) => + MaterialApp( + color: const Color(0xFFFFFFFF), + locale: locale, + supportedLocales: supportedLocales, + localizationsDelegates: delegates, + localeResolutionCallback: localeResolutionCallback, + onGenerateRoute: (RouteSettings settings) => MaterialPageRoute( + builder: (BuildContext context) => buildContent(context)), + ); + +void main() { + testWidgets( + 'Locale fallbacks', + (WidgetTester tester) async { + final Key textKey = UniqueKey(); + + await tester.pumpWidget( + buildFrame( + buildContent: (BuildContext context) => Text( + StreamChatLocalizations.of(context)!.launchUrlError, + key: textKey, + ), + ), + ); + + expect( + tester.widget(find.byKey(textKey)).data, + 'Cannot launch the url', + ); + + // Unrecognized locale falls back to 'en' + await tester.binding.setLocale('foo', 'BAR'); + await tester.pump(); + expect( + tester.widget(find.byKey(textKey)).data, + 'Cannot launch the url', + ); + + // Indian hindi locale, falls back to just 'hi' + await tester.binding.setLocale('hi', 'IN'); + await tester.pump(); + expect( + tester.widget(find.byKey(textKey)).data, + 'यूआरएल लॉन्च नहीं कर सकते', + ); + }, + ); + + testWidgets( + "Localizations.override widget tracks parent's locale", + (WidgetTester tester) async { + Widget buildLocaleFrame(Locale locale) => buildFrame( + locale: locale, + supportedLocales: [locale], + buildContent: (BuildContext context) => Localizations.override( + context: context, + child: Builder( + builder: (BuildContext context) { + // No StreamChatLocalizations are defined for the first + // Localizations ancestor, so we should get the values from + // the default one, i.e. the one created by WidgetsApp via + // the LocalizationsDelegate provided by MaterialApp. + return Text( + StreamChatLocalizations.of(context)!.launchUrlError, + ); + }, + ), + ), + ); + + await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US'))); + expect(find.text('Cannot launch the url'), findsOneWidget); + + await tester.pumpWidget(buildLocaleFrame(const Locale('hi', 'IN'))); + expect(find.text('यूआरएल लॉन्च नहीं कर सकते'), findsOneWidget); + }, + ); + + testWidgets('Localizations.override widget with hardwired locale', + (WidgetTester tester) async { + Widget buildLocaleFrame(Locale locale) => buildFrame( + locale: locale, + buildContent: (BuildContext context) { + return Localizations.override( + context: context, + locale: const Locale('en', 'US'), + child: Builder( + builder: (BuildContext context) { + // No StreamChatLocalizations are defined for the first + // Localizations ancestor, so we should get the values from + // the default one, i.e. the one created by WidgetsApp via + // the LocalizationsDelegate provided by MaterialApp. + return Text( + StreamChatLocalizations.of(context)!.launchUrlError, + ); + }, + ), + ); + }, + ); + + await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US'))); + expect(find.text('Cannot launch the url'), findsOneWidget); + + await tester.pumpWidget(buildLocaleFrame(const Locale('hi', 'IN'))); + expect(find.text('Cannot launch the url'), findsOneWidget); + }); + + testWidgets( + 'MaterialApp adds StreamChatLocalizations for additional languages', + (WidgetTester tester) async { + final Key textKey = UniqueKey(); + + await tester.pumpWidget(buildFrame( + delegates: >[ + GlobalStreamChatLocalizations.delegate, + const FooStreamChatLocalizationsDelegate( + supportedLanguage: 'fr', + launchUrlError: "Impossible de lancer l'url", + ), + const FooStreamChatLocalizationsDelegate( + supportedLanguage: 'de', + launchUrlError: 'Kann die URL nicht starten', + ), + ], + supportedLocales: const [ + Locale('en'), + Locale('hi'), + Locale('fr'), + Locale('de'), + ], + buildContent: (BuildContext context) => Text( + StreamChatLocalizations.of(context)!.launchUrlError, + key: textKey, + ), + )); + + expect( + tester.widget(find.byKey(textKey)).data, + 'Cannot launch the url', + ); + + await tester.binding.setLocale('hi', 'IN'); + await tester.pump(); + expect(find.text('यूआरएल लॉन्च नहीं कर सकते'), findsOneWidget); + + await tester.binding.setLocale('fr', 'CA'); + await tester.pump(); + expect(find.text("Impossible de lancer l'url"), findsOneWidget); + + await tester.binding.setLocale('de', 'DE'); + await tester.pump(); + expect(find.text('Kann die URL nicht starten'), findsOneWidget); + }, + ); + + testWidgets( + 'MaterialApp overrides MaterialLocalizations for all locales', + (WidgetTester tester) async { + final Key textKey = UniqueKey(); + + await tester.pumpWidget(buildFrame( + // Accept whatever locale we're given + localeResolutionCallback: + (Locale? locale, Iterable supportedLocales) => locale, + delegates: [ + const FooStreamChatLocalizationsDelegate( + supportedLanguage: 'allLanguages', + ), + ], + buildContent: (BuildContext context) { + // Should always be 'foo', no matter what the locale is + return Text( + StreamChatLocalizations.of(context)!.launchUrlError, + key: textKey, + ); + }, + )); + + expect(tester.widget(find.byKey(textKey)).data, 'foo'); + + await tester.binding.setLocale('zh', 'CN'); + await tester.pump(); + expect(find.text('foo'), findsOneWidget); + + await tester.binding.setLocale('de', 'DE'); + await tester.pump(); + expect(find.text('foo'), findsOneWidget); + }, + ); + + testWidgets( + 'MaterialApp overrides MaterialLocalizations for default locale', + (WidgetTester tester) async { + final Key textKey = UniqueKey(); + + await tester.pumpWidget(buildFrame( + delegates: [ + const FooStreamChatLocalizationsDelegate(), + ], + // supportedLocales not specified, so all locales resolve to 'en' + buildContent: (BuildContext context) => Text( + StreamChatLocalizations.of(context)!.launchUrlError, + key: textKey, + ), + )); + + // Unsupported locale '_' (the widget tester's default) resolves to 'en'. + expect(tester.widget(find.byKey(textKey)).data, 'foo'); + + // Unsupported locale 'zh' resolves to 'en'. + await tester.binding.setLocale('zh', 'CN'); + await tester.pump(); + expect(find.text('foo'), findsOneWidget); + + // Unsupported locale 'de' resolves to 'en'. + await tester.binding.setLocale('de', 'DE'); + await tester.pump(); + expect(find.text('foo'), findsOneWidget); + }, + ); +} diff --git a/packages/stream_chat_localizations/test/stream_chat_localization_test.dart b/packages/stream_chat_localizations/test/stream_chat_localization_test.dart deleted file mode 100644 index 0324dd17..00000000 --- a/packages/stream_chat_localizations/test/stream_chat_localization_test.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter_test/flutter_test.dart'; - -import 'package:stream_chat_localizations/stream_chat_localizations.dart'; - -void main() { - for (final language in kStreamChatSupportedLanguages) { - test('translations exist for $language', () async { - final locale = Locale(language); - expect( - GlobalStreamChatLocalizations.delegate.isSupported(locale), - isTrue, - ); - final localizations = - await GlobalStreamChatLocalizations.delegate.load(locale); - expect(localizations.launchUrlError, isNotNull); - }); - } -} diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart new file mode 100644 index 00000000..6e796d08 --- /dev/null +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -0,0 +1,164 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'package:stream_chat_localizations/stream_chat_localizations.dart'; + +void main() { + for (final language in kStreamChatSupportedLanguages) { + test('translations exist for $language', () async { + final locale = Locale(language); + expect( + GlobalStreamChatLocalizations.delegate.isSupported(locale), isTrue); + final localizations = + await GlobalStreamChatLocalizations.delegate.load(locale); + expect(localizations.launchUrlError, isNotNull); + expect(localizations.loadingUsersError, isNotNull); + expect(localizations.noUsersLabel, isNotNull); + expect(localizations.retryLabel, isNotNull); + expect(localizations.userLastOnlineText, isNotNull); + expect(localizations.userOnlineText, isNotNull); + expect(localizations.userOnlineText, isNotNull); + // no users + expect(localizations.userTypingText([]), isNotNull); + // single user + expect(localizations.userTypingText([User(id: 'test-id')]), isNotNull); + // multiple users + expect( + localizations.userTypingText([ + User(id: 'test-id-1'), + User(id: 'test-id-2'), + ]), + isNotNull, + ); + expect(localizations.threadReplyLabel, isNotNull); + expect(localizations.onlyVisibleToYouText, isNotNull); + expect(localizations.threadReplyCountText(3), isNotNull); + expect( + localizations.attachmentsUploadProgressText(remaining: 3, total: 10), + isNotNull, + ); + expect( + localizations.pinnedByUserText( + pinnedBy: User(id: 'pinned-by-user-id'), + currentUser: OwnUser(id: 'current-user-id'), + ), + isNotNull, + ); + expect(localizations.emptyMessagesText, isNotNull); + expect(localizations.genericErrorText, isNotNull); + expect(localizations.loadingMessagesError, isNotNull); + expect(localizations.resultCountText(3), isNotNull); + expect(localizations.messageDeletedText, isNotNull); + expect(localizations.messageDeletedLabel, isNotNull); + expect(localizations.messageReactionsText, isNotNull); + expect(localizations.emptyChatMessagesText, isNotNull); + expect(localizations.threadSeparatorText(3), isNotNull); + expect(localizations.connectedLabel, isNotNull); + expect(localizations.disconnectedLabel, isNotNull); + expect(localizations.reconnectingLabel, isNotNull); + expect(localizations.alsoSendAsDirectMessageLabel, isNotNull); + expect(localizations.addACommentOrSendLabel, isNotNull); + expect(localizations.searchGifLabel, isNotNull); + expect(localizations.writeAMessageLabel, isNotNull); + expect(localizations.instantCommandsLabel, isNotNull); + expect(localizations.fileTooLargeAfterCompressionError, isNotNull); + expect(localizations.fileTooLargeError, isNotNull); + expect(localizations.emojiMatchingQueryText('sahil'), isNotNull); + expect(localizations.addAFileLabel, isNotNull); + expect(localizations.photoFromCameraLabel, isNotNull); + expect(localizations.uploadAFileLabel, isNotNull); + expect(localizations.uploadAPhotoLabel, isNotNull); + expect(localizations.uploadAVideoLabel, isNotNull); + expect(localizations.videoFromCameraLabel, isNotNull); + expect(localizations.okLabel, isNotNull); + expect(localizations.somethingWentWrongLabel, isNotNull); + expect(localizations.addMoreFilesLabel, isNotNull); + expect(localizations.enablePhotoAndVideoAccessMessage, isNotNull); + expect(localizations.allowGalleryAccessMessage, isNotNull); + expect(localizations.flagMessageLabel, isNotNull); + expect(localizations.flagMessageQuestion, isNotNull); + expect(localizations.flagLabel, isNotNull); + expect(localizations.cancelLabel, isNotNull); + expect(localizations.flagMessageSuccessfulLabel, isNotNull); + expect(localizations.flagMessageSuccessfulText, isNotNull); + expect(localizations.deleteLabel, isNotNull); + expect(localizations.deleteMessageLabel, isNotNull); + expect(localizations.deleteMessageQuestion, isNotNull); + expect(localizations.operationCouldNotBeCompletedText, isNotNull); + expect(localizations.replyLabel, isNotNull); + // pinned + expect(localizations.togglePinUnpinText(pinned: true), isNotNull); + // un-pinned + expect(localizations.togglePinUnpinText(pinned: false), isNotNull); + // delete-failed + expect( + localizations.toggleDeleteRetryDeleteMessageText(isDeleteFailed: true), + isNotNull, + ); + // first-delete + expect( + localizations.toggleDeleteRetryDeleteMessageText(isDeleteFailed: false), + isNotNull, + ); + expect(localizations.copyMessageLabel, isNotNull); + expect(localizations.editMessageLabel, isNotNull); + // resend-failed + expect( + localizations.toggleResendOrResendEditedMessage(isUpdateFailed: true), + isNotNull, + ); + // first resend + expect( + localizations.toggleResendOrResendEditedMessage(isUpdateFailed: false), + isNotNull, + ); + expect(localizations.photosLabel, isNotNull); + expect( + localizations.sentAtText( + date: DateTime.now(), + time: DateTime.now(), + ), + isNotNull, + ); + expect(localizations.todayLabel, isNotNull); + expect(localizations.yesterdayLabel, isNotNull); + expect(localizations.channelIsMutedText, isNotNull); + expect(localizations.noTitleText, isNotNull); + expect(localizations.letsStartChattingLabel, isNotNull); + expect(localizations.sendingFirstMessageLabel, isNotNull); + expect(localizations.startAChatLabel, isNotNull); + expect(localizations.loadingChannelsError, isNotNull); + expect(localizations.deleteConversationQuestion, isNotNull); + expect(localizations.streamChatLabel, isNotNull); + expect(localizations.searchingForNetworkLabel, isNotNull); + expect(localizations.offlineLabel, isNotNull); + expect(localizations.tryAgainLabel, isNotNull); + // 1 member + expect(localizations.membersCountText(1), isNotNull); + // 3 members + expect(localizations.membersCountText(3), isNotNull); + // 1 member + expect(localizations.watchersCountText(1), isNotNull); + // 3 members + expect(localizations.watchersCountText(3), isNotNull); + expect(localizations.viewInfoLabel, isNotNull); + expect(localizations.leaveGroupLabel, isNotNull); + expect(localizations.leaveLabel, isNotNull); + expect(localizations.leaveConversationLabel, isNotNull); + expect(localizations.leaveConversationQuestion, isNotNull); + expect(localizations.showInChatLabel, isNotNull); + expect(localizations.saveVideoLabel, isNotNull); + expect(localizations.uploadErrorLabel, isNotNull); + expect(localizations.giphyLabel, isNotNull); + expect(localizations.shuffleLabel, isNotNull); + expect(localizations.sendLabel, isNotNull); + expect(localizations.withText, isNotNull); + expect(localizations.inText, isNotNull); + expect(localizations.youText, isNotNull); + expect(localizations.ofText, isNotNull); + expect(localizations.fileText, isNotNull); + expect(localizations.replyToMessageLabel, isNotNull); + }); + } +} From f109a479091cb306a6fb53fffeacb642fac05736 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Wed, 21 Jul 2021 12:28:04 -0400 Subject: [PATCH 029/113] fix: JsonKey for User.language --- packages/stream_chat/lib/src/core/models/user.dart | 3 +-- packages/stream_chat/lib/src/core/models/user.g.dart | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index 87b4f9aa..bd4bf226 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -86,8 +86,7 @@ class User extends Equatable { /// The language this user prefers. /// /// Defaults to 'en'. - @JsonKey( - includeIfNull: false, toJson: Serializer.readOnly, defaultValue: 'en') + @JsonKey(defaultValue: 'en') final String? language; @override 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 7b193f04..85fdfef9 100644 --- a/packages/stream_chat/lib/src/core/models/user.g.dart +++ b/packages/stream_chat/lib/src/core/models/user.g.dart @@ -48,6 +48,6 @@ Map _$UserToJson(User instance) { writeNotNull('online', readonly(instance.online)); writeNotNull('banned', readonly(instance.banned)); val['extra_data'] = instance.extraData; - writeNotNull('language', readonly(instance.language)); + val['language'] = instance.language; return val; } From 3e552ab4f62db9fd8d54a68db97e3faa68ac0413 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 22 Jul 2021 17:18:51 +0530 Subject: [PATCH 030/113] chore(ui, localization): add doc-comments Signed-off-by: xsahil03x --- .../stream_chat_flutter/example/lib/main.dart | 1 - .../lib/src/channel_info.dart | 2 +- .../lib/src/channel_list_header.dart | 2 +- .../lib/src/extension.dart | 2 + .../stream_chat_localizations.dart | 2 +- .../lib/src/localization/translations.dart | 120 +++++++++++++++++- .../lib/src/message_actions_modal.dart | 2 +- .../lib/src/message_input.dart | 2 +- .../lib/src/message_reactions_modal.dart | 2 +- .../src/attachment_actions_modal_test.dart | 1 - .../lib/src/stream_chat_localizations.dart | 15 ++- .../lib/src/stream_chat_localizations_en.dart | 6 +- .../lib/src/stream_chat_localizations_fr.dart | 13 +- .../lib/src/stream_chat_localizations_hi.dart | 6 +- .../lib/src/stream_chat_localizations_it.dart | 10 +- .../test/translations_test.dart | 6 +- 16 files changed, 153 insertions(+), 39 deletions(-) diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index b340ac28..c2663a6a 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -1,7 +1,6 @@ import 'package:flutter/cupertino.dart'; 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'; import 'package:stream_chat_localizations/stream_chat_localizations.dart'; diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index 4e860745..e4226b72 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -116,7 +116,7 @@ class ChannelInfo extends StatelessWidget { ), const SizedBox(width: 10), Text( - context.translations.searchingForNetworkLabel, + context.translations.searchingForNetworkText, style: textStyle, ), ], 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 6ef3ba10..3c7024ed 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -226,7 +226,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { ), const SizedBox(width: 10), Text( - context.translations.searchingForNetworkLabel, + context.translations.searchingForNetworkText, style: StreamChatTheme.of(context) .channelListHeaderTheme .title diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index 5d67ee50..b975941e 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -106,6 +106,8 @@ extension BuildContextX on BuildContext { 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; } 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 index b2a1bd5e..e9f8cf4b 100644 --- a/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart @@ -7,7 +7,7 @@ import 'package:stream_chat_flutter/src/localization/translations.dart' /// /// See also: /// -/// * [GlobalStreamChatLocalizations], which provides material localizations +/// * [GlobalStreamChatLocalizations], which provides stream chat localizations /// for many languages. abstract class StreamChatLocalizations implements Translations { /// The `StreamChatLocalizations` from the closest [Localizations] instance diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 6a7ac657..17355b6f 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -1,208 +1,316 @@ 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; - String get messageReactionsText; + /// 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; - String get somethingWentWrongLabel; + /// 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; - String get searchingForNetworkLabel; + /// 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 @@ -278,7 +386,7 @@ class DefaultTranslations implements Translations { String get messageDeletedLabel => 'Message deleted'; @override - String get messageReactionsText => 'Message Reactions'; + String get messageReactionsLabel => 'Message Reactions'; @override String get emptyChatMessagesText => 'No chats here yet...'; @@ -348,7 +456,7 @@ class DefaultTranslations implements Translations { String get okLabel => 'OK'; @override - String get somethingWentWrongLabel => 'Something went wrong'; + String get somethingWentWrongError => 'Something went wrong'; @override String get addMoreFilesLabel => 'Add more files'; @@ -479,7 +587,7 @@ class DefaultTranslations implements Translations { String get streamChatLabel => 'Stream Chat'; @override - String get searchingForNetworkLabel => 'Searching for Network'; + String get searchingForNetworkText => 'Searching for Network'; @override String get offlineLabel => 'Offline...'; 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 3e0afe75..2e9659fa 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -365,7 +365,7 @@ class _MessageActionsModalState extends State { size: 24, ), details: context.translations.operationCouldNotBeCompletedText, - title: context.translations.somethingWentWrongLabel, + title: context.translations.somethingWentWrongError, okText: context.translations.okLabel, ); } diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 7843e77e..feb5bd6d 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -2121,7 +2121,7 @@ class MessageInputState extends State { height: 26, ), Text( - context.translations.somethingWentWrongLabel, + context.translations.somethingWentWrongError, style: _streamChatTheme.textTheme.headlineBold, ), const SizedBox( 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 7926dcab..9bd518ce 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -155,7 +155,7 @@ class MessageReactionsModal extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - context.translations.messageReactionsText, + context.translations.messageReactionsLabel, style: chatThemeData.textTheme.headlineBold, ), const SizedBox(height: 16), 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 59a49ac7..3f210742 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 @@ -7,7 +7,6 @@ import 'package:stream_chat_flutter/src/attachment_actions_modal.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; class MockAttachmentDownloader extends Mock { ProgressCallback? progressCallback; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart index d0b1f6dc..bb2d791f 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -6,8 +6,11 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart' show StreamChatLocalizations, User; 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. @@ -28,8 +31,8 @@ const kStreamChatSupportedLanguages = { /// 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 +/// [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: @@ -92,13 +95,15 @@ abstract class 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. + /// 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(); 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 index ba515e2f..ad58b89a 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -79,7 +79,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get messageDeletedLabel => 'Message deleted'; @override - String get messageReactionsText => 'Message Reactions'; + String get messageReactionsLabel => 'Message Reactions'; @override String get emptyChatMessagesText => 'No chats here yet...'; @@ -149,7 +149,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get okLabel => 'OK'; @override - String get somethingWentWrongLabel => 'Something went wrong'; + String get somethingWentWrongError => 'Something went wrong'; @override String get addMoreFilesLabel => 'Add more files'; @@ -280,7 +280,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get streamChatLabel => 'Stream Chat'; @override - String get searchingForNetworkLabel => 'Searching for Network'; + String get searchingForNetworkText => 'Searching for Network'; @override String get offlineLabel => 'Offline...'; 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 index 297fbc1a..ba8511a0 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -1,6 +1,6 @@ part of 'stream_chat_localizations.dart'; -/// The translations for English (`fr`). +/// The translations for French (`fr`). class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { /// Create an instance of the translation bundle for French. const StreamChatLocalizationsFr({String localeName = 'fr'}) @@ -42,7 +42,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { @override String threadReplyCountText(int count) => - "$count Réponses au fil de discussion"; + '$count Réponses au fil de discussion'; @override String attachmentsUploadProgressText({ @@ -80,7 +80,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get messageDeletedLabel => 'Message supprimé'; @override - String get messageReactionsText => 'Réactions aux messages'; + String get messageReactionsLabel => 'Réactions aux messages'; @override String get emptyChatMessagesText => 'Pas encore de chats ici...'; @@ -124,7 +124,8 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { @override String fileTooLargeError(double limitInMB) => - 'Le fichier est trop volumineux pour être téléchargé. La taille limite du fichier est de $limitInMB Mo.'; + 'Le fichier est trop volumineux pour être téléchargé. ' + 'La taille limite du fichier est de $limitInMB Mo.'; @override String emojiMatchingQueryText(String query) => @@ -152,7 +153,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get okLabel => 'OK'; @override - String get somethingWentWrongLabel => 'Quelque chose a mal tourné'; + String get somethingWentWrongError => 'Quelque chose a mal tourné'; @override String get addMoreFilesLabel => "Ajouter d'autres fichiers"; @@ -283,7 +284,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get streamChatLabel => 'Stream Chat'; @override - String get searchingForNetworkLabel => 'Recherche de réseau'; + String get searchingForNetworkText => 'Recherche de réseau'; @override String get offlineLabel => 'Hors ligne...'; 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 index c6e94e32..93a5ca30 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -79,7 +79,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get messageDeletedLabel => 'संदेश हटाये'; @override - String get messageReactionsText => 'संदेश प्रतिक्रियाएं'; + String get messageReactionsLabel => 'संदेश प्रतिक्रियाएं'; @override String get emptyChatMessagesText => 'यहां अभी तक कोई चैट नहीं...'; @@ -149,7 +149,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get okLabel => 'ठीक'; @override - String get somethingWentWrongLabel => 'लोड करने में समस्या'; + String get somethingWentWrongError => 'लोड करने में समस्या'; @override String get addMoreFilesLabel => 'और फ़ाइलें जोड़ें'; @@ -279,7 +279,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get streamChatLabel => 'स्ट्रीम चैट'; @override - String get searchingForNetworkLabel => 'नेटवर्क खोज रहे हैं'; + String get searchingForNetworkText => 'नेटवर्क खोज रहे हैं'; @override String get offlineLabel => 'ऑफलाइन...'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart index 8797de27..ce5faf55 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -1,8 +1,8 @@ part of 'stream_chat_localizations.dart'; -/// The translations for English (`hi`). +/// The translations for Italian (`it`). class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { - /// Create an instance of the translation bundle for Hindi. + /// Create an instance of the translation bundle for Italian. const StreamChatLocalizationsIt({String localeName = 'it'}) : super(localeName: localeName); @@ -80,7 +80,7 @@ class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { String get messageDeletedLabel => 'Messaggio cancellato'; @override - String get messageReactionsText => 'Reazioni al messaggio'; + String get messageReactionsLabel => 'Reazioni al messaggio'; @override String get emptyChatMessagesText => 'Nessuna conversazione al momento...'; @@ -151,7 +151,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get okLabel => 'Ok'; @override - String get somethingWentWrongLabel => 'Qualcosa è andato storto'; + String get somethingWentWrongError => 'Qualcosa è andato storto'; @override String get addMoreFilesLabel => 'Aggiungi altri file'; @@ -281,7 +281,7 @@ Inviato il ${_getDay(date)} alle ${Jiffy(time.toLocal()).format('HH:mm')}'''; String get streamChatLabel => 'Stream Chat'; @override - String get searchingForNetworkLabel => 'Cercando una connessione'; + String get searchingForNetworkText => 'Cercando una connessione'; @override String get offlineLabel => 'Offline...'; diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart index 6e796d08..31471406 100644 --- a/packages/stream_chat_localizations/test/translations_test.dart +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -51,7 +51,7 @@ void main() { expect(localizations.resultCountText(3), isNotNull); expect(localizations.messageDeletedText, isNotNull); expect(localizations.messageDeletedLabel, isNotNull); - expect(localizations.messageReactionsText, isNotNull); + expect(localizations.messageReactionsLabel, isNotNull); expect(localizations.emptyChatMessagesText, isNotNull); expect(localizations.threadSeparatorText(3), isNotNull); expect(localizations.connectedLabel, isNotNull); @@ -72,7 +72,7 @@ void main() { expect(localizations.uploadAVideoLabel, isNotNull); expect(localizations.videoFromCameraLabel, isNotNull); expect(localizations.okLabel, isNotNull); - expect(localizations.somethingWentWrongLabel, isNotNull); + expect(localizations.somethingWentWrongError, isNotNull); expect(localizations.addMoreFilesLabel, isNotNull); expect(localizations.enablePhotoAndVideoAccessMessage, isNotNull); expect(localizations.allowGalleryAccessMessage, isNotNull); @@ -131,7 +131,7 @@ void main() { expect(localizations.loadingChannelsError, isNotNull); expect(localizations.deleteConversationQuestion, isNotNull); expect(localizations.streamChatLabel, isNotNull); - expect(localizations.searchingForNetworkLabel, isNotNull); + expect(localizations.searchingForNetworkText, isNotNull); expect(localizations.offlineLabel, isNotNull); expect(localizations.tryAgainLabel, isNotNull); // 1 member From 37a81ebb88759dc627a996362e576d2582f10bdb Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 22 Jul 2021 17:46:28 +0530 Subject: [PATCH 031/113] test(localization): add remaining tests Signed-off-by: xsahil03x --- .../test/translations_test.dart | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart index 31471406..571fec29 100644 --- a/packages/stream_chat_localizations/test/translations_test.dart +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -161,4 +161,25 @@ void main() { expect(localizations.replyToMessageLabel, isNotNull); }); } + + test('should throw if try to load locale which is not supported', () async { + const locale = Locale('not-supported-locale'); + expect( + GlobalStreamChatLocalizations.delegate.isSupported(locale), + isFalse, + ); + try { + await GlobalStreamChatLocalizations.delegate.load(locale); + } catch (e) { + expect(e, isA()); + } + }); + + test('`.toString`', () { + final supportedLocales = kStreamChatSupportedLanguages; + expect( + GlobalStreamChatLocalizations.delegate.toString(), + 'GlobalStreamChatLocalizations.delegate($supportedLocales locales)', + ); + }); } From a1189162631f3aeb79e680504f4a1be609473f63 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 22 Jul 2021 17:50:37 +0530 Subject: [PATCH 032/113] chore(localization): fix analysis error Signed-off-by: xsahil03x --- packages/stream_chat_localizations/test/translations_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart index 571fec29..04b25e5e 100644 --- a/packages/stream_chat_localizations/test/translations_test.dart +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -176,7 +176,7 @@ void main() { }); test('`.toString`', () { - final supportedLocales = kStreamChatSupportedLanguages; + const supportedLocales = kStreamChatSupportedLanguages; expect( GlobalStreamChatLocalizations.delegate.toString(), 'GlobalStreamChatLocalizations.delegate($supportedLocales locales)', From f1133dd4b0253a31577cc0eb7760ec17f27976df Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 22 Jul 2021 18:02:17 +0530 Subject: [PATCH 033/113] test(localization): fix `.toString` test Signed-off-by: xsahil03x --- packages/stream_chat_localizations/test/translations_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart index 04b25e5e..81acf58f 100644 --- a/packages/stream_chat_localizations/test/translations_test.dart +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -176,7 +176,7 @@ void main() { }); test('`.toString`', () { - const supportedLocales = kStreamChatSupportedLanguages; + final supportedLocales = kStreamChatSupportedLanguages.length; expect( GlobalStreamChatLocalizations.delegate.toString(), 'GlobalStreamChatLocalizations.delegate($supportedLocales locales)', From 5f6f891eac25d9d731d608a7196fe121d1967624 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 22 Jul 2021 18:02:37 +0530 Subject: [PATCH 034/113] chore(repo): update melos for localization Signed-off-by: xsahil03x --- melos.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 From af0356e5a15afbbd00ff2f5e78a8d142d59eb154 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 22 Jul 2021 19:47:54 +0530 Subject: [PATCH 035/113] test(localization): fix `sentAtText`, add remaining tests Signed-off-by: xsahil03x --- .../lib/src/localization/translations.dart | 13 +- .../test/src/default_translations_test.dart | 174 ++++++++++++++++++ .../lib/src/stream_chat_localizations_en.dart | 13 +- .../lib/src/stream_chat_localizations_fr.dart | 13 +- .../lib/src/stream_chat_localizations_hi.dart | 13 +- .../lib/src/stream_chat_localizations_it.dart | 17 +- .../test/translations_test.dart | 19 ++ 7 files changed, 235 insertions(+), 27 deletions(-) create mode 100644 packages/stream_chat_flutter/test/src/default_translations_test.dart diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 17355b6f..d6e5add0 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -535,15 +535,18 @@ class DefaultTranslations implements Translations { String get photosLabel => 'Photos'; String _getDay(DateTime dateTime) { - final now = Jiffy(DateTime.now()); - final date = Jiffy(dateTime); + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final yesterday = DateTime(now.year, now.month, now.day - 1); - if (date.isSame(now, Units.DAY)) { + final date = DateTime(dateTime.year, dateTime.month, dateTime.day); + + if (date == today) { return 'today'; - } else if (now.diff(date, Units.HOUR) < 24) { + } else if (date == yesterday) { return 'yesterday'; } else { - return 'on ${date.MMMd}'; + return 'on ${Jiffy(date).MMMd}'; } } 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..60e69fab --- /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', () { + final 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, isNotNull); + expect(translations.fileTooLargeError, 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_localizations/lib/src/stream_chat_localizations_en.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart index ad58b89a..5041d398 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -228,15 +228,18 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get photosLabel => 'Photos'; String _getDay(DateTime dateTime) { - final now = Jiffy(DateTime.now()); - final date = Jiffy(dateTime); + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final yesterday = DateTime(now.year, now.month, now.day - 1); - if (date.isSame(now, Units.DAY)) { + final date = DateTime(dateTime.year, dateTime.month, dateTime.day); + + if (date == today) { return 'today'; - } else if (now.diff(date, Units.HOUR) < 24) { + } else if (date == yesterday) { return 'yesterday'; } else { - return 'on ${date.MMMd}'; + return 'on ${Jiffy(date).MMMd}'; } } 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 index ba8511a0..1bbfbc5a 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -232,15 +232,18 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get photosLabel => 'Photos'; String _getDay(DateTime dateTime) { - final now = Jiffy(DateTime.now()); - final date = Jiffy(dateTime); + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final yesterday = DateTime(now.year, now.month, now.day - 1); - if (date.isSame(now, Units.DAY)) { + final date = DateTime(dateTime.year, dateTime.month, dateTime.day); + + if (date == today) { return "aujourd'hui"; - } else if (now.diff(date, Units.HOUR) < 24) { + } else if (date == yesterday) { return 'hier'; } else { - return 'le ${date.MMMd}'; + return 'le ${Jiffy(date).MMMd}'; } } 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 index 93a5ca30..850e481b 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -227,15 +227,18 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get photosLabel => 'तस्वीरें'; String _getDay(DateTime dateTime) { - final now = Jiffy(DateTime.now()); - final date = Jiffy(dateTime); + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final yesterday = DateTime(now.year, now.month, now.day - 1); - if (date.isSame(now, Units.DAY)) { + final date = DateTime(dateTime.year, dateTime.month, dateTime.day); + + if (date == today) { return 'आज'; - } else if (now.diff(date, Units.HOUR) < 24) { + } else if (date == yesterday) { return 'कल'; } else { - return '${date.MMMd} को'; + return '${Jiffy(date).MMMd} को'; } } diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart index ce5faf55..8b2e1692 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -229,21 +229,24 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get photosLabel => 'Foto'; String _getDay(DateTime dateTime) { - final now = Jiffy(DateTime.now()); - final date = Jiffy(dateTime); + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final yesterday = DateTime(now.year, now.month, now.day - 1); - if (date.isSame(now, Units.DAY)) { + final date = DateTime(dateTime.year, dateTime.month, dateTime.day); + + if (date == today) { return 'oggi'; - } else if (now.diff(date, Units.HOUR) < 24) { + } else if (date == yesterday) { return 'ieri'; } else { - return 'il ${date.MMMd}'; + return 'il ${Jiffy(date).MMMd}'; } } @override - String sentAtText({required DateTime date, required DateTime time}) => ''' -Inviato il ${_getDay(date)} alle ${Jiffy(time.toLocal()).format('HH:mm')}'''; + String sentAtText({required DateTime date, required DateTime time}) => + "Inviato ${_getDay(date)} alle ${Jiffy(time.toLocal()).format('HH:mm')}"; @override String get todayLabel => 'Oggi'; diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart index 81acf58f..199557d8 100644 --- a/packages/stream_chat_localizations/test/translations_test.dart +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -114,6 +114,7 @@ void main() { isNotNull, ); expect(localizations.photosLabel, isNotNull); + // today expect( localizations.sentAtText( date: DateTime.now(), @@ -121,6 +122,22 @@ void main() { ), isNotNull, ); + // yesterday + expect( + localizations.sentAtText( + date: DateTime.now().subtract(const Duration(days: 1)), + time: DateTime.now(), + ), + isNotNull, + ); + // any other day + expect( + localizations.sentAtText( + date: DateTime.now().subtract(const Duration(days: 3)), + time: DateTime.now(), + ), + isNotNull, + ); expect(localizations.todayLabel, isNotNull); expect(localizations.yesterdayLabel, isNotNull); expect(localizations.channelIsMutedText, isNotNull); @@ -129,6 +146,7 @@ void main() { expect(localizations.sendingFirstMessageLabel, isNotNull); expect(localizations.startAChatLabel, isNotNull); expect(localizations.loadingChannelsError, isNotNull); + expect(localizations.deleteConversationLabel, isNotNull); expect(localizations.deleteConversationQuestion, isNotNull); expect(localizations.streamChatLabel, isNotNull); expect(localizations.searchingForNetworkText, isNotNull); @@ -148,6 +166,7 @@ void main() { expect(localizations.leaveConversationLabel, isNotNull); expect(localizations.leaveConversationQuestion, isNotNull); expect(localizations.showInChatLabel, isNotNull); + expect(localizations.saveImageLabel, isNotNull); expect(localizations.saveVideoLabel, isNotNull); expect(localizations.uploadErrorLabel, isNotNull); expect(localizations.giphyLabel, isNotNull); From 17ea43b9d99963079c2cc3824bb692c84cfd72b9 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 22 Jul 2021 19:55:23 +0530 Subject: [PATCH 036/113] test(localization): fix test Signed-off-by: xsahil03x --- .../test/translations_test.dart | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart index 199557d8..ce00b9ac 100644 --- a/packages/stream_chat_localizations/test/translations_test.dart +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import 'package:stream_chat_localizations/stream_chat_localizations.dart'; +import 'package:stream_chat_localizations/src/stream_chat_localizations.dart'; void main() { for (final language in kStreamChatSupportedLanguages) { @@ -183,12 +182,8 @@ void main() { test('should throw if try to load locale which is not supported', () async { const locale = Locale('not-supported-locale'); - expect( - GlobalStreamChatLocalizations.delegate.isSupported(locale), - isFalse, - ); try { - await GlobalStreamChatLocalizations.delegate.load(locale); + getStreamChatTranslation(locale); } catch (e) { expect(e, isA()); } From c92cba44041c0c7f2a66b5fd610388519c185014 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 22 Jul 2021 16:31:36 +0200 Subject: [PATCH 037/113] fix(docs): fix highlight --- docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 1dcf969c..8f5a6864 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -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, From 937f55e67b7ab6d3b7019d4116cf612667b819e9 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 22 Jul 2021 20:02:36 +0530 Subject: [PATCH 038/113] chore(ui): fix analysis error Signed-off-by: xsahil03x --- .../stream_chat_flutter/test/src/default_translations_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/test/src/default_translations_test.dart b/packages/stream_chat_flutter/test/src/default_translations_test.dart index 60e69fab..13941c2e 100644 --- a/packages/stream_chat_flutter/test/src/default_translations_test.dart +++ b/packages/stream_chat_flutter/test/src/default_translations_test.dart @@ -3,7 +3,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { test('Default translations should exist', () { - final translations = DefaultTranslations.instance; + const translations = DefaultTranslations.instance; expect(translations.launchUrlError, isNotNull); expect(translations.loadingUsersError, isNotNull); expect(translations.noUsersLabel, isNotNull); From 36a13e8a2b527fe95402908f2a1a186a912ae5a7 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 22 Jul 2021 20:16:36 +0530 Subject: [PATCH 039/113] fix(localization): fix `fileTooLargeAfterCompressionError`, `fileTooLargeError` Signed-off-by: xsahil03x --- .../test/src/default_translations_test.dart | 4 ++-- .../stream_chat_localizations/test/translations_test.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/default_translations_test.dart b/packages/stream_chat_flutter/test/src/default_translations_test.dart index 13941c2e..e78c0115 100644 --- a/packages/stream_chat_flutter/test/src/default_translations_test.dart +++ b/packages/stream_chat_flutter/test/src/default_translations_test.dart @@ -54,8 +54,8 @@ void main() { expect(translations.searchGifLabel, isNotNull); expect(translations.writeAMessageLabel, isNotNull); expect(translations.instantCommandsLabel, isNotNull); - expect(translations.fileTooLargeAfterCompressionError, isNotNull); - expect(translations.fileTooLargeError, isNotNull); + expect(translations.fileTooLargeAfterCompressionError(33), isNotNull); + expect(translations.fileTooLargeError(33), isNotNull); expect(translations.emojiMatchingQueryText('sahil'), isNotNull); expect(translations.addAFileLabel, isNotNull); expect(translations.photoFromCameraLabel, isNotNull); diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart index ce00b9ac..0e621159 100644 --- a/packages/stream_chat_localizations/test/translations_test.dart +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -61,8 +61,8 @@ void main() { expect(localizations.searchGifLabel, isNotNull); expect(localizations.writeAMessageLabel, isNotNull); expect(localizations.instantCommandsLabel, isNotNull); - expect(localizations.fileTooLargeAfterCompressionError, isNotNull); - expect(localizations.fileTooLargeError, isNotNull); + expect(localizations.fileTooLargeAfterCompressionError(33), isNotNull); + expect(localizations.fileTooLargeError(33), isNotNull); expect(localizations.emojiMatchingQueryText('sahil'), isNotNull); expect(localizations.addAFileLabel, isNotNull); expect(localizations.photoFromCameraLabel, isNotNull); From 4f768024c663e17876b8962fce9bb59910188c6e Mon Sep 17 00:00:00 2001 From: groovinchip Date: Thu, 22 Jul 2021 11:08:10 -0400 Subject: [PATCH 040/113] chore: Add language to OwnUser --- packages/stream_chat/lib/src/core/models/own_user.dart | 6 ++++++ packages/stream_chat/lib/src/core/models/user.dart | 4 ++++ packages/stream_chat_flutter/example/lib/main.dart | 6 +++++- packages/stream_chat_flutter/lib/src/message_text.dart | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) 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..0eada8d0 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,6 +93,7 @@ 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 @@ -113,6 +118,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/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index bd4bf226..12ab162d 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -37,6 +37,7 @@ class User extends Equatable { 'online', 'banned', 'teams', + 'language', ]; /// User id @@ -126,6 +127,7 @@ class User extends Equatable { Map? extraData, bool? banned, List? teams, + String? language, }) => User( id: id ?? this.id, @@ -137,6 +139,7 @@ class User extends Equatable { extraData: extraData ?? this.extraData, banned: banned ?? this.banned, teams: teams ?? this.teams, + language: language ?? this.language, ); @override @@ -150,5 +153,6 @@ class User extends Equatable { online, banned, extraData, + language, ]; } diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 89362d81..42891aa7 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -24,11 +24,15 @@ void main() async { /// 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'), + User(id: 'super-band-9', language: 'fr'), 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.' '0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); + client.on().listen((e) { + print('e.user.language ${e.me?.language}'); + }); + final channel = client.channel('messaging', id: 'godevs'); await channel.watch(); diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index 6d006085..7e86fed2 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -29,6 +29,7 @@ class MessageText extends StatelessWidget { @override Widget build(BuildContext context) { + //print(StreamChat.of(context).user); final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\n\n'); final themeData = Theme.of(context); From 9ebd8c7876be9e3e475d3f9da72859f1a81f0d09 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 23 Jul 2021 09:42:59 +0200 Subject: [PATCH 041/113] fix(llc): ignore healthcheck user.me objects --- .../stream_chat/lib/src/client/client.dart | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 050d36f1..514dd086 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -388,9 +388,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); @@ -1333,16 +1330,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); - } + this.user = user; + final totalUnreadCount = user.totalUnreadCount; + _totalUnreadCountController.add(totalUnreadCount); - final unreadChannels = user?.unreadChannels; + final unreadChannels = user.unreadChannels; if (unreadChannels != null) { _unreadChannelsController.add(unreadChannels); } From 9b759df8131e52dadb202d6187eacfc6a60b8d2f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 23 Jul 2021 16:55:46 +0530 Subject: [PATCH 042/113] chore(localization): add examples Signed-off-by: xsahil03x --- .../example/.gitignore | 41 ++ .../example/.metadata | 10 + .../example/README.md | 2 + .../example/android/.gitignore | 11 + .../example/android/app/build.gradle | 64 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 47 ++ .../com/example/example/MainActivity.kt | 6 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../example/android/build.gradle | 31 + .../example/android/gradle.properties | 4 + .../gradle/wrapper/gradle-wrapper.properties | 6 + .../example/android/settings.gradle | 11 + .../example/ios/.gitignore | 32 + .../ios/Runner.xcodeproj/project.pbxproj | 563 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 91 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 ++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 ++ .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../example/ios/Runner/Info.plist | 45 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../example/lib/add_new_lang.dart | 500 ++++++++++++++++ .../example/lib/main.dart | 115 ++++ .../example/lib/override_lang.dart | 142 +++++ .../example/pubspec.yaml | 26 + 59 files changed, 2067 insertions(+) create mode 100644 packages/stream_chat_localizations/example/.gitignore create mode 100644 packages/stream_chat_localizations/example/.metadata create mode 100644 packages/stream_chat_localizations/example/README.md create mode 100644 packages/stream_chat_localizations/example/android/.gitignore create mode 100644 packages/stream_chat_localizations/example/android/app/build.gradle create mode 100644 packages/stream_chat_localizations/example/android/app/src/debug/AndroidManifest.xml create mode 100644 packages/stream_chat_localizations/example/android/app/src/main/AndroidManifest.xml create mode 100644 packages/stream_chat_localizations/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt create mode 100644 packages/stream_chat_localizations/example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 packages/stream_chat_localizations/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 packages/stream_chat_localizations/example/android/app/src/main/res/values/styles.xml create mode 100644 packages/stream_chat_localizations/example/android/app/src/profile/AndroidManifest.xml create mode 100644 packages/stream_chat_localizations/example/android/build.gradle create mode 100644 packages/stream_chat_localizations/example/android/gradle.properties create mode 100644 packages/stream_chat_localizations/example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 packages/stream_chat_localizations/example/android/settings.gradle create mode 100644 packages/stream_chat_localizations/example/ios/.gitignore create mode 100644 packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/stream_chat_localizations/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/stream_chat_localizations/example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/stream_chat_localizations/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/stream_chat_localizations/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/stream_chat_localizations/example/ios/Runner/AppDelegate.swift create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Info.plist create mode 100644 packages/stream_chat_localizations/example/ios/Runner/Runner-Bridging-Header.h create mode 100644 packages/stream_chat_localizations/example/lib/add_new_lang.dart create mode 100644 packages/stream_chat_localizations/example/lib/main.dart create mode 100644 packages/stream_chat_localizations/example/lib/override_lang.dart create mode 100644 packages/stream_chat_localizations/example/pubspec.yaml 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..07e5ac18 --- /dev/null +++ b/packages/stream_chat_localizations/example/README.md @@ -0,0 +1,2 @@ +# Stream Chat Persistence Example +Please see `lib/` for example code. \ 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 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 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..e96ef602 --- /dev/null +++ b/packages/stream_chat_localizations/example/ios/.gitignore @@ -0,0 +1,32 @@ +*.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/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/Runner.xcodeproj/project.pbxproj b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..261aa5a8 --- /dev/null +++ b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,563 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0E9B23A7BA08E142FA5000CF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D76F8024ABE1070895D659BA /* Pods_Runner.framework */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 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 = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3F6A054EEDAF06BCF649C130 /* 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 = ""; }; + 6FE1ECA061EBB001F6BCE8B7 /* 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 = ""; }; + 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 = ""; }; + D76F8024ABE1070895D659BA /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EC2A45E9198C1011BED23834 /* 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E9B23A7BA08E142FA5000CF /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 04AAB960E493BD92262BBF82 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D76F8024ABE1070895D659BA /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 8559384DCD98ED6067CEF8CB /* Pods */ = { + isa = PBXGroup; + children = ( + 3F6A054EEDAF06BCF649C130 /* Pods-Runner.debug.xcconfig */, + EC2A45E9198C1011BED23834 /* Pods-Runner.release.xcconfig */, + 6FE1ECA061EBB001F6BCE8B7 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 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 */, + 8559384DCD98ED6067CEF8CB /* Pods */, + 04AAB960E493BD92262BBF82 /* 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 = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + C1EE41B94EADE099F7AF3A1C /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + BD38DACC9A0AD429D9ED9939 /* [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"; + }; + 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"; + }; + BD38DACC9A0AD429D9ED9939 /* [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; + }; + C1EE41B94EADE099F7AF3A1C /* [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; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + 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; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + 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; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + 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..1d526a16 --- /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 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 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 iW8Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 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..a060db61 --- /dev/null +++ b/packages/stream_chat_localizations/example/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + 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..95e1130d --- /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) { + return 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, + 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 From d6db3d84e05dc5357fc4273d024c9904252ee6d6 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 23 Jul 2021 16:56:21 +0530 Subject: [PATCH 043/113] chore(localization): fix analyzer warning Signed-off-by: xsahil03x --- .../example/lib/main.dart | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/stream_chat_localizations/example/lib/main.dart b/packages/stream_chat_localizations/example/lib/main.dart index 95e1130d..436553c1 100644 --- a/packages/stream_chat_localizations/example/lib/main.dart +++ b/packages/stream_chat_localizations/example/lib/main.dart @@ -64,29 +64,27 @@ class MyApp extends StatelessWidget { final Channel channel; @override - Widget build(BuildContext context) { - return 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, - builder: (context, widget) => StreamChat( - client: client, - child: widget, - ), - home: StreamChannel( - channel: channel, - child: const ChannelPage(), - ), - ); - } + 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, + builder: (context, widget) => StreamChat( + client: client, + child: widget, + ), + home: StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ); } /// A list of messages sent in the current channel. From e7a7521669f7c0ca88713c7ea330e58a082c89ac Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 23 Jul 2021 17:19:10 +0530 Subject: [PATCH 044/113] chore(localization): update readme.md Signed-off-by: xsahil03x --- packages/stream_chat_localizations/README.md | 96 ++++++++++++++++++-- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md index 3ec4d86e..393a414b 100644 --- a/packages/stream_chat_localizations/README.md +++ b/packages/stream_chat_localizations/README.md @@ -1,14 +1,90 @@ -# stream_chat_localizations +# Official Localizations for [Stream Chat Flutter](https://getstream.io/chat/sdk/flutter/) library. -A new Flutter project. +> The Official localizations for Stream Chat Flutter, a service for +> building chat applications. -## Getting Started +[![Pub](https://img.shields.io/pub/v/stream_chat_localizations.svg)](https://pub.dartlang.org/packages/stream_chat_localizations) +![](https://img.shields.io/badge/platform-flutter%20%7C%20flutter%20web-ff69b4.svg?style=flat-square) +![CI](https://github.com/GetStream/stream-chat-flutter/workflows/stream_flutter_workflow/badge.svg?branch=master) -This project is a starting point for a Dart -[package](https://flutter.dev/developing-packages/), -a library module containing code that can be shared easily across -multiple Flutter or Dart projects. -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +**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/) + +This package provides localized strings for the stream chat widgets for many languages. + +### Changelog + +Check out the [changelog on pub.dev](https://pub.dev/packages/stream_chat_localizations/changelog) to see the latest changes in the package. + +## Add dependency +Add this to your package's pubspec.yaml file, use the latest version [![Pub](https://img.shields.io/pub/v/stream_chat_localizations.svg)](https://pub.dartlang.org/packages/stream_chat_localizations) +```yaml +dependencies: + stream_chat_localizations: ^latest_version +``` + +You should 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(), + ), + ); + } +} +``` + +### ⚠️ 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 + +``` + +## 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. From ccd748a93602791289ab3d1625e29e0a22b5a8cc Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 26 Jul 2021 10:38:42 +0200 Subject: [PATCH 045/113] bump version and changelog --- packages/stream_chat_localizations/CHANGELOG.md | 4 ++-- packages/stream_chat_localizations/pubspec.yaml | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md index 41cc7d81..5ca6d89e 100644 --- a/packages/stream_chat_localizations/CHANGELOG.md +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -1,3 +1,3 @@ -## 0.0.1 +## 1.0.0 -* TODO: Describe initial release. +* First release diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index ac999a9c..4ef1ce69 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -1,7 +1,9 @@ name: stream_chat_localizations -description: A new Flutter project. -version: 0.0.1 -homepage: +description: The Official localizations for Stream Chat Flutter, a service for building chat applications +version: 1.0.0 +homepage: https://github.com/GetStream/stream-chat-flutter +repository: https://github.com/GetStream/stream-chat-flutter +issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues environment: sdk: ">=2.12.0 <3.0.0" From c0951272ebbd906750ccc9e774a8f660da657814 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 26 Jul 2021 15:06:47 +0530 Subject: [PATCH 046/113] feat: Extracted visible_footnote.dart, corrected default filter --- .../lib/src/attachment/giphy_attachment.dart | 32 +++---------------- .../lib/src/message_widget.dart | 28 +++++++--------- .../lib/src/visible_footnote.dart | 29 +++++++++++++++++ .../lib/src/message_list_core.dart | 3 +- 4 files changed, 45 insertions(+), 47 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/visible_footnote.dart 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..fd29724d 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -2,6 +2,7 @@ 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'; @@ -216,36 +217,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(), ), ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index c9dfd9d4..47f1182b 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -15,6 +15,7 @@ import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart'; +import 'package:stream_chat_flutter/src/visible_footnote.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Widget builder for building attachments @@ -92,6 +93,7 @@ class MessageWidget extends StatefulWidget { this.userAvatarBuilder, this.editMessageInputBuilder, this.textBuilder, + this.bottomRowBuilder, this.onReturnAction, Map? customAttachmentBuilders, this.readList, @@ -275,6 +277,9 @@ 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 user avatar final Widget Function(BuildContext, User)? userAvatarBuilder; @@ -782,7 +787,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( @@ -830,22 +839,7 @@ 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 const VisibleFootnote(); } final children = []; 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..bbdb9144 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/visible_footnote.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.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( + 'Only visible to you', + style: chatThemeData.textTheme.footnote + .copyWith(color: chatThemeData.colorTheme.textLowEmphasis), + ), + ], + ); + } +} 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 4acf0ba7..83ef4e24 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 @@ -134,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; } From 13782f8e1b58677c1ee906bf4790b8eb6d82430c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 26 Jul 2021 15:49:04 +0530 Subject: [PATCH 047/113] feat: Added new builders and restructured --- .../example/lib/tutorial_part_4.dart | 7 +++++++ .../lib/src/message_widget.dart | 18 +++++++++++++++--- .../lib/stream_chat_flutter.dart | 1 + 3 files changed, 23 insertions(+), 3 deletions(-) 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..0a8e5b11 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart @@ -96,6 +96,13 @@ class ChannelPage extends StatelessWidget { threadBuilder: (_, parentMessage) => ThreadPage( parent: parentMessage, ), + messageBuilder: (context, message, list, defaultMessage) { + return defaultMessage.copyWith( + deletedBottomRowBuilder: (c, m) { + return VisibleFootnote(); + }, + ); + }, ), ), const MessageInput(), diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 47f1182b..f0a340d1 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -15,7 +15,6 @@ import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart'; -import 'package:stream_chat_flutter/src/visible_footnote.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Widget builder for building attachments @@ -94,6 +93,7 @@ class MessageWidget extends StatefulWidget { this.editMessageInputBuilder, this.textBuilder, this.bottomRowBuilder, + this.deletedBottomRowBuilder, this.onReturnAction, Map? customAttachmentBuilders, this.readList, @@ -277,9 +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 + /// Widget builder for building a bottom row below the message when deleted final Widget Function(BuildContext, Message)? bottomRowBuilder; + /// Widget builder for building a bottom row below the message when deleted + final Widget Function(BuildContext, Message)? deletedBottomRowBuilder; + /// Widget builder for building user avatar final Widget Function(BuildContext, User)? userAvatarBuilder; @@ -415,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, @@ -468,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, @@ -839,7 +847,11 @@ class _MessageWidgetState extends State Widget get _bottomRow { if (isDeleted) { - return const VisibleFootnote(); + return widget.deletedBottomRowBuilder?.call( + context, + widget.message, + ) ?? + const SizedBox.shrink(); } final children = []; diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 12049fc6..082b66a7 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -31,6 +31,7 @@ export 'src/sending_indicator.dart'; export 'src/stream_chat.dart'; export 'src/stream_chat_theme.dart'; export 'src/stream_neumorphic_button.dart'; +export 'src/visible_footnote.dart'; export 'src/stream_svg_icon.dart'; export 'src/system_message.dart'; export 'src/thread_header.dart'; From 010d0f83824e8ecde565eb444c01b059514ead2b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 26 Jul 2021 15:55:45 +0530 Subject: [PATCH 048/113] fix --- .../stream_chat_flutter/example/lib/tutorial_part_4.dart | 7 ------- 1 file changed, 7 deletions(-) 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 0a8e5b11..07942302 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart @@ -96,13 +96,6 @@ class ChannelPage extends StatelessWidget { threadBuilder: (_, parentMessage) => ThreadPage( parent: parentMessage, ), - messageBuilder: (context, message, list, defaultMessage) { - return defaultMessage.copyWith( - deletedBottomRowBuilder: (c, m) { - return VisibleFootnote(); - }, - ); - }, ), ), const MessageInput(), From e373d42c1b34d5b2e7598619fe0574ae2ac7e8b5 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 26 Jul 2021 16:52:06 +0530 Subject: [PATCH 049/113] fix: analysis --- packages/stream_chat_flutter/lib/stream_chat_flutter.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 082b66a7..8c3a6f8e 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -31,7 +31,6 @@ export 'src/sending_indicator.dart'; export 'src/stream_chat.dart'; export 'src/stream_chat_theme.dart'; export 'src/stream_neumorphic_button.dart'; -export 'src/visible_footnote.dart'; export 'src/stream_svg_icon.dart'; export 'src/system_message.dart'; export 'src/thread_header.dart'; @@ -43,3 +42,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'; From f326480ed3eb0842a431ba6be765ee19c62041c2 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 26 Jul 2021 18:30:31 +0530 Subject: [PATCH 050/113] added changelog entries --- packages/stream_chat_flutter/CHANGELOG.md | 1 + packages/stream_chat_flutter_core/CHANGELOG.md | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 16e65fa7..9761d0b1 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -4,6 +4,7 @@ - Added `MessageListView.paginationLimit` - Allow the various ListView widgets to be themed via ThemeData classes +- Added `bottomRowBuilder` and `deletedBottomRowBuilder` that build a widget below a `MessageWidget` 🐞 Fixed diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index a823a0db..d42b7eff 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -3,6 +3,9 @@ ✅ Added - Added `MessageListCore.paginationLimit` +🛑️ Changed +- Changed default message filter of `MessageListCore` + ## 2.0.0 🛑️ Breaking Changes from `1.5.3` From 63ed35a5de77bfd1d9514f62fd43a1748a6888f9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 26 Jul 2021 18:33:41 +0530 Subject: [PATCH 051/113] added changelog entries --- packages/stream_chat_flutter_core/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index d42b7eff..2c6b1b65 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -3,7 +3,7 @@ ✅ Added - Added `MessageListCore.paginationLimit` -🛑️ Changed +🔄 Changed - Changed default message filter of `MessageListCore` ## 2.0.0 From 6cd80a738230faadc4c2de4b1d1ba7badf13fd7f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 27 Jul 2021 15:03:19 +0530 Subject: [PATCH 052/113] Update packages/stream_chat_flutter/lib/src/message_widget.dart Co-authored-by: Salvatore Giordano --- packages/stream_chat_flutter/lib/src/message_widget.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 075f4d2b..ca10d3d4 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -277,10 +277,10 @@ 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 when deleted + /// Widget builder for building a bottom row below the message final Widget Function(BuildContext, Message)? bottomRowBuilder; - /// Widget builder for building a bottom row below the message when deleted + /// Widget builder for building a bottom row below a deleted message final Widget Function(BuildContext, Message)? deletedBottomRowBuilder; /// Widget builder for building user avatar From cef76aa27e4c332c1b6beed3c15d7e038bb19cc5 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 27 Jul 2021 15:04:10 +0530 Subject: [PATCH 053/113] Update packages/stream_chat_flutter/lib/src/message_widget.dart Co-authored-by: Salvatore Giordano --- packages/stream_chat_flutter/lib/src/message_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index ca10d3d4..970ae9da 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -851,7 +851,7 @@ class _MessageWidgetState extends State context, widget.message, ) ?? - const SizedBox.shrink(); + const Offstage(); } final children = []; From c5782e0275c2b03e1894ca3634b1461a42e57c7f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Jul 2021 16:47:24 +0530 Subject: [PATCH 054/113] chore(llc): re-generate model classes Signed-off-by: xsahil03x --- .../lib/src/core/models/message.g.dart | 2 +- .../lib/src/core/models/own_user.dart | 7 ++---- .../lib/src/core/models/own_user.g.dart | 1 + .../stream_chat/lib/src/core/models/user.dart | 23 ++----------------- 4 files changed, 6 insertions(+), 27 deletions(-) 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 8ad1b3e1..5fb559f0 100644 --- a/packages/stream_chat/lib/src/core/models/message.g.dart +++ b/packages/stream_chat/lib/src/core/models/message.g.dart @@ -110,6 +110,6 @@ Map _$MessageToJson(Message instance) { val['pinned_by'] = readonly(instance.pinnedBy); val['extra_data'] = instance.extraData; writeNotNull('deleted_at', readonly(instance.deletedAt)); - val['i18n'] = instance.i18n; + writeNotNull('i18n', readonly(instance.i18n)); return val; } 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 0eada8d0..116a2f84 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -27,7 +27,7 @@ class OwnUser extends User { Map extraData = const {}, bool banned = false, List teams = const [], - String? language, + String language = 'en', }) : super( id: id, role: role, @@ -99,10 +99,7 @@ class OwnUser extends User { /// 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, 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..5d80cc1c 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? ?? 'en', ); } diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index 12ab162d..2cdb6dd6 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -88,10 +88,7 @@ class User extends Equatable { /// /// Defaults to 'en'. @JsonKey(defaultValue: 'en') - final String? language; - - @override - int get hashCode => id.hashCode; + final String language; /// Shortcut for user name String get name { @@ -106,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), @@ -143,16 +135,5 @@ class User extends Equatable { ); @override - List get props => [ - id, - role, - teams, - createdAt, - updatedAt, - lastActive, - online, - banned, - extraData, - language, - ]; + List get props => [id]; } From aad60bbc66edc5540b561e2ed5d8bee748f13b63 Mon Sep 17 00:00:00 2001 From: xsahil03x Date: Tue, 27 Jul 2021 16:48:25 +0530 Subject: [PATCH 055/113] chore(llc): remove redundant `event.me` listener Signed-off-by: xsahil03x --- .../stream_chat/lib/src/client/client.dart | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 3941beac..e1212e83 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -318,9 +318,7 @@ class StreamChatClient { final ownUser = OwnUser.fromUser(user); state.currentUser = ownUser; - if (!connectWebSocket) { - return ownUser; - } + if (!connectWebSocket) return ownUser; try { if (_originalChatPersistenceClient != null) { @@ -1336,21 +1334,6 @@ class ClientState { /// Creates a new instance listening to events and updating the state ClientState(this._client) { _subscriptions.addAll([ - _client - .on() - .where((event) => - event.me != null && event.type != EventType.healthCheck) - .map((e) => e.me!) - .listen((user) { - currentUser = user; - final totalUnreadCount = user.totalUnreadCount; - _totalUnreadCountController.add(totalUnreadCount); - - final unreadChannels = user.unreadChannels; - if (unreadChannels != null) { - _unreadChannelsController.add(unreadChannels); - } - }), _client .on() .map((event) => event.unreadChannels) @@ -1413,7 +1396,8 @@ class ClientState { final StreamChatClient _client; - /// Update user information + /// Sets the user currently interacting with the client + /// note: this fully overrides the [currentUser] set currentUser(OwnUser? user) { _currentUserController.add(user); } From 467e892079a043d34349b1423fd5f252dad00109 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Jul 2021 16:49:04 +0530 Subject: [PATCH 056/113] feat(ui): add support for translated message in MessageText Signed-off-by: xsahil03x --- .../lib/src/message_text.dart | 108 ++++++++-------- .../test/src/message_text_test.dart | 119 ++++++++++++++++++ 2 files changed, 178 insertions(+), 49 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index 7e86fed2..a1e3a4f8 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -29,61 +29,71 @@ class MessageText extends StatelessWidget { @override Widget build(BuildContext context) { - //print(StreamChat.of(context).user); - 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), + initialData: streamChat.currentUser!.language, + 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, + ); + if (mentionedUser == null) { + return; + } - 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 (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, + 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, + ), + ), + ).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/test/src/message_text_test.dart b/packages/stream_chat_flutter/test/src/message_text_test.dart index 5daa94ba..80ba5a3c 100644 --- a/packages/stream_chat_flutter/test/src/message_text_test.dart +++ b/packages/stream_chat_flutter/test/src/message_text_test.dart @@ -8,6 +8,28 @@ 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', @@ -54,6 +76,103 @@ 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 { From 7449540f3483219322e3eb062d3ce463422e86de Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Jul 2021 18:35:15 +0530 Subject: [PATCH 057/113] fix(llc, ui): fix failing tests Signed-off-by: xsahil03x --- .../lib/src/core/models/message.dart | 2 +- .../lib/src/core/models/message.g.dart | 2 +- .../stream_chat/lib/src/core/models/user.dart | 6 +++- .../lib/src/core/models/user.g.dart | 2 +- .../test/src/message_text_test.dart | 28 +++++++++++++------ 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index d5435222..6ad8721c 100644 --- a/packages/stream_chat/lib/src/core/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -220,7 +220,7 @@ class Message extends Equatable { final DateTime? deletedAt; /// A Map of translations. - @JsonKey(includeIfNull: false, toJson: Serializer.readOnly) + @JsonKey(includeIfNull: false) final Map? i18n; /// Known top level fields. 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 5fb559f0..25254e2c 100644 --- a/packages/stream_chat/lib/src/core/models/message.g.dart +++ b/packages/stream_chat/lib/src/core/models/message.g.dart @@ -110,6 +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', readonly(instance.i18n)); + writeNotNull('i18n', instance.i18n); return val; } diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index 2cdb6dd6..dd4abcc6 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -87,7 +87,11 @@ class User extends Equatable { /// The language this user prefers. /// /// Defaults to 'en'. - @JsonKey(defaultValue: 'en') + @JsonKey( + includeIfNull: false, + toJson: Serializer.readOnly, + defaultValue: 'en', + ) final String language; /// Shortcut for user name 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 85fdfef9..7b193f04 100644 --- a/packages/stream_chat/lib/src/core/models/user.g.dart +++ b/packages/stream_chat/lib/src/core/models/user.g.dart @@ -48,6 +48,6 @@ Map _$UserToJson(User instance) { writeNotNull('online', readonly(instance.online)); writeNotNull('banned', readonly(instance.banned)); val['extra_data'] = instance.extraData; - val['language'] = instance.language; + writeNotNull('language', readonly(instance.language)); return val; } 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 80ba5a3c..5bfe0a34 100644 --- a/packages/stream_chat_flutter/test/src/message_text_test.dart +++ b/packages/stream_chat_flutter/test/src/message_text_test.dart @@ -34,6 +34,7 @@ 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(); @@ -43,7 +44,9 @@ void main() { final streamTheme = StreamChatThemeData.fromTheme(themeData); when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).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); @@ -176,6 +179,7 @@ void main() { testGoldens( 'control test', (WidgetTester tester) async { + final currentUser = OwnUser(id: 'user-id'); final client = MockClient(); final clientState = MockClientState(); final channel = MockChannel(); @@ -185,7 +189,9 @@ void main() { final streamTheme = StreamChatThemeData.fromTheme(themeData); when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).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); @@ -209,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, ), ), ), From 658f7f2504d0674a89a276b885d92cfd627f2573 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Jul 2021 20:06:06 +0530 Subject: [PATCH 058/113] feat(persistence): add support for message.i18n Signed-off-by: xsahil03x --- .../lib/src/db/moor_chat_database.g.dart | 159 +++++++++++++++--- .../lib/src/entity/messages.dart | 3 + .../lib/src/entity/users.dart | 3 + .../lib/src/mapper/message_mapper.dart | 2 + .../lib/src/mapper/pinned_message_mapper.dart | 4 + .../lib/src/mapper/user_mapper.dart | 2 + .../test/src/dao/message_dao_test.dart | 21 ++- .../test/src/dao/user_dao_test.dart | 1 + .../test/src/mapper/message_mapper_test.dart | 16 +- .../mapper/pinned_message_mapper_test.dart | 16 +- .../test/src/mapper/user_mapper_test.dart | 4 + 11 files changed, 205 insertions(+), 26 deletions(-) diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index 6a5bc38a..6274e932 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -647,6 +647,9 @@ class MessageEntity extends DataClass implements Insertable { /// The channel cid of which this message is part of final String? channelCid; + /// A Map of [messageText] translations. + final Map? i18n; + /// Message custom extraData final Map? extraData; MessageEntity( @@ -673,6 +676,7 @@ class MessageEntity extends DataClass implements Insertable { this.pinExpires, this.pinnedByUserId, this.channelCid, + this.i18n, this.extraData}); factory MessageEntity.fromData( Map data, GeneratedDatabase db, @@ -725,7 +729,9 @@ class MessageEntity extends DataClass implements Insertable { .mapFromDatabaseResponse(data['${effectivePrefix}pinned_by_user_id']), channelCid: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), - extraData: $MessagesTable.$converter5.mapToDart(const StringType() + i18n: $MessagesTable.$converter5.mapToDart(const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}i18n'])), + extraData: $MessagesTable.$converter6.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); } @@ -797,8 +803,12 @@ class MessageEntity extends DataClass implements Insertable { if (!nullToAbsent || channelCid != null) { map['channel_cid'] = Variable(channelCid); } - if (!nullToAbsent || extraData != null) { + if (!nullToAbsent || i18n != null) { final converter = $MessagesTable.$converter5; + map['i18n'] = Variable(converter.mapToSql(i18n)); + } + if (!nullToAbsent || extraData != null) { + final converter = $MessagesTable.$converter6; map['extra_data'] = Variable(converter.mapToSql(extraData)); } return map; @@ -833,6 +843,7 @@ class MessageEntity extends DataClass implements Insertable { pinExpires: serializer.fromJson(json['pinExpires']), pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), channelCid: serializer.fromJson(json['channelCid']), + i18n: serializer.fromJson?>(json['i18n']), extraData: serializer.fromJson?>(json['extraData']), ); } @@ -863,6 +874,7 @@ class MessageEntity extends DataClass implements Insertable { 'pinExpires': serializer.toJson(pinExpires), 'pinnedByUserId': serializer.toJson(pinnedByUserId), 'channelCid': serializer.toJson(channelCid), + 'i18n': serializer.toJson?>(i18n), 'extraData': serializer.toJson?>(extraData), }; } @@ -891,6 +903,7 @@ class MessageEntity extends DataClass implements Insertable { Value pinExpires = const Value.absent(), Value pinnedByUserId = const Value.absent(), Value channelCid = const Value.absent(), + Value?> i18n = const Value.absent(), Value?> extraData = const Value.absent()}) => MessageEntity( id: id ?? this.id, @@ -922,6 +935,7 @@ class MessageEntity extends DataClass implements Insertable { pinnedByUserId: pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, channelCid: channelCid.present ? channelCid.value : this.channelCid, + i18n: i18n.present ? i18n.value : this.i18n, extraData: extraData.present ? extraData.value : this.extraData, ); @override @@ -950,6 +964,7 @@ class MessageEntity extends DataClass implements Insertable { ..write('pinExpires: $pinExpires, ') ..write('pinnedByUserId: $pinnedByUserId, ') ..write('channelCid: $channelCid, ') + ..write('i18n: $i18n, ') ..write('extraData: $extraData') ..write(')')) .toString(); @@ -998,7 +1013,7 @@ class MessageEntity extends DataClass implements Insertable { .hashCode, $mrjc( pinned.hashCode, - $mrjc(pinnedAt.hashCode, $mrjc(pinExpires.hashCode, $mrjc(pinnedByUserId.hashCode, $mrjc(channelCid.hashCode, extraData.hashCode)))))))))))))))))))))))); + $mrjc(pinnedAt.hashCode, $mrjc(pinExpires.hashCode, $mrjc(pinnedByUserId.hashCode, $mrjc(channelCid.hashCode, $mrjc(i18n.hashCode, extraData.hashCode))))))))))))))))))))))))); @override bool operator ==(Object other) => identical(this, other) || @@ -1026,6 +1041,7 @@ class MessageEntity extends DataClass implements Insertable { other.pinExpires == this.pinExpires && other.pinnedByUserId == this.pinnedByUserId && other.channelCid == this.channelCid && + other.i18n == this.i18n && other.extraData == this.extraData); } @@ -1053,6 +1069,7 @@ class MessagesCompanion extends UpdateCompanion { final Value pinExpires; final Value pinnedByUserId; final Value channelCid; + final Value?> i18n; final Value?> extraData; const MessagesCompanion({ this.id = const Value.absent(), @@ -1078,6 +1095,7 @@ class MessagesCompanion extends UpdateCompanion { this.pinExpires = const Value.absent(), this.pinnedByUserId = const Value.absent(), this.channelCid = const Value.absent(), + this.i18n = const Value.absent(), this.extraData = const Value.absent(), }); MessagesCompanion.insert({ @@ -1104,6 +1122,7 @@ class MessagesCompanion extends UpdateCompanion { this.pinExpires = const Value.absent(), this.pinnedByUserId = const Value.absent(), this.channelCid = const Value.absent(), + this.i18n = const Value.absent(), this.extraData = const Value.absent(), }) : id = Value(id), attachments = Value(attachments), @@ -1132,6 +1151,7 @@ class MessagesCompanion extends UpdateCompanion { Expression? pinExpires, Expression? pinnedByUserId, Expression? channelCid, + Expression?>? i18n, Expression?>? extraData, }) { return RawValuesInsertable({ @@ -1158,6 +1178,7 @@ class MessagesCompanion extends UpdateCompanion { if (pinExpires != null) 'pin_expires': pinExpires, if (pinnedByUserId != null) 'pinned_by_user_id': pinnedByUserId, if (channelCid != null) 'channel_cid': channelCid, + if (i18n != null) 'i18n': i18n, if (extraData != null) 'extra_data': extraData, }); } @@ -1186,6 +1207,7 @@ class MessagesCompanion extends UpdateCompanion { Value? pinExpires, Value? pinnedByUserId, Value? channelCid, + Value?>? i18n, Value?>? extraData}) { return MessagesCompanion( id: id ?? this.id, @@ -1211,6 +1233,7 @@ class MessagesCompanion extends UpdateCompanion { pinExpires: pinExpires ?? this.pinExpires, pinnedByUserId: pinnedByUserId ?? this.pinnedByUserId, channelCid: channelCid ?? this.channelCid, + i18n: i18n ?? this.i18n, extraData: extraData ?? this.extraData, ); } @@ -1296,8 +1319,12 @@ class MessagesCompanion extends UpdateCompanion { if (channelCid.present) { map['channel_cid'] = Variable(channelCid.value); } - if (extraData.present) { + if (i18n.present) { final converter = $MessagesTable.$converter5; + map['i18n'] = Variable(converter.mapToSql(i18n.value)); + } + if (extraData.present) { + final converter = $MessagesTable.$converter6; map['extra_data'] = Variable(converter.mapToSql(extraData.value)); } @@ -1330,6 +1357,7 @@ class MessagesCompanion extends UpdateCompanion { ..write('pinExpires: $pinExpires, ') ..write('pinnedByUserId: $pinnedByUserId, ') ..write('channelCid: $channelCid, ') + ..write('i18n: $i18n, ') ..write('extraData: $extraData') ..write(')')) .toString(); @@ -1468,11 +1496,16 @@ class $MessagesTable extends Messages requiredDuringInsert: false, $customConstraints: 'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE'); + final VerificationMeta _i18nMeta = const VerificationMeta('i18n'); + late final GeneratedColumnWithTypeConverter, String?> + i18n = GeneratedColumn('i18n', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false) + .withConverter>($MessagesTable.$converter5); final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); late final GeneratedColumnWithTypeConverter, String?> extraData = GeneratedColumn('extra_data', aliasedName, true, typeName: 'TEXT', requiredDuringInsert: false) - .withConverter>($MessagesTable.$converter5); + .withConverter>($MessagesTable.$converter6); @override List get $columns => [ id, @@ -1498,6 +1531,7 @@ class $MessagesTable extends Messages pinExpires, pinnedByUserId, channelCid, + i18n, extraData ]; @override @@ -1601,6 +1635,7 @@ class $MessagesTable extends Messages channelCid.isAcceptableOrUnknown( data['channel_cid']!, _channelCidMeta)); } + context.handle(_i18nMeta, const VerificationResult.success()); context.handle(_extraDataMeta, const VerificationResult.success()); return context; } @@ -1628,7 +1663,9 @@ class $MessagesTable extends Messages MapConverter(); static TypeConverter, String> $converter4 = MapConverter(); - static TypeConverter, String> $converter5 = + static TypeConverter, String> $converter5 = + MapConverter(); + static TypeConverter, String> $converter6 = MapConverter(); } @@ -1704,6 +1741,9 @@ class PinnedMessageEntity extends DataClass /// The channel cid of which this message is part of final String? channelCid; + /// A Map of [messageText] translations. + final Map? i18n; + /// Message custom extraData final Map? extraData; PinnedMessageEntity( @@ -1730,6 +1770,7 @@ class PinnedMessageEntity extends DataClass this.pinExpires, this.pinnedByUserId, this.channelCid, + this.i18n, this.extraData}); factory PinnedMessageEntity.fromData( Map data, GeneratedDatabase db, @@ -1785,7 +1826,9 @@ class PinnedMessageEntity extends DataClass .mapFromDatabaseResponse(data['${effectivePrefix}pinned_by_user_id']), channelCid: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), - extraData: $PinnedMessagesTable.$converter5.mapToDart(const StringType() + i18n: $PinnedMessagesTable.$converter5.mapToDart(const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}i18n'])), + extraData: $PinnedMessagesTable.$converter6.mapToDart(const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); } @@ -1857,8 +1900,12 @@ class PinnedMessageEntity extends DataClass if (!nullToAbsent || channelCid != null) { map['channel_cid'] = Variable(channelCid); } - if (!nullToAbsent || extraData != null) { + if (!nullToAbsent || i18n != null) { final converter = $PinnedMessagesTable.$converter5; + map['i18n'] = Variable(converter.mapToSql(i18n)); + } + if (!nullToAbsent || extraData != null) { + final converter = $PinnedMessagesTable.$converter6; map['extra_data'] = Variable(converter.mapToSql(extraData)); } return map; @@ -1893,6 +1940,7 @@ class PinnedMessageEntity extends DataClass pinExpires: serializer.fromJson(json['pinExpires']), pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), channelCid: serializer.fromJson(json['channelCid']), + i18n: serializer.fromJson?>(json['i18n']), extraData: serializer.fromJson?>(json['extraData']), ); } @@ -1923,6 +1971,7 @@ class PinnedMessageEntity extends DataClass 'pinExpires': serializer.toJson(pinExpires), 'pinnedByUserId': serializer.toJson(pinnedByUserId), 'channelCid': serializer.toJson(channelCid), + 'i18n': serializer.toJson?>(i18n), 'extraData': serializer.toJson?>(extraData), }; } @@ -1951,6 +2000,7 @@ class PinnedMessageEntity extends DataClass Value pinExpires = const Value.absent(), Value pinnedByUserId = const Value.absent(), Value channelCid = const Value.absent(), + Value?> i18n = const Value.absent(), Value?> extraData = const Value.absent()}) => PinnedMessageEntity( id: id ?? this.id, @@ -1982,6 +2032,7 @@ class PinnedMessageEntity extends DataClass pinnedByUserId: pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, channelCid: channelCid.present ? channelCid.value : this.channelCid, + i18n: i18n.present ? i18n.value : this.i18n, extraData: extraData.present ? extraData.value : this.extraData, ); @override @@ -2010,6 +2061,7 @@ class PinnedMessageEntity extends DataClass ..write('pinExpires: $pinExpires, ') ..write('pinnedByUserId: $pinnedByUserId, ') ..write('channelCid: $channelCid, ') + ..write('i18n: $i18n, ') ..write('extraData: $extraData') ..write(')')) .toString(); @@ -2058,7 +2110,7 @@ class PinnedMessageEntity extends DataClass .hashCode, $mrjc( pinned.hashCode, - $mrjc(pinnedAt.hashCode, $mrjc(pinExpires.hashCode, $mrjc(pinnedByUserId.hashCode, $mrjc(channelCid.hashCode, extraData.hashCode)))))))))))))))))))))))); + $mrjc(pinnedAt.hashCode, $mrjc(pinExpires.hashCode, $mrjc(pinnedByUserId.hashCode, $mrjc(channelCid.hashCode, $mrjc(i18n.hashCode, extraData.hashCode))))))))))))))))))))))))); @override bool operator ==(Object other) => identical(this, other) || @@ -2086,6 +2138,7 @@ class PinnedMessageEntity extends DataClass other.pinExpires == this.pinExpires && other.pinnedByUserId == this.pinnedByUserId && other.channelCid == this.channelCid && + other.i18n == this.i18n && other.extraData == this.extraData); } @@ -2113,6 +2166,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { final Value pinExpires; final Value pinnedByUserId; final Value channelCid; + final Value?> i18n; final Value?> extraData; const PinnedMessagesCompanion({ this.id = const Value.absent(), @@ -2138,6 +2192,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { this.pinExpires = const Value.absent(), this.pinnedByUserId = const Value.absent(), this.channelCid = const Value.absent(), + this.i18n = const Value.absent(), this.extraData = const Value.absent(), }); PinnedMessagesCompanion.insert({ @@ -2164,6 +2219,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { this.pinExpires = const Value.absent(), this.pinnedByUserId = const Value.absent(), this.channelCid = const Value.absent(), + this.i18n = const Value.absent(), this.extraData = const Value.absent(), }) : id = Value(id), attachments = Value(attachments), @@ -2192,6 +2248,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { Expression? pinExpires, Expression? pinnedByUserId, Expression? channelCid, + Expression?>? i18n, Expression?>? extraData, }) { return RawValuesInsertable({ @@ -2218,6 +2275,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { if (pinExpires != null) 'pin_expires': pinExpires, if (pinnedByUserId != null) 'pinned_by_user_id': pinnedByUserId, if (channelCid != null) 'channel_cid': channelCid, + if (i18n != null) 'i18n': i18n, if (extraData != null) 'extra_data': extraData, }); } @@ -2246,6 +2304,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { Value? pinExpires, Value? pinnedByUserId, Value? channelCid, + Value?>? i18n, Value?>? extraData}) { return PinnedMessagesCompanion( id: id ?? this.id, @@ -2271,6 +2330,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { pinExpires: pinExpires ?? this.pinExpires, pinnedByUserId: pinnedByUserId ?? this.pinnedByUserId, channelCid: channelCid ?? this.channelCid, + i18n: i18n ?? this.i18n, extraData: extraData ?? this.extraData, ); } @@ -2356,8 +2416,12 @@ class PinnedMessagesCompanion extends UpdateCompanion { if (channelCid.present) { map['channel_cid'] = Variable(channelCid.value); } - if (extraData.present) { + if (i18n.present) { final converter = $PinnedMessagesTable.$converter5; + map['i18n'] = Variable(converter.mapToSql(i18n.value)); + } + if (extraData.present) { + final converter = $PinnedMessagesTable.$converter6; map['extra_data'] = Variable(converter.mapToSql(extraData.value)); } @@ -2390,6 +2454,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { ..write('pinExpires: $pinExpires, ') ..write('pinnedByUserId: $pinnedByUserId, ') ..write('channelCid: $channelCid, ') + ..write('i18n: $i18n, ') ..write('extraData: $extraData') ..write(')')) .toString(); @@ -2529,12 +2594,17 @@ class $PinnedMessagesTable extends PinnedMessages requiredDuringInsert: false, $customConstraints: 'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE'); + final VerificationMeta _i18nMeta = const VerificationMeta('i18n'); + late final GeneratedColumnWithTypeConverter, String?> + i18n = GeneratedColumn('i18n', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false) + .withConverter>($PinnedMessagesTable.$converter5); final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); late final GeneratedColumnWithTypeConverter, String?> extraData = GeneratedColumn('extra_data', aliasedName, true, typeName: 'TEXT', requiredDuringInsert: false) .withConverter>( - $PinnedMessagesTable.$converter5); + $PinnedMessagesTable.$converter6); @override List get $columns => [ id, @@ -2560,6 +2630,7 @@ class $PinnedMessagesTable extends PinnedMessages pinExpires, pinnedByUserId, channelCid, + i18n, extraData ]; @override @@ -2664,6 +2735,7 @@ class $PinnedMessagesTable extends PinnedMessages channelCid.isAcceptableOrUnknown( data['channel_cid']!, _channelCidMeta)); } + context.handle(_i18nMeta, const VerificationResult.success()); context.handle(_extraDataMeta, const VerificationResult.success()); return context; } @@ -2691,7 +2763,9 @@ class $PinnedMessagesTable extends PinnedMessages MapConverter(); static TypeConverter, String> $converter4 = MapConverter(); - static TypeConverter, String> $converter5 = + static TypeConverter, String> $converter5 = + MapConverter(); + static TypeConverter, String> $converter6 = MapConverter(); } @@ -3030,6 +3104,9 @@ class UserEntity extends DataClass implements Insertable { /// User role final String? role; + /// The language this user prefers. + final String language; + /// Date of user creation final DateTime createdAt; @@ -3050,6 +3127,7 @@ class UserEntity extends DataClass implements Insertable { UserEntity( {required this.id, this.role, + required this.language, required this.createdAt, required this.updatedAt, this.lastActive, @@ -3064,6 +3142,8 @@ class UserEntity extends DataClass implements Insertable { .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, role: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}role']), + language: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}language'])!, createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, updatedAt: const DateTimeType() @@ -3085,6 +3165,7 @@ class UserEntity extends DataClass implements Insertable { if (!nullToAbsent || role != null) { map['role'] = Variable(role); } + map['language'] = Variable(language); map['created_at'] = Variable(createdAt); map['updated_at'] = Variable(updatedAt); if (!nullToAbsent || lastActive != null) { @@ -3105,6 +3186,7 @@ class UserEntity extends DataClass implements Insertable { return UserEntity( id: serializer.fromJson(json['id']), role: serializer.fromJson(json['role']), + language: serializer.fromJson(json['language']), createdAt: serializer.fromJson(json['createdAt']), updatedAt: serializer.fromJson(json['updatedAt']), lastActive: serializer.fromJson(json['lastActive']), @@ -3119,6 +3201,7 @@ class UserEntity extends DataClass implements Insertable { return { 'id': serializer.toJson(id), 'role': serializer.toJson(role), + 'language': serializer.toJson(language), 'createdAt': serializer.toJson(createdAt), 'updatedAt': serializer.toJson(updatedAt), 'lastActive': serializer.toJson(lastActive), @@ -3131,6 +3214,7 @@ class UserEntity extends DataClass implements Insertable { UserEntity copyWith( {String? id, Value role = const Value.absent(), + String? language, DateTime? createdAt, DateTime? updatedAt, Value lastActive = const Value.absent(), @@ -3140,6 +3224,7 @@ class UserEntity extends DataClass implements Insertable { UserEntity( id: id ?? this.id, role: role.present ? role.value : this.role, + language: language ?? this.language, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, lastActive: lastActive.present ? lastActive.value : this.lastActive, @@ -3152,6 +3237,7 @@ class UserEntity extends DataClass implements Insertable { return (StringBuffer('UserEntity(') ..write('id: $id, ') ..write('role: $role, ') + ..write('language: $language, ') ..write('createdAt: $createdAt, ') ..write('updatedAt: $updatedAt, ') ..write('lastActive: $lastActive, ') @@ -3168,19 +3254,24 @@ class UserEntity extends DataClass implements Insertable { $mrjc( role.hashCode, $mrjc( - createdAt.hashCode, + language.hashCode, $mrjc( - updatedAt.hashCode, + createdAt.hashCode, $mrjc( - lastActive.hashCode, - $mrjc(online.hashCode, - $mrjc(banned.hashCode, extraData.hashCode)))))))); + updatedAt.hashCode, + $mrjc( + lastActive.hashCode, + $mrjc( + online.hashCode, + $mrjc( + banned.hashCode, extraData.hashCode))))))))); @override bool operator ==(Object other) => identical(this, other) || (other is UserEntity && other.id == this.id && other.role == this.role && + other.language == this.language && other.createdAt == this.createdAt && other.updatedAt == this.updatedAt && other.lastActive == this.lastActive && @@ -3192,6 +3283,7 @@ class UserEntity extends DataClass implements Insertable { class UsersCompanion extends UpdateCompanion { final Value id; final Value role; + final Value language; final Value createdAt; final Value updatedAt; final Value lastActive; @@ -3201,6 +3293,7 @@ class UsersCompanion extends UpdateCompanion { const UsersCompanion({ this.id = const Value.absent(), this.role = const Value.absent(), + this.language = const Value.absent(), this.createdAt = const Value.absent(), this.updatedAt = const Value.absent(), this.lastActive = const Value.absent(), @@ -3211,6 +3304,7 @@ class UsersCompanion extends UpdateCompanion { UsersCompanion.insert({ required String id, this.role = const Value.absent(), + this.language = const Value.absent(), this.createdAt = const Value.absent(), this.updatedAt = const Value.absent(), this.lastActive = const Value.absent(), @@ -3222,6 +3316,7 @@ class UsersCompanion extends UpdateCompanion { static Insertable custom({ Expression? id, Expression? role, + Expression? language, Expression? createdAt, Expression? updatedAt, Expression? lastActive, @@ -3232,6 +3327,7 @@ class UsersCompanion extends UpdateCompanion { return RawValuesInsertable({ if (id != null) 'id': id, if (role != null) 'role': role, + if (language != null) 'language': language, if (createdAt != null) 'created_at': createdAt, if (updatedAt != null) 'updated_at': updatedAt, if (lastActive != null) 'last_active': lastActive, @@ -3244,6 +3340,7 @@ class UsersCompanion extends UpdateCompanion { UsersCompanion copyWith( {Value? id, Value? role, + Value? language, Value? createdAt, Value? updatedAt, Value? lastActive, @@ -3253,6 +3350,7 @@ class UsersCompanion extends UpdateCompanion { return UsersCompanion( id: id ?? this.id, role: role ?? this.role, + language: language ?? this.language, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, lastActive: lastActive ?? this.lastActive, @@ -3271,6 +3369,9 @@ class UsersCompanion extends UpdateCompanion { if (role.present) { map['role'] = Variable(role.value); } + if (language.present) { + map['language'] = Variable(language.value); + } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); } @@ -3299,6 +3400,7 @@ class UsersCompanion extends UpdateCompanion { return (StringBuffer('UsersCompanion(') ..write('id: $id, ') ..write('role: $role, ') + ..write('language: $language, ') ..write('createdAt: $createdAt, ') ..write('updatedAt: $updatedAt, ') ..write('lastActive: $lastActive, ') @@ -3322,6 +3424,12 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { late final GeneratedColumn role = GeneratedColumn( 'role', aliasedName, true, typeName: 'TEXT', requiredDuringInsert: false); + final VerificationMeta _languageMeta = const VerificationMeta('language'); + late final GeneratedColumn language = GeneratedColumn( + 'language', aliasedName, false, + typeName: 'TEXT', + requiredDuringInsert: false, + defaultValue: const Constant('en')); final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); late final GeneratedColumn createdAt = GeneratedColumn( 'created_at', aliasedName, false, @@ -3358,8 +3466,17 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { typeName: 'TEXT', requiredDuringInsert: true) .withConverter>($UsersTable.$converter0); @override - List get $columns => - [id, role, createdAt, updatedAt, lastActive, online, banned, extraData]; + List get $columns => [ + id, + role, + language, + createdAt, + updatedAt, + lastActive, + online, + banned, + extraData + ]; @override String get aliasedName => _alias ?? 'users'; @override @@ -3378,6 +3495,10 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { context.handle( _roleMeta, role.isAcceptableOrUnknown(data['role']!, _roleMeta)); } + if (data.containsKey('language')) { + context.handle(_languageMeta, + language.isAcceptableOrUnknown(data['language']!, _languageMeta)); + } if (data.containsKey('created_at')) { context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); diff --git a/packages/stream_chat_persistence/lib/src/entity/messages.dart b/packages/stream_chat_persistence/lib/src/entity/messages.dart index 4006d6ce..0aef2b84 100644 --- a/packages/stream_chat_persistence/lib/src/entity/messages.dart +++ b/packages/stream_chat_persistence/lib/src/entity/messages.dart @@ -80,6 +80,9 @@ class Messages extends Table { TextColumn get channelCid => text().nullable().customConstraint( 'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE')(); + /// A Map of [messageText] translations. + TextColumn get i18n => text().nullable().map(MapConverter())(); + /// Message custom extraData TextColumn get extraData => text().nullable().map(MapConverter())(); diff --git a/packages/stream_chat_persistence/lib/src/entity/users.dart b/packages/stream_chat_persistence/lib/src/entity/users.dart index 303092c9..49aae500 100644 --- a/packages/stream_chat_persistence/lib/src/entity/users.dart +++ b/packages/stream_chat_persistence/lib/src/entity/users.dart @@ -11,6 +11,9 @@ class Users extends Table { /// User role TextColumn get role => text().nullable()(); + /// The language this user prefers. + TextColumn get language => text().withDefault(const Constant('en'))(); + /// Date of user creation DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); diff --git a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart index 558c6e45..85692296 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart @@ -44,6 +44,7 @@ extension MessageEntityX on MessageEntity { pinnedBy: pinnedBy, mentionedUsers: mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), + i18n: i18n, ); } @@ -75,5 +76,6 @@ extension MessageX on Message { pinnedAt: pinnedAt, pinExpires: pinExpires, pinnedByUserId: pinnedBy?.id, + i18n: i18n, ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart index 1083e239..b1a7849a 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart @@ -42,6 +42,9 @@ extension PinnedMessageEntityX on PinnedMessageEntity { pinnedAt: pinnedAt, pinExpires: pinExpires, pinnedBy: pinnedBy, + mentionedUsers: + mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), + i18n: i18n, ); } @@ -73,5 +76,6 @@ extension PMessageX on Message { pinnedAt: pinnedAt, pinExpires: pinExpires, pinnedByUserId: pinnedBy?.id, + i18n: i18n, ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart index 533a45b1..8b38a74d 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart @@ -7,6 +7,7 @@ extension UserEntityX on UserEntity { User toUser() => User( id: id, updatedAt: updatedAt, + language: language, role: role, online: online, lastActive: lastActive, @@ -22,6 +23,7 @@ extension UserX on User { UserEntity toEntity() => UserEntity( id: id, role: role, + language: language, createdAt: createdAt, updatedAt: updatedAt, lastActive: lastActive, diff --git a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart index 54cc219a..fd0a43ce 100644 --- a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart @@ -35,10 +35,15 @@ void main() { replyCount: index, updatedAt: DateTime.now(), extraData: const {'extra_test_field': 'extraTestData'}, - text: 'Dummy text #$index', + text: 'Hello #$index', pinned: math.Random().nextBool(), pinnedAt: DateTime.now(), pinnedBy: User(id: 'testUserId$index'), + i18n: { + 'en_text': 'Hello #$index', + 'hi_text': 'नमस्ते #$index', + 'language': 'en', + }, ), ); final quotedMessages = List.generate( @@ -52,11 +57,16 @@ void main() { replyCount: index, updatedAt: DateTime.now(), extraData: const {'extra_test_field': 'extraTestData'}, - text: 'Dummy text #$index', + text: 'Hello #$index', quotedMessageId: messages[index].id, pinned: math.Random().nextBool(), pinnedAt: DateTime.now(), pinnedBy: User(id: 'testUserId$index'), + i18n: { + 'en_text': 'Hello #$index', + 'hi_text': 'नमस्ते #$index', + 'language': 'en', + }, ), ); final threadMessages = List.generate( @@ -72,10 +82,15 @@ void main() { replyCount: index, updatedAt: DateTime.now(), extraData: const {'extra_test_field': 'extraTestData'}, - text: 'Dummy text #$index', + text: 'Hello #$index', pinned: math.Random().nextBool(), pinnedAt: DateTime.now(), pinnedBy: User(id: 'testUserId$index'), + i18n: { + 'en_text': 'Hello #$index', + 'hi_text': 'नमस्ते #$index', + 'language': 'en', + }, ), ); final allMessages = [ diff --git a/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart index ffb50edd..5b5d04d4 100644 --- a/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart @@ -22,6 +22,7 @@ void main() { (index) => User( id: 'testUserId$index', role: 'testRole', + language: 'hi', createdAt: DateTime.now(), updatedAt: DateTime.now(), lastActive: DateTime.now(), diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart index 7d867deb..0f9e8cf6 100644 --- a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -56,11 +56,16 @@ void main() { extraData: {'extra_test_data': 'extraData'}, userId: user.id, deletedAt: DateTime.now(), - messageText: 'dummy text', + messageText: 'Hello', pinned: true, pinExpires: DateTime.now().toUtc(), pinnedAt: DateTime.now(), pinnedByUserId: user.id, + i18n: const { + 'en_text': 'Hello', + 'hi_text': 'नमस्ते', + 'language': 'en', + }, ); final message = entity.toMessage( user: user, @@ -99,6 +104,7 @@ void main() { expect(message.pinnedBy!.id, entity.pinnedByUserId); expect(message.reactionCounts, entity.reactionCounts); expect(message.reactionScores, entity.reactionScores); + expect(message.i18n, entity.i18n); for (var i = 0; i < message.attachments.length; i++) { final messageAttachment = message.attachments[i]; final entityAttachmentData = jsonDecode(entity.attachments[i]); @@ -156,11 +162,16 @@ void main() { extraData: const {'extra_test_data': 'extraData'}, user: user, deletedAt: DateTime.now(), - text: 'dummy text', + text: 'Hello', pinned: true, pinExpires: DateTime.now(), pinnedAt: DateTime.now(), pinnedBy: user, + i18n: const { + 'en_text': 'Hello', + 'hi_text': 'नमस्ते', + 'language': 'en', + }, ); final entity = message.toEntity(cid: cid); expect(entity, isA()); @@ -193,5 +204,6 @@ void main() { entity.attachments, message.attachments.map((it) => jsonEncode(it.toData())).toList(), ); + expect(entity.i18n, message.i18n); }); } diff --git a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart index 8b5f4db4..5150df62 100644 --- a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart @@ -54,11 +54,16 @@ void main() { extraData: {'extra_test_data': 'extraData'}, userId: user.id, deletedAt: DateTime.now(), - messageText: 'dummy text', + messageText: 'Hello', pinned: true, pinExpires: DateTime.now().toUtc(), pinnedAt: DateTime.now(), pinnedByUserId: user.id, + i18n: const { + 'en_text': 'Hello', + 'hi_text': 'नमस्ते', + 'language': 'en', + }, ); final message = entity.toMessage( user: user, @@ -92,6 +97,7 @@ void main() { expect(message.pinnedBy!.id, entity.pinnedByUserId); expect(message.reactionCounts, entity.reactionCounts); expect(message.reactionScores, entity.reactionScores); + expect(message.i18n, entity.i18n); for (var i = 0; i < message.attachments.length; i++) { final messageAttachment = message.attachments[i]; final entityAttachmentData = jsonDecode(entity.attachments[i]); @@ -146,11 +152,16 @@ void main() { extraData: const {'extra_test_data': 'extraData'}, user: user, deletedAt: DateTime.now(), - text: 'dummy text', + text: 'Hello', pinned: true, pinExpires: DateTime.now(), pinnedAt: DateTime.now(), pinnedBy: user, + i18n: const { + 'en_text': 'Hello', + 'hi_text': 'नमस्ते', + 'language': 'en', + }, ); final entity = message.toPinnedEntity(cid: cid); expect(entity, isA()); @@ -181,5 +192,6 @@ void main() { entity.attachments, message.attachments.map((it) => jsonEncode(it.toData())).toList(), ); + expect(entity.i18n, message.i18n); }); } diff --git a/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart index 4454ad73..eca2a32a 100644 --- a/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart @@ -11,6 +11,7 @@ void main() { final entity = UserEntity( id: 'testUserId', role: 'testType', + language: 'hi', createdAt: DateTime.now(), updatedAt: DateTime.now(), lastActive: DateTime.now(), @@ -22,6 +23,7 @@ void main() { expect(user, isA()); expect(user.id, entity.id); expect(user.role, entity.role); + expect(user.language, entity.language); expect(user.createdAt, isSameDateAs(entity.createdAt)); expect(user.updatedAt, isSameDateAs(entity.updatedAt)); expect(user.lastActive, isSameDateAs(entity.lastActive!)); @@ -34,6 +36,7 @@ void main() { final user = User( id: 'testUserId', role: 'testType', + language: 'hi', createdAt: DateTime.now(), updatedAt: DateTime.now(), lastActive: DateTime.now(), @@ -45,6 +48,7 @@ void main() { expect(entity, isA()); expect(entity.id, user.id); expect(entity.role, user.role); + expect(entity.language, user.language); expect(entity.createdAt, isSameDateAs(user.createdAt)); expect(entity.updatedAt, isSameDateAs(user.updatedAt)); expect(entity.lastActive, isSameDateAs(user.lastActive!)); From 39449e57008899bfe9788816518c04ac6e5af74d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Jul 2021 20:07:55 +0530 Subject: [PATCH 059/113] chore(persistence): update CHANGELOG.md Signed-off-by: xsahil03x --- packages/stream_chat_persistence/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index 3a65db58..466d1e05 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +✅ Added +- Added support for `Message.i18n` +- Added support for `User.language` + ## 2.0.0 * Migrate this package to null safety * Minor fixes and improvements From f6cf835b4e439945e92fb2d801c798c135cb34ea Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Jul 2021 20:23:28 +0530 Subject: [PATCH 060/113] fix(llc): update currentUser after successful connection Signed-off-by: xsahil03x --- packages/stream_chat/lib/src/client/client.dart | 4 ++-- packages/stream_chat_flutter/example/lib/main.dart | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index e1212e83..b25ebe88 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -325,8 +325,8 @@ class StreamChatClient { _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(); diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 42891aa7..76ba60b5 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -23,16 +23,12 @@ void main() async { /// /// 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', language: 'fr'), + final res = await client.connectUser( + User(id: 'super-band-9'), 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.' '0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); - client.on().listen((e) { - print('e.user.language ${e.me?.language}'); - }); - final channel = client.channel('messaging', id: 'godevs'); await channel.watch(); From ba251e4d88a52a18648bb3ce0c3413b4374b9beb Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 27 Jul 2021 20:25:26 +0530 Subject: [PATCH 061/113] chore(ui): fix unused variable warning Signed-off-by: xsahil03x --- packages/stream_chat_flutter/example/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 76ba60b5..89362d81 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -23,7 +23,7 @@ void main() async { /// /// Please see the following for more information: /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ - final res = await client.connectUser( + await client.connectUser( User(id: 'super-band-9'), 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.' '0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', From fe7a8a1ed0d3d995dcaa784f64236ec6cde5207b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 11:20:07 +0200 Subject: [PATCH 062/113] make language nullable --- .../lib/src/core/models/own_user.dart | 2 +- .../lib/src/core/models/own_user.g.dart | 2 +- .../stream_chat/lib/src/core/models/user.dart | 10 +--- .../lib/src/core/models/user.g.dart | 4 +- .../test/utils/golden.dart | 58 +++++++++++++++++++ .../lib/src/db/moor_chat_database.g.dart | 32 +++++----- .../lib/src/entity/users.dart | 2 +- 7 files changed, 82 insertions(+), 28 deletions(-) create mode 100644 packages/stream_chat_flutter/test/utils/golden.dart 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 116a2f84..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,7 +27,7 @@ class OwnUser extends User { Map extraData = const {}, bool banned = false, List teams = const [], - String language = 'en', + String? language, }) : super( id: id, role: role, 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 5d80cc1c..f5ad08a7 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,6 +39,6 @@ OwnUser _$OwnUserFromJson(Map json) { teams: (json['teams'] as List?)?.map((e) => e as String).toList() ?? [], - language: json['language'] as String? ?? 'en', + 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 dd4abcc6..a7e7a51e 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -18,7 +18,7 @@ class User extends Equatable { this.extraData = const {}, this.banned = false, this.teams = const [], - this.language = 'en', + this.language, }) : createdAt = createdAt ?? DateTime.now(), updatedAt = updatedAt ?? DateTime.now(); @@ -87,12 +87,8 @@ class User extends Equatable { /// The language this user prefers. /// /// Defaults to 'en'. - @JsonKey( - includeIfNull: false, - toJson: Serializer.readOnly, - defaultValue: 'en', - ) - final String language; + @JsonKey(includeIfNull: false) + final String? language; /// Shortcut for user name String get name { 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 7b193f04..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,7 +25,7 @@ User _$UserFromJson(Map json) { teams: (json['teams'] as List?)?.map((e) => e as String).toList() ?? [], - language: json['language'] as String? ?? 'en', + language: json['language'] as String?, ); } @@ -48,6 +48,6 @@ Map _$UserToJson(User instance) { writeNotNull('online', readonly(instance.online)); writeNotNull('banned', readonly(instance.banned)); val['extra_data'] = instance.extraData; - writeNotNull('language', readonly(instance.language)); + writeNotNull('language', instance.language); return val; } 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_persistence/lib/src/db/moor_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index 6274e932..a61d1e6e 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -3105,7 +3105,7 @@ class UserEntity extends DataClass implements Insertable { final String? role; /// The language this user prefers. - final String language; + final String? language; /// Date of user creation final DateTime createdAt; @@ -3127,7 +3127,7 @@ class UserEntity extends DataClass implements Insertable { UserEntity( {required this.id, this.role, - required this.language, + this.language, required this.createdAt, required this.updatedAt, this.lastActive, @@ -3143,7 +3143,7 @@ class UserEntity extends DataClass implements Insertable { role: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}role']), language: const StringType() - .mapFromDatabaseResponse(data['${effectivePrefix}language'])!, + .mapFromDatabaseResponse(data['${effectivePrefix}language']), createdAt: const DateTimeType() .mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!, updatedAt: const DateTimeType() @@ -3165,7 +3165,9 @@ class UserEntity extends DataClass implements Insertable { if (!nullToAbsent || role != null) { map['role'] = Variable(role); } - map['language'] = Variable(language); + if (!nullToAbsent || language != null) { + map['language'] = Variable(language); + } map['created_at'] = Variable(createdAt); map['updated_at'] = Variable(updatedAt); if (!nullToAbsent || lastActive != null) { @@ -3186,7 +3188,7 @@ class UserEntity extends DataClass implements Insertable { return UserEntity( id: serializer.fromJson(json['id']), role: serializer.fromJson(json['role']), - language: serializer.fromJson(json['language']), + language: serializer.fromJson(json['language']), createdAt: serializer.fromJson(json['createdAt']), updatedAt: serializer.fromJson(json['updatedAt']), lastActive: serializer.fromJson(json['lastActive']), @@ -3201,7 +3203,7 @@ class UserEntity extends DataClass implements Insertable { return { 'id': serializer.toJson(id), 'role': serializer.toJson(role), - 'language': serializer.toJson(language), + 'language': serializer.toJson(language), 'createdAt': serializer.toJson(createdAt), 'updatedAt': serializer.toJson(updatedAt), 'lastActive': serializer.toJson(lastActive), @@ -3214,7 +3216,7 @@ class UserEntity extends DataClass implements Insertable { UserEntity copyWith( {String? id, Value role = const Value.absent(), - String? language, + Value language = const Value.absent(), DateTime? createdAt, DateTime? updatedAt, Value lastActive = const Value.absent(), @@ -3224,7 +3226,7 @@ class UserEntity extends DataClass implements Insertable { UserEntity( id: id ?? this.id, role: role.present ? role.value : this.role, - language: language ?? this.language, + language: language.present ? language.value : this.language, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, lastActive: lastActive.present ? lastActive.value : this.lastActive, @@ -3283,7 +3285,7 @@ class UserEntity extends DataClass implements Insertable { class UsersCompanion extends UpdateCompanion { final Value id; final Value role; - final Value language; + final Value language; final Value createdAt; final Value updatedAt; final Value lastActive; @@ -3316,7 +3318,7 @@ class UsersCompanion extends UpdateCompanion { static Insertable custom({ Expression? id, Expression? role, - Expression? language, + Expression? language, Expression? createdAt, Expression? updatedAt, Expression? lastActive, @@ -3340,7 +3342,7 @@ class UsersCompanion extends UpdateCompanion { UsersCompanion copyWith( {Value? id, Value? role, - Value? language, + Value? language, Value? createdAt, Value? updatedAt, Value? lastActive, @@ -3370,7 +3372,7 @@ class UsersCompanion extends UpdateCompanion { map['role'] = Variable(role.value); } if (language.present) { - map['language'] = Variable(language.value); + map['language'] = Variable(language.value); } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); @@ -3426,10 +3428,8 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _languageMeta = const VerificationMeta('language'); late final GeneratedColumn language = GeneratedColumn( - 'language', aliasedName, false, - typeName: 'TEXT', - requiredDuringInsert: false, - defaultValue: const Constant('en')); + 'language', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); late final GeneratedColumn createdAt = GeneratedColumn( 'created_at', aliasedName, false, diff --git a/packages/stream_chat_persistence/lib/src/entity/users.dart b/packages/stream_chat_persistence/lib/src/entity/users.dart index 49aae500..286bfb40 100644 --- a/packages/stream_chat_persistence/lib/src/entity/users.dart +++ b/packages/stream_chat_persistence/lib/src/entity/users.dart @@ -12,7 +12,7 @@ class Users extends Table { TextColumn get role => text().nullable()(); /// The language this user prefers. - TextColumn get language => text().withDefault(const Constant('en'))(); + TextColumn get language => text().nullable()(); /// Date of user creation DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); From 5954bb2abc968939c64ce7ef5708c4220445a518 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 11:24:10 +0200 Subject: [PATCH 063/113] fix tests --- packages/stream_chat/lib/src/core/models/own_user.g.dart | 2 +- packages/stream_chat/test/fixtures/user.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) 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 f5ad08a7..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,6 +39,6 @@ OwnUser _$OwnUserFromJson(Map json) { teams: (json['teams'] as List?)?.map((e) => e as String).toList() ?? [], - language: json['language'] as String, + language: json['language'] as String?, ); } 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 From 3e88dbedef8a1a0964337e6d6c85a0d5cd73e5a4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 11:25:55 +0200 Subject: [PATCH 064/113] update stream_chat_flutter changelog --- packages/stream_chat_flutter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 41e4fa5d..8843205f 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -3,6 +3,7 @@ ✅ Added - Added `MessageListView.paginationLimit` +- `MessageText` renders message translation if available - Allow the various ListView widgets to be themed via ThemeData classes 🔄 Changed From 0eb85684cf3afdf945ad164839d448eda7b5dc8e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 11:39:26 +0200 Subject: [PATCH 065/113] fix message text with null language --- packages/stream_chat_flutter/lib/src/message_text.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index a1e3a4f8..721e4670 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -31,9 +31,9 @@ class MessageText extends StatelessWidget { Widget build(BuildContext context) { final streamChat = StreamChat.of(context); assert(streamChat.currentUser != null, ''); - return BetterStreamBuilder( - stream: streamChat.currentUserStream.map((it) => it!.language), - initialData: streamChat.currentUser!.language, + 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; From 24bb8ced541978d5b433450b929a853c1e4710cf Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 28 Jul 2021 15:24:19 +0530 Subject: [PATCH 066/113] chore(ui): fix merge changes Signed-off-by: xsahil03x --- packages/stream_chat_flutter/lib/src/visible_footnote.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/visible_footnote.dart b/packages/stream_chat_flutter/lib/src/visible_footnote.dart index bbdb9144..8d16ce41 100644 --- a/packages/stream_chat_flutter/lib/src/visible_footnote.dart +++ b/packages/stream_chat_flutter/lib/src/visible_footnote.dart @@ -1,6 +1,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/src/extension.dart'; /// Widget for displaying a footnote class VisibleFootnote extends StatelessWidget { @@ -19,7 +20,7 @@ class VisibleFootnote extends StatelessWidget { ), const SizedBox(width: 8), Text( - 'Only visible to you', + context.translations.onlyVisibleToYouText, style: chatThemeData.textTheme.footnote .copyWith(color: chatThemeData.colorTheme.textLowEmphasis), ), From 05e3ca1c48eef39b63d662f21160c457fa19db14 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 28 Jul 2021 15:30:11 +0530 Subject: [PATCH 067/113] chore(persistence): update schema version Signed-off-by: xsahil03x --- .../stream_chat_persistence/lib/src/db/moor_chat_database.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart index 447533e0..7f5bd4d0 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart @@ -51,7 +51,7 @@ class MoorChatDatabase extends _$MoorChatDatabase { // you should bump this number whenever you change or add a table definition. @override - int get schemaVersion => 4; + int get schemaVersion => 5; @override MigrationStrategy get migration => MigrationStrategy( From 1107d99076bcaa3b705a2d9bd9fca4e7997876a2 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 12:12:31 +0200 Subject: [PATCH 068/113] update readmes --- README.md | 3 +++ packages/stream_chat_flutter/README.md | 1 - packages/stream_chat_localizations/README.md | 25 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) 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/packages/stream_chat_flutter/README.md b/packages/stream_chat_flutter/README.md index 41ea079d..e79c4b42 100644 --- a/packages/stream_chat_flutter/README.md +++ b/packages/stream_chat_flutter/README.md @@ -15,7 +15,6 @@ - [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/) - [Chat UI Kit](https://getstream.io/chat/ui-kit/) - ### Changelog Check out the [changelog on pub.dev](https://pub.dev/packages/stream_chat_flutter/changelog) to see the latest changes in the package. diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md index 393a414b..49fd4442 100644 --- a/packages/stream_chat_localizations/README.md +++ b/packages/stream_chat_localizations/README.md @@ -20,7 +20,18 @@ This package provides localized strings for the stream chat widgets for many lan Check out the [changelog on pub.dev](https://pub.dev/packages/stream_chat_localizations/changelog) to see the latest changes in the package. +## Supported languages + +At the moment we support the following languages: +- [English](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsEn-class.html) +- [Hindi](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsHi-class.html) +- [Italian](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsIt-class.html) +- [French](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsFr-class.html) + +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 [![Pub](https://img.shields.io/pub/v/stream_chat_localizations.svg)](https://pub.dartlang.org/packages/stream_chat_localizations) ```yaml dependencies: @@ -30,6 +41,7 @@ dependencies: You should then run `flutter packages get` ### Usage + ```dart import 'package:flutter/material.dart'; import 'package:stream_chat_localizations/stream_chat_localizations.dart'; @@ -65,7 +77,20 @@ class MyApp extends StatelessWidget { } ``` +### 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. + +Checkout [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 exisiting 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. + +Checkout [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#specifying-supportedlocales). From f16bb82964501074d671b817e65ac65fb3667a4e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 12:26:29 +0200 Subject: [PATCH 069/113] merge master --- .../docs/Flutter/assets/chat_basics.png | Bin 28913 -> 63987 bytes .../Flutter/guides/migration_guide_2_0.mdx | 2 +- packages/stream_chat/README.md | 1 + packages/stream_chat_flutter/README.md | 2 ++ packages/stream_chat_flutter_core/README.md | 1 + packages/stream_chat_persistence/README.md | 1 + 6 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/assets/chat_basics.png b/docusaurus/docs/Flutter/assets/chat_basics.png index 4eb0aa67639ff65c4331c28c45737a04b88be8f1..82e27cfa49ceac64fa7d27c37f280058397d9693 100644 GIT binary patch literal 63987 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 Nsj-z&{jGaR{|{{gfH(jE literal 28913 zcmc$`1yEhh*X|h-5(vRvLxAA!4#C~s-8HxqEV#RSAh=5i?ryheuvQ>!-Xz4Z|6~)ay}kMOZkr*EUFex^or}qYZcL=z^Qb377k8r*rp52Sd)h zjG<@SoPqRrf`~{@FDw`=iBE!0FIe{HxBsX2t|)=$@Bgd!|Du`yH2+^U^Plhk51Ui` z)I$XkZ*&xqG=`PFB-aVQ{Y^ep-S5iq4lVpx0MTPPi|3$^MRkpoI|7m{*0w#)M?ylvuedq<>xQ8=Z*q@uD(Hr9;_3K_DIF|=%HR+`L!Ehbev?%W4FWHM zz(T|QT_2%N*%=7mF9qam7qm|4v0lh%JDU+laN|vH7mIRyh^BvrqnBNoqF4w6TY@1f z`l5`a)UO%73xMHG?>N^A3LbP}{>Xom`~%N`V|8-bwlwULGdt~VPpQe)(_O@&v|YXo zpgW5Uoy8+dYa)NM?p)wC6k|)icY*c9*yvJPbn#!x|Gk-(d>?&7q>r8(%Q?dWW;tFC zx1U~hI^?9$kIpMgB%-|Rj{R;DPn7PA%);nehnC5KU!I}`rf0&BHkpkO-G)Z+btuLg z0x*6;{l4rRhDx*9b+OW_x-E3w9)xpDUfI^ui+g|U$7+^UiofTdB83sZG-GII*g7Di zO&~nE75%2{csaXJn0czgx39{tGff=bE#xvzYb@Hv1tOc_zMFcc=2S7i|ESyf*_!Xk zuVC2~84O_vq=lMG{N6DlN30eE$nK4Q<#U`@7xbG_HV#RN~dt{sNgY&{EQ zX9{!5O3xB900zUL;@|^IhR3yh$(gfT-HYqYsaunY@*O7zB z?=~I3RBtK*3;3O-qmS~3QfA^v?CqnBQ;T}0+$Xraxe_wrU$8Nur7wNvk~g>1P;tZf za{Kn`5Z|NrmUzYzbM6hklMoSUQu3$VZa>I72W*|Afihd80aQZRC%oG*^mht1B>{N& zqD{MV_8tB-STe(st7<64BH3lHz8w!)k;|)MzoJzk7tHrm374&|LN)lo)OPhjuxq>e zacgkRk0$E=(Q*(=0?Xi9l&RDQfn~e()!||lmt*0F{M^>u%0DOX+$YA6c*7D1lOv*G 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 Nky5yqdFQG7{{c%u2zdYi diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 1dcf969c..8f5a6864 100644 --- a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx +++ b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx @@ -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/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_flutter/README.md b/packages/stream_chat_flutter/README.md index e79c4b42..811135a6 100644 --- a/packages/stream_chat_flutter/README.md +++ b/packages/stream_chat_flutter/README.md @@ -14,6 +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_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_persistence/README.md b/packages/stream_chat_persistence/README.md index 7f94eda1..0851335b 100644 --- a/packages/stream_chat_persistence/README.md +++ b/packages/stream_chat_persistence/README.md @@ -13,6 +13,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/) +- [Flutter Offline Docs](https://getstream.io/chat/docs/flutter-dart/flutter_offline/) This package provides a persistence client for fetching and saving chat data locally. Stream Chat Persistence uses [Moor](https://github.com/simolus3/moor) as a disk cache. From b52351084572eee69b5dceb4a4a88fabd81fa9e7 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 12:28:24 +0200 Subject: [PATCH 070/113] bump version and changelog --- packages/stream_chat/CHANGELOG.md | 2 +- packages/stream_chat/lib/version.dart | 2 +- packages/stream_chat/pubspec.yaml | 2 +- packages/stream_chat_flutter/CHANGELOG.md | 2 +- packages/stream_chat_flutter/pubspec.yaml | 4 ++-- packages/stream_chat_flutter_core/CHANGELOG.md | 2 +- packages/stream_chat_flutter_core/pubspec.yaml | 4 ++-- packages/stream_chat_persistence/CHANGELOG.md | 2 +- packages/stream_chat_persistence/pubspec.yaml | 4 ++-- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index f03b637a..84f12d05 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming +## 2.1.0 🛑️ Removed diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index d09a2ccc..aba200a5 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.0'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 1f145992..78f5380b 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.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 1612bc4a..487af25e 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming +## 2.1.0 ✅ Added diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 391ddfdd..55960661 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.0.0 +version: 2.1.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -37,7 +37,7 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 shimmer: ^2.0.0 - stream_chat_flutter_core: ^2.0.0 + stream_chat_flutter_core: ^2.1.0 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 7d990182..770f6678 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming +## 2.1.0 🛑️ Breaking Changes from `2.0.0` - Changed default message filter of `MessageListCore` diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index cc59f9fe..8a83f4b8 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.0 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.0 dev_dependencies: fake_async: ^1.2.0 diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index 466d1e05..459b7127 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming +## 2.1.0 ✅ Added - Added support for `Message.i18n` diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 08029e91..f3ea112a 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_persistence homepage: https://github.com/GetStream/stream-chat-flutter description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. -version: 2.0.0 +version: 2.1.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -18,7 +18,7 @@ dependencies: path: ^1.8.0 path_provider: ^2.0.1 sqlite3_flutter_libs: ^0.5.0 - stream_chat: ^2.0.0 + stream_chat: ^2.1.0 dev_dependencies: build_runner: ^2.0.1 From fcce46cf166813ac63d2c3400fb490a2a56a3c94 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 12:33:21 +0200 Subject: [PATCH 071/113] fix pubspecs --- packages/stream_chat_flutter/pubspec.yaml | 1 + packages/stream_chat_localizations/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 55960661..f651573f 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -60,4 +60,5 @@ dev_dependencies: golden_toolkit: ^0.9.0 mocktail: ^0.1.2 pedantic: ^1.11.0 + path: ^1.8.0 diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index 4ef1ce69..01b027a5 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - stream_chat_flutter: ^2.0.0 + stream_chat_flutter: ^2.1.0 dev_dependencies: flutter_test: From 93ee4260f80152f9aed1c4dbb292a578cbe3e931 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 12:44:10 +0200 Subject: [PATCH 072/113] chore(ui): fix analysis --- packages/stream_chat_flutter/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index f651573f..6a1588ef 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -59,6 +59,6 @@ dev_dependencies: sdk: flutter golden_toolkit: ^0.9.0 mocktail: ^0.1.2 - pedantic: ^1.11.0 path: ^1.8.0 + pedantic: ^1.11.0 From 953d330bbe0452c0d73ae05f15630e16fc71216d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 28 Jul 2021 16:54:39 +0530 Subject: [PATCH 073/113] feat: Added guide --- .../guides/customize_text_messages.mdx | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 docusaurus/docs/Flutter/guides/customize_text_messages.mdx 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..b6216eaf --- /dev/null +++ b/docusaurus/docs/Flutter/guides/customize_text_messages.mdx @@ -0,0 +1,100 @@ +--- +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 text messages in the `MessageListView` 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. + +``` +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. +::: + +``` +MessageListView( + ... + messageBuilder: (context, messageDetails, messageList, defaultWidget) { + return defaultWidget; + }, +) +``` + +We use `.copyWith()` to customize the widget: + +``` +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. + +``` +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 + +``` +MessageListView( + ... + messageBuilder: (context, messageDetails, messageList, defaultWidget) { + return defaultWidget.copyWith( + textBuilder: (context, message) { + return Text(message.text); + }, + ); + }, +) +``` \ No newline at end of file From 800baec9323c5679a53077739ac03c046ce32038 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 17:20:14 +0200 Subject: [PATCH 074/113] docs(localization): add localization docs --- .../docs/Flutter/basics/introduction.mdx | 1 + .../guides/adding_custom_attachments.mdx | 2 +- .../Flutter/guides/adding_localization.mdx | 97 +++++++++++++++++++ .../Flutter/guides/migration_guide_2_0.mdx | 2 +- .../test/src/core/models/user_test.dart | 2 - packages/stream_chat_localizations/README.md | 9 +- 6 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 docusaurus/docs/Flutter/guides/adding_localization.mdx 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..678a7d4d --- /dev/null +++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx @@ -0,0 +1,97 @@ +--- +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). + +### Supported languages + +At the moment we support the following languages: +- [English](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsEn-class.html) +- [Hindi](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsHi-class.html) +- [Italian](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsIt-class.html) +- [French](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsFr-class.html) + +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 [![Pub](https://img.shields.io/pub/v/stream_chat_localizations.svg)](https://pub.dartlang.org/packages/stream_chat_localizations) +```yaml +dependencies: + stream_chat_localizations: ^latest_version +``` + +You should 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. + +Checkout [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 exisiting 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. + +Checkout [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#specifying-supportedlocales). + +Example: + +```xml +CFBundleLocalizations + + en + nb + fr + it + +``` diff --git a/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx b/docusaurus/docs/Flutter/guides/migration_guide_2_0.mdx index 8f5a6864..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) --- diff --git a/packages/stream_chat/test/src/core/models/user_test.dart b/packages/stream_chat/test/src/core/models/user_test.dart index 5319c02b..ce488cab 100644 --- a/packages/stream_chat/test/src/core/models/user_test.dart +++ b/packages/stream_chat/test/src/core/models/user_test.dart @@ -29,7 +29,6 @@ void main() { expect(newUser.id, user.id); expect(newUser.role, user.role); expect(newUser.name, user.name); - expect(newUser.language, user.language); newUser = user.copyWith( id: 'test', @@ -42,7 +41,6 @@ void main() { expect(newUser.id, 'test'); expect(newUser.role, 'test'); expect(newUser.name, 'test'); - expect(newUser.language, 'en'); }); }); } diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md index 49fd4442..15f7cabc 100644 --- a/packages/stream_chat_localizations/README.md +++ b/packages/stream_chat_localizations/README.md @@ -13,6 +13,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/) +- [UI Docs](https://getstream.io/chat/docs/sdk/flutter/stream_chat_flutter/introduction/) This package provides localized strings for the stream chat widgets for many languages. @@ -23,10 +24,10 @@ Check out the [changelog on pub.dev](https://pub.dev/packages/stream_chat_locali ## Supported languages At the moment we support the following languages: -- [English](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsEn-class.html) -- [Hindi](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsHi-class.html) -- [Italian](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsIt-class.html) -- [French](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsFr-class.html) +- [English](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsEn-class.html) +- [Hindi](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsHi-class.html) +- [Italian](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsIt-class.html) +- [French](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsFr-class.html) 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. From 18d49dd5711f5847b2aecd8394fef733f3abea02 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 17:40:16 +0200 Subject: [PATCH 075/113] Update docusaurus/docs/Flutter/guides/adding_localization.mdx Co-authored-by: Gordon Hayes --- docusaurus/docs/Flutter/guides/adding_localization.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/adding_localization.mdx b/docusaurus/docs/Flutter/guides/adding_localization.mdx index 678a7d4d..1ae2efc0 100644 --- a/docusaurus/docs/Flutter/guides/adding_localization.mdx +++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx @@ -75,7 +75,7 @@ Checkout [this example](https://github.com/GetStream/stream-chat-flutter/blob/ma ### Override exisiting 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. +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. Checkout [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. From 00e3f58258b36ed436937cde7f668ddcb6573f3f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 17:40:44 +0200 Subject: [PATCH 076/113] Update docusaurus/docs/Flutter/guides/adding_localization.mdx Co-authored-by: Gordon Hayes --- docusaurus/docs/Flutter/guides/adding_localization.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/adding_localization.mdx b/docusaurus/docs/Flutter/guides/adding_localization.mdx index 1ae2efc0..68e37464 100644 --- a/docusaurus/docs/Flutter/guides/adding_localization.mdx +++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx @@ -69,7 +69,7 @@ class MyApp extends StatelessWidget { ### 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. +To add a new language, create a new class extending `GlobalStreamChatLocalizations` and create a delegate for it, adding it to the `delegates` array. Checkout [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. From 236a32403ec0a0215344d05b92b70498dbfde64e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 28 Jul 2021 21:18:43 +0530 Subject: [PATCH 077/113] fix(localizations): minor fixes Signed-off-by: xsahil03x --- packages/stream_chat_localizations/CHANGELOG.md | 4 ++++ .../lib/src/stream_chat_localizations.dart | 4 +--- .../lib/stream_chat_localizations.dart | 2 +- packages/stream_chat_localizations/pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md index 5ca6d89e..772ae6d0 100644 --- a/packages/stream_chat_localizations/CHANGELOG.md +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.1 + +* Minor Fixes + ## 1.0.0 * First release diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart index bb2d791f..f98932b5 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -1,9 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:jiffy/jiffy.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart' - show StreamChatLocalizations, User; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; part 'stream_chat_localizations_en.dart'; diff --git a/packages/stream_chat_localizations/lib/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/stream_chat_localizations.dart index 0cfb60ba..27c01880 100644 --- a/packages/stream_chat_localizations/lib/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/stream_chat_localizations.dart @@ -1,5 +1,5 @@ /// Localizations for the StreamChat Flutter library. -library stream_chat_localization; +library stream_chat_localizations; export 'package:flutter_localizations/flutter_localizations.dart' show diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index 01b027a5..445c06ae 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -1,6 +1,6 @@ name: stream_chat_localizations description: The Official localizations for Stream Chat Flutter, a service for building chat applications -version: 1.0.0 +version: 1.0.1 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 From 587fc473162a16c42953cabedd24ba171cb0a9ac Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 28 Jul 2021 17:49:27 +0200 Subject: [PATCH 078/113] fix links --- docusaurus/docs/Flutter/guides/adding_localization.mdx | 8 ++++---- packages/stream_chat_localizations/README.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/adding_localization.mdx b/docusaurus/docs/Flutter/guides/adding_localization.mdx index 68e37464..b48f355c 100644 --- a/docusaurus/docs/Flutter/guides/adding_localization.mdx +++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx @@ -13,10 +13,10 @@ We have a dedicated package for adding localization to our UI widgets. It's call ### Supported languages At the moment we support the following languages: -- [English](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsEn-class.html) -- [Hindi](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsHi-class.html) -- [Italian](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsIt-class.html) -- [French](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsFr-class.html) +- [English](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsEn-class.html) +- [Hindi](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsHi-class.html) +- [Italian](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsIt-class.html) +- [French](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsFr-class.html) 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. diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md index 15f7cabc..c96ec8af 100644 --- a/packages/stream_chat_localizations/README.md +++ b/packages/stream_chat_localizations/README.md @@ -24,10 +24,10 @@ Check out the [changelog on pub.dev](https://pub.dev/packages/stream_chat_locali ## Supported languages At the moment we support the following languages: -- [English](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsEn-class.html) -- [Hindi](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsHi-class.html) -- [Italian](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsIt-class.html) -- [French](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localization/StreamChatLocalizationsFr-class.html) +- [English](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsEn-class.html) +- [Hindi](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsHi-class.html) +- [Italian](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsIt-class.html) +- [French](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsFr-class.html) 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. From 3c48e70108b90d8ca7bb12978ec8afa5726ae4bc Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Jul 2021 11:14:21 +0200 Subject: [PATCH 079/113] fix(ui): jiffy use app level locale --- packages/stream_chat_flutter/lib/src/stream_chat.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 6eae0fcc..b3654a31 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -148,8 +148,8 @@ class StreamChatState extends State { @override void didChangeDependencies() { - final locale = ui.window.locale; - final languageCode = locale.languageCode; + final currentLocale = Localizations.localeOf(context); + final languageCode = currentLocale.languageCode; final availableLocales = Jiffy.getAllAvailableLocales(); if (availableLocales.contains(languageCode)) { Jiffy.locale(languageCode); From 993cd4ad6ce14c5b27782ce57ae2a1b228645d56 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Jul 2021 11:16:30 +0200 Subject: [PATCH 080/113] docs(localization): fix translation links --- docusaurus/docs/Flutter/guides/adding_localization.mdx | 8 ++++---- packages/stream_chat_localizations/README.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/adding_localization.mdx b/docusaurus/docs/Flutter/guides/adding_localization.mdx index b48f355c..fb537e2e 100644 --- a/docusaurus/docs/Flutter/guides/adding_localization.mdx +++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx @@ -13,10 +13,10 @@ We have a dedicated package for adding localization to our UI widgets. It's call ### Supported languages At the moment we support the following languages: -- [English](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsEn-class.html) -- [Hindi](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsHi-class.html) -- [Italian](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsIt-class.html) -- [French](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsFr-class.html) +- [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. diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md index c96ec8af..ebf16a74 100644 --- a/packages/stream_chat_localizations/README.md +++ b/packages/stream_chat_localizations/README.md @@ -24,10 +24,10 @@ Check out the [changelog on pub.dev](https://pub.dev/packages/stream_chat_locali ## Supported languages At the moment we support the following languages: -- [English](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsEn-class.html) -- [Hindi](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsHi-class.html) -- [Italian](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsIt-class.html) -- [French](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsFr-class.html) +- [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. From 87dcaa3bf0b3d863e77793fe707e7897e2852a32 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Jul 2021 11:22:27 +0200 Subject: [PATCH 081/113] docs(localization): add ` Changing the default language` section --- .../Flutter/guides/adding_localization.mdx | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docusaurus/docs/Flutter/guides/adding_localization.mdx b/docusaurus/docs/Flutter/guides/adding_localization.mdx index fb537e2e..e8f0dfa5 100644 --- a/docusaurus/docs/Flutter/guides/adding_localization.mdx +++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx @@ -32,6 +32,8 @@ You should then run `flutter packages get` ### Usage +Flutter generally and the Stream Chat SDK will use the system locale of the users device, if that locale is supported (see below), if the locale is not supported we will default to `en` + ```dart import 'package:flutter/material.dart'; import 'package:stream_chat_localizations/stream_chat_localizations.dart'; @@ -79,6 +81,43 @@ To override an existing language, create a new class extending that particular l Checkout [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'), + ], + localeListResolutionCallback: (locales, supportedLocales) { + final supportedLanguageCodes = + supportedLocales.map((e) => e.languageCode); + if (locales != null) { + for (final locale in locales) { + if (supportedLanguageCodes.contains(locale.languageCode)) { + return locale; + } + } + } + + 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 From 48d1eecf8c9b4a268c837bf86f7711fc20321506 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Jul 2021 11:28:22 +0200 Subject: [PATCH 082/113] docs(localization): add flutter docs link --- docusaurus/docs/Flutter/guides/adding_localization.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/adding_localization.mdx b/docusaurus/docs/Flutter/guides/adding_localization.mdx index e8f0dfa5..abbd9e70 100644 --- a/docusaurus/docs/Flutter/guides/adding_localization.mdx +++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx @@ -32,7 +32,8 @@ You should then run `flutter packages get` ### Usage -Flutter generally and the Stream Chat SDK will use the system locale of the users device, if that locale is supported (see below), if the locale is not supported we will default to `en` +Flutter generally and the Stream Chat SDK will use the system locale of the users device, if that locale is supported (see below), if the locale is not supported we will default to `en`. +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'; From fc5e3add5de42751ae7a898ae1d04c0b59e5a366 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Jul 2021 11:39:15 +0200 Subject: [PATCH 083/113] fix analysis --- packages/stream_chat_flutter/lib/src/stream_chat.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index b3654a31..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'; From 4985cb1904b23c5ba2f1ead0f707a54e4a574ddc Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 29 Jul 2021 15:13:16 +0530 Subject: [PATCH 084/113] added hashtag example --- .../docs/Flutter/assets/hashtag_example.jpg | Bin 0 -> 10727 bytes .../guides/customize_text_messages.mdx | 60 +++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 docusaurus/docs/Flutter/assets/hashtag_example.jpg diff --git a/docusaurus/docs/Flutter/assets/hashtag_example.jpg b/docusaurus/docs/Flutter/assets/hashtag_example.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a2bb3ba4c9591e4623696643b2d0a3a88ab3db32 GIT binary patch literal 10727 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 Date: Thu, 29 Jul 2021 15:14:13 +0530 Subject: [PATCH 085/113] added hashtag example --- docusaurus/docs/Flutter/guides/customize_text_messages.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/customize_text_messages.mdx b/docusaurus/docs/Flutter/guides/customize_text_messages.mdx index ea528649..f0fb1ac0 100644 --- a/docusaurus/docs/Flutter/guides/customize_text_messages.mdx +++ b/docusaurus/docs/Flutter/guides/customize_text_messages.mdx @@ -11,8 +11,8 @@ Customizing Text Messages 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 text messages in the `MessageListView` in the Stream Chat Flutter -UI SDK. +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` From 76074b4774d93d8ac2e34352b74b0b09e38f3e4b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Jul 2021 11:47:25 +0200 Subject: [PATCH 086/113] fix code snippet and add comments --- .../docs/Flutter/guides/adding_localization.mdx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/adding_localization.mdx b/docusaurus/docs/Flutter/guides/adding_localization.mdx index abbd9e70..5645e811 100644 --- a/docusaurus/docs/Flutter/guides/adding_localization.mdx +++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx @@ -98,17 +98,23 @@ Here is an example of how that would look like: 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) { - for (final locale in locales) { - if (supportedLanguageCodes.contains(locale.languageCode)) { - return locale; + // 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 @@ -117,7 +123,7 @@ Here is an example of how that would look like: ``` -In this case we're using Italian as the default language. +In this case we're using italian as the default language. ### ⚠️ Note on **iOS** From 66cc8d3ef8cf0649536045f061421001fa53ab61 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Thu, 29 Jul 2021 11:58:22 +0200 Subject: [PATCH 087/113] docs: fix ios locales link --- packages/stream_chat_localizations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md index ebf16a74..59cf6810 100644 --- a/packages/stream_chat_localizations/README.md +++ b/packages/stream_chat_localizations/README.md @@ -93,7 +93,7 @@ Checkout [this example](https://github.com/GetStream/stream-chat-flutter/blob/ma ### ⚠️ 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). +`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: From ef12e1dc11cb137db34042b936cfb8c9d954709e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Jul 2021 13:18:23 +0200 Subject: [PATCH 088/113] readd event.me listener --- packages/stream_chat/lib/src/client/client.dart | 15 +++++++++++++++ .../stream_chat_flutter/lib/src/stream_chat.dart | 1 - 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index b25ebe88..8507423b 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1334,6 +1334,21 @@ class ClientState { /// Creates a new instance listening to events and updating the state ClientState(this._client) { _subscriptions.addAll([ + _client + .on() + .where((event) => + event.me != null && event.type != EventType.healthCheck) + .map((e) => e.me!) + .listen((user) { + currentUser = user; + final totalUnreadCount = user.totalUnreadCount; + _totalUnreadCountController.add(totalUnreadCount); + + final unreadChannels = user.unreadChannels; + if (unreadChannels != null) { + _unreadChannelsController.add(unreadChannels); + } + }), _client .on() .map((event) => event.unreadChannels) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index b3654a31..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'; From cf675269f0ed3856fd9349b147b6c31f0b179708 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Jul 2021 13:23:42 +0200 Subject: [PATCH 089/113] merge event user to current user --- packages/stream_chat/lib/src/client/client.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 8507423b..104b23fc 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1340,7 +1340,7 @@ class ClientState { event.me != null && event.type != EventType.healthCheck) .map((e) => e.me!) .listen((user) { - currentUser = user; + currentUser = currentUser?.merge(user) ?? user; final totalUnreadCount = user.totalUnreadCount; _totalUnreadCountController.add(totalUnreadCount); From c46397b63a40611c14c3eb078a52af30dbc7cb66 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Jul 2021 13:24:13 +0200 Subject: [PATCH 090/113] bump version and changelog --- packages/stream_chat/CHANGELOG.md | 6 ++++++ packages/stream_chat/lib/version.dart | 2 +- packages/stream_chat/pubspec.yaml | 2 +- packages/stream_chat_flutter/CHANGELOG.md | 4 ++++ packages/stream_chat_flutter/pubspec.yaml | 4 ++-- packages/stream_chat_flutter_core/CHANGELOG.md | 4 ++++ packages/stream_chat_flutter_core/pubspec.yaml | 4 ++-- packages/stream_chat_persistence/CHANGELOG.md | 4 ++++ packages/stream_chat_persistence/pubspec.yaml | 4 ++-- 9 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 84f12d05..9bfe494f 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.1.1 + +🐞 Fixed + +- Mutes were not working correctly in 2.1.0 + ## 2.1.0 🛑️ Removed diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index aba200a5..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.1.0'; +const PACKAGE_VERSION = '2.1.1'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 78f5380b..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.1.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_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 487af25e..68a74fae 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,5 +1,9 @@ ## 2.1.0 +- Updated core dependency + +## 2.1.0 + ✅ Added - Added `MessageListView.paginationLimit` diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 6a1588ef..f47fa9f5 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.1.0 +version: 2.1.1 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -37,7 +37,7 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 shimmer: ^2.0.0 - stream_chat_flutter_core: ^2.1.0 + stream_chat_flutter_core: ^2.1.1 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 770f6678..c34bf047 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,5 +1,9 @@ ## 2.1.0 +- Updated llc dependency + +## 2.1.0 + 🛑️ Breaking Changes from `2.0.0` - Changed default message filter of `MessageListCore` diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 8a83f4b8..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.1.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.1.0 + stream_chat: ^2.1.1 dev_dependencies: fake_async: ^1.2.0 diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index 459b7127..c348002e 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.1 + +- Updated llc dependency + ## 2.1.0 ✅ Added diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index f3ea112a..744005d8 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_persistence homepage: https://github.com/GetStream/stream-chat-flutter description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. -version: 2.1.0 +version: 2.1.1 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -18,7 +18,7 @@ dependencies: path: ^1.8.0 path_provider: ^2.0.1 sqlite3_flutter_libs: ^0.5.0 - stream_chat: ^2.1.0 + stream_chat: ^2.1.1 dev_dependencies: build_runner: ^2.0.1 From 4e611b35c3995b42688d04dad71fce74574b535d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Jul 2021 13:30:17 +0200 Subject: [PATCH 091/113] bump version and changelog of localization --- packages/stream_chat_localizations/CHANGELOG.md | 4 ++++ packages/stream_chat_localizations/pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md index 772ae6d0..4667ab93 100644 --- a/packages/stream_chat_localizations/CHANGELOG.md +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.2 + +* Updated stream_chat_flutter dependency + ## 1.0.1 * Minor Fixes diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index 445c06ae..4d982a60 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -1,6 +1,6 @@ name: stream_chat_localizations description: The Official localizations for Stream Chat Flutter, a service for building chat applications -version: 1.0.1 +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 @@ -14,7 +14,7 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - stream_chat_flutter: ^2.1.0 + stream_chat_flutter: ^2.1.1 dev_dependencies: flutter_test: From 127f83457b1d50637d9d20657b9c40da7ea1b03b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Jul 2021 14:20:07 +0200 Subject: [PATCH 092/113] fix typo --- packages/stream_chat_flutter_core/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index c34bf047..ef24e401 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.1.0 +## 2.1.1 - Updated llc dependency From f893d906aae24c2b7e9a2a1f7c7e060e7a0f17c0 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 29 Jul 2021 14:21:01 +0200 Subject: [PATCH 093/113] fix typo --- packages/stream_chat_flutter/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 68a74fae..de55a3ca 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.1.0 +## 2.1.1 - Updated core dependency From 8f7163b3a07b7bd36fc67760a9d0801911f79d40 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Thu, 29 Jul 2021 14:50:36 +0200 Subject: [PATCH 094/113] fix: ios build config --- .../example/ios/.gitignore | 1 + .../ios/Flutter/AppFrameworkInfo.plist | 26 ++++ .../example/ios/Flutter/Debug.xcconfig | 2 + .../example/ios/Flutter/Release.xcconfig | 2 + .../ios/Runner.xcodeproj/project.pbxproj | 114 +++++++----------- .../contents.xcworkspacedata | 2 +- .../example/ios/Runner/Info.plist | 7 ++ 7 files changed, 84 insertions(+), 70 deletions(-) create mode 100644 packages/stream_chat_localizations/example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 packages/stream_chat_localizations/example/ios/Flutter/Debug.xcconfig create mode 100644 packages/stream_chat_localizations/example/ios/Flutter/Release.xcconfig diff --git a/packages/stream_chat_localizations/example/ios/.gitignore b/packages/stream_chat_localizations/example/ios/.gitignore index e96ef602..151026b9 100644 --- a/packages/stream_chat_localizations/example/ios/.gitignore +++ b/packages/stream_chat_localizations/example/ios/.gitignore @@ -18,6 +18,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ 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 index 261aa5a8..3ea4f826 100644 --- a/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/stream_chat_localizations/example/ios/Runner.xcodeproj/project.pbxproj @@ -7,9 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - 0E9B23A7BA08E142FA5000CF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D76F8024ABE1070895D659BA /* Pods_Runner.framework */; }; 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 */; }; @@ -32,12 +32,14 @@ /* 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 = ""; }; - 3F6A054EEDAF06BCF649C130 /* 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 = ""; }; - 6FE1ECA061EBB001F6BCE8B7 /* 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 = ""; }; + 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; }; @@ -45,8 +47,6 @@ 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 = ""; }; - D76F8024ABE1070895D659BA /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EC2A45E9198C1011BED23834 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,32 +54,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 0E9B23A7BA08E142FA5000CF /* Pods_Runner.framework in Frameworks */, + 710C8D61445A55538E32FA76 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 04AAB960E493BD92262BBF82 /* Frameworks */ = { - isa = PBXGroup; - children = ( - D76F8024ABE1070895D659BA /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 8559384DCD98ED6067CEF8CB /* Pods */ = { - isa = PBXGroup; - children = ( - 3F6A054EEDAF06BCF649C130 /* Pods-Runner.debug.xcconfig */, - EC2A45E9198C1011BED23834 /* Pods-Runner.release.xcconfig */, - 6FE1ECA061EBB001F6BCE8B7 /* Pods-Runner.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -97,8 +78,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 8559384DCD98ED6067CEF8CB /* Pods */, - 04AAB960E493BD92262BBF82 /* Frameworks */, + D27696257385EFA4B6A3AECC /* Pods */, + A92D4F040E5D68A2DFDFF35C /* Frameworks */, ); sourceTree = ""; }; @@ -125,6 +106,25 @@ 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 */ @@ -132,14 +132,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - C1EE41B94EADE099F7AF3A1C /* [CP] Check Pods Manifest.lock */, + BC7B06B6092566B153F8E77E /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - BD38DACC9A0AD429D9ED9939 /* [CP] Embed Pods Frameworks */, + 91A9FD3C359F260AE72CC421 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -212,21 +212,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 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"; - }; - BD38DACC9A0AD429D9ED9939 /* [CP] Embed Pods Frameworks */ = { + 91A9FD3C359F260AE72CC421 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -243,7 +229,21 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - C1EE41B94EADE099F7AF3A1C /* [CP] Check Pods Manifest.lock */ = { + 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 = ( @@ -357,16 +357,8 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -489,16 +481,8 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -516,16 +500,8 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 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 index 1d526a16..919434a6 100644 --- 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 @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/stream_chat_localizations/example/ios/Runner/Info.plist b/packages/stream_chat_localizations/example/ios/Runner/Info.plist index a060db61..c9bfb029 100644 --- a/packages/stream_chat_localizations/example/ios/Runner/Info.plist +++ b/packages/stream_chat_localizations/example/ios/Runner/Info.plist @@ -2,6 +2,13 @@ + CFBundleLocalizations + + en + it + fr + hi + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable From 2e1bd3d2bef79f9ebc6eca40366ad44368398f0d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 29 Jul 2021 18:22:55 +0530 Subject: [PATCH 095/113] added new guide --- .../docs/Flutter/assets/live_stream_1.jpg | Bin 0 -> 26070 bytes .../docs/Flutter/assets/live_stream_2.jpg | Bin 0 -> 30178 bytes .../adding_chat_to_video_livestreams.mdx | 76 ++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 docusaurus/docs/Flutter/assets/live_stream_1.jpg create mode 100644 docusaurus/docs/Flutter/assets/live_stream_2.jpg create mode 100644 docusaurus/docs/Flutter/guides/adding_chat_to_video_livestreams.mdx diff --git a/docusaurus/docs/Flutter/assets/live_stream_1.jpg b/docusaurus/docs/Flutter/assets/live_stream_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bef68c6edb98f28f3b789a8ea9a15a3aa853181c GIT binary patch literal 26070 zcmbrl1yChjlPGv`_r~4f;%-gj?(XjH-Zb90OXIE=cc*c8cXxMapyBcT|HSO<+nsr_ zv3aVpQcg)`MO0>-%=%dV*ao0Vi%W?Ez`(!&lAipk3c>=y3I=Lw2Ay=(F;I> z1=j?_ga9K0fTMvypn-k#0|)^CFsRQb1q1vifI~n+L4(1-!hNg)VE?VKYS*!A?4{!Y zHXdWvotzb0<|)dAO*F25H{tfiKcjW9VaECqV3cKSq1jk|k8kaTnP*~yN9(yKFRiQ&)ivFyta9~v%qSpx`!BwLpoGV$Ul}FyBpRK$$+fN4X6ovbKJP{f1r4A%7PQnF zNadwHUwMWG_>B}Yxsd3_4oXW((TGjqfipYy?=et#YhI&eWhE;UwL{BC92gzD?&dSI zLvFPdV_geG)fy?H){MUolYTv9S9VPr(!08@%3gXGG|NlPr_2?aZu}n6kWsr|NgE@m zXs#$4t;lO|#=q!P(G0iM8_TeP&XM4yZ}93VPA-~Q{qvqNR%I-mkvwlp&yZTeCsiJA zpe1DB%gBJ#xw<}lZo)zMJ9W;fIDOFnQS82gnsZfeZA;b4GzSmU*iLiRbaF=|O z;_dz4hj<8omd}|gw=aC$Y6@2OPO@ufPc5X&Q-=t%6d!(yx32ysiHRhH=Sm^sBNsDB ze}OwSV6Izcgm#%BJxB9?)4h*bv7>aS$RE^N<@>re==j^70HZE zulZ5Vlxd-69GO-cj_hYuTkl|^cd~k9khWYouDw=j?`HGm&)Gx)2=p^DhW#vlS5!SW zfM`@ku?RgWtP<3kKHQcke@mY?vwv3>3GN%2Wn@>@|G;d04qPiAJaMoybE%@W7faw$ zS=ul*&65x*;{RS^{>F5&=GBJZ#emCkQ6!pqiXkUq!fSdubjUk5GSSi@QUijOi_CVYJ$5l_E%7dERuW45)2DI|~TBm+@NHpnYp>`st(Q6;k*F-nR@vjiw zeCNeuDT4#XpDUn+5|M+=CXDO*p8uSR=Q&3!Zyhzd!+V!6!?49R*Sb3jJ8JW)WXmaa zaXAM{9VhPaBo}7G?<@TQeoyxH#bMIs^PuooDS8#`no4C(h>UP`1zCNlOweO50DBiuj`c>qsql#Sa*^~ z2VbvDj>^5_ewFwwj;~vG&7A_Nt~4}>ay0wy!{*&R2>@nf)5XV*IDv$NkyrPMZ_z|e zd49I_WXl5pAg1)fPJa1TraEUNG^5YCyI7P*8;hQ3_vjA*yIGp#ifEDe5sO5j?PY}!yA(XWd1OT@G;XFbG0pJP2 zN1p#;By0fyV`px`P$;G4Ym-Tjr5bc5h2#mNfv?7_Zj{ARX z_pk&2lr4hG*0q(>6tGe*s)QBci9Uc#wQM#uW%*0uh2D_lixwEFzY<<7?B zLVLKgF|<)HJ6>R7GPz}6r+cDU#Ku|A$;qT5A2&KW>7!BuT932W9~@siw70jf@5;l8 zQ9UCsnY%xly3^t0`Qr;V2lDbL(D^2~nZf4Ck=gxU%>M64fln{t{o6_Z)lFcZc?I;p z-30NEo8B(GTbU%j-euK)E7@=L_!V2gR#Bemx-{+SN1QZdpf^9nc*U{sQk{Ol>uA$l zkWH_zby4O1FfwaWv41u)Dsw!j+q%N*n)y^=H+kuLWWm^RVtq8XmO^{4z$_WeWZB3R zn%$E^s;V>=7`mgp^Ev{iMkUH^c#=GXS=>zSY0W&xSz4Bp%t8nH&$0aG4Wqcnr~ ztEY57x{I2E++tP~JXbMr*V%9r=k16$M4M}0^$cj!0z|&7onZ?Y-rQ3s#@ArPqta+_ zz{r+(<$~4O+1aH&rQ7RuouUO} zjMz?ycn1~%mbzGl;<;xffIrF07pRsnxw^?5?s7ezIBZ7X(5JH=Cl=6j>0G_e0YbI1 zHHDMNc}KVU?`=#pc1bqLhT`aph>Ju<{Vj1R1AREj|q-7Ufe; zT_f2`ENX@<8dH4%hcYvv*N0C#u|==HRirdQ#5d@LUf%fkHzkz|g>ugm{PRIEGZIemGBw{;uM$Jo#O>DHIo#RHLLE+p40P9BUGI6 zfz(zOX#r>*LOzY9Dwz_H6*j$G&dQn}fZ;yV1%?XG%`{aXe9zy*LPJ2HhCBsWf<5P= zGKG=ntfa_qTpKzE^(GZT-6cY2t%!vW09Imx$aRPx_cQf?^is;z)@hU~e8l@s4Z;My z^G=G+FLD{TuI^LU+7W#|9I~s}&P?Ni2s*RH%KfaHYT2MhT{+kO3eF}B z8W+A*t@z-?XzJD3!9%LZU>l1>G; zv#-j!vdJq1nDuSl53Y(8dn65%|&ysFzz+6H1x3wOhK!FsdKvN@iBH8+El3Etl+HG$7tN zFK&+5_+UR_o-4bHbv&R7MVD1B&yP2~Z?Yc8O#DFg&PW$hG!Q?F-yKz%Z9uRAVA8_OVitDCz`L zt}^!EQBLroySc3$Io=#vrW}tvpQ4Z5po*q3Sb-|Avtp`{xSzLvNv^AEG!EZ&&Zk*6 zFH9`v(_ZgLU}h7+N4@|dUylr4YT?;%BvctnS$5p)a*u5e|74qOQCSWj&a-@fppZ~A znI&Fsx)swR|lE@-s}8J_?}9TI(EU1>}y+O}IMf?u!ur zVIN#~k1Q6bMhb>5w0*IX0w|AUT14oeXkw$S3=u?;@0SqAoKMV`=C`N`5Nx#N8d%*gj4W_L2ky`=VxQ} zS(b2Y=AvQzv@uf~J`OsM>x*yDXsUfN361y2SlZc*Gp#Hn3sx>&rKzGMXP-S_HJ4{q zM#9%r!ILmIQ~DXP$hlBF?rfvXscOt63d4MMTB?;7herps(XP5U0hyj^ZzkvwGs=_b ziB$R0s%YF271KbVi;fu1KrT}0_AtusVEiSN!5Ra>CV)DB7gO?eS!;EjJuMqw;3g<4 z+il!W!LvZ`NGr)okpf<)%wgj%g4Oqpg6vVrJO(}yTU`!bXIZ|r3NR;f{$Gy0$Q-HQ z9m?V|xb#aV74E|t=yLt*XkjG{o#F3$Ito<_*5=WX3sZD-?vkc4vd})?4`hefsJyQUfDMLWb8_2sHab%K|x z!!wJm`7V=8Ohz}^N~O-}s~pW@!yDA@IYM%tJfJ{4g^4k-*BDwy3=TFHj?Xc-t7?V? z<@TIfFPz=(s40)lfgbc=O`;!w1n!nalDeB4I~m*fXxSRfOVXx}bYI+-6*Aly&wUss z2MQqH^MJwg>?=1Z6Ii#^1*1ZlAv(^3Tb=BaJ+FZ4@$k>&lE-|mVIv3ZMo-Dn*%d*c zr?FDp8A6WYex4|7Gs}jqd%2aFW)y}V)3S!}xfWI% zZgl$n$|iV!7$k-}hWHGp`aobf;(pK0h>oY)$O-N&!S3n1^K%IoADU7<@F2mIf0Zvp z)^#^+Q~H$iz><~8cupewW;_Z`Elbxtkttm@>9sUooMv6JF!0w*ju%<@fRqhFbn=D! zpuH?(57br|*55A)8}sSFm-J|Og5Wqh`PiK~AHkK)t<>xr*MVeHc{G0t0ae23|{LcGIO-ja5QwrP`{x<%L zg)df~+ewQhCMjCHm38zHtwpdOfVjP1wvk~~8qiU*z}+ZpXYm0B>aL%Plo+Q3IhB$n zvrA1FD7wN6b*15`=NqI=BE9&xIzEc>n0q)wa=1=PwA@g6&;d@D6-X8b5te-_;Dr&w z^CLvomVPximq5{EWKk}3T0S8%mdnL7_Ob}HdP>UXSJ$3m!giZxZ?*d<*c|&?C&iBT zG&?N>Bl_J1!R990^zryE;HHInkN#e}aI}0xZ3L3wW1ej8jGk}1L;BRjFOmpV+SN{3 z7m=1Sz=WB#W?WWzA;?9E-QV!BV$s_69EpW@UI{EchU3y|S#dRHrP6OvSLG$totMch zL4FOzdbj|kQ=;hz=(FT)4o{Qd6sW0a@LiZ9U3t_h%x!o+NR|RF z{egvJuTV=BsF$f>@V78`Hn=!X|NfhB(}Vg#P4O2KibVlX41;gBVgnb84wg$p3D(zUh`v0jG<$Tb81d^yha{A?Gq{?_EnT|Wyb33j0@|r9`CAwfL2{3#AkH6%_j%-J zmUz+c30w~5!d)+mNhU^@mTFlOr*$Bm&b~aY{jTO{QLZb zE^%uXW(SeAvCCCFG=9tHVvRx}b=2p`0K6eP6hb1YX0GO{L9E7l=SW_sjnUTci8_sL zM9J#}Gb`=C#9{8Qo2TrYVKvmN7EUVPbt06s(@&YJ(dTvc6ZP7H#iJ@(QgB)pm6S#X z`{k>>Of;}phJ{=hogwG!(%JZG7hqcfl$M$G%x`ebA*S8z2&EvQ5{HR_Q6xd75=-`>Y#WvmlNwkoT6qFtnq=#zLTgx9(m_2Vd`g&DX z`bTTV%>Xnd=hk{f5RGw2^M}hM^eZUVXjoptp-k62z}+^SY@d)%c=gE0jfNt&v{p}3 z0ryMA2EWMKph~-DbBkx1oa<%;bEO=;=VW}?G@!b7kvc|yiZZoV@p@M`N(Ofo5EvMg_Dpg1oN?Ra&-SL`xm|NifI!) zg~y8GbaQfb=KV)ln`t_;g0fs1_yKD=@H9t13Gbf2u#$$N3B&V(74YyjB0_*dqe`60 zH$o|FZ<5%Ql7sZMM*FRq^+o)nlZr||@%o(X1E8aBV=X|x_%Ls9{?%Z` z^8-*BH(lY^wZ)R@kx%E%3V({BgX+v+to55HD~Ug>OV>E$O<=udZM{gLkKl!JPgW|$ z^bd{gTxNowmce|57~X`f4ORQ1jrC^6KH7xfZSNm&)v7ArrZ063c6Dp|^c$cTG=jo8 zne=5pP4g$y0mC2lTMrid_?QdUXB=*;Z4thcjho3gViR^krQhg4^XBY~@Y$2E1dBg( zh*QLfr@;gYF!QQ1W?&wn%;OVZ&nTf)B&2y~AYP^}B4CI%MVqS0bTr}_%7>dC_I0d@ zA=aj!8R2i`Dv1N^fKNXgn_Clr1Zq{S)kWG%vv#F7?#cHVwx&d!^BS`k){LwROYELw zhBhaQjrV8PFLsT-tczV~HU_dy)x|vuW~h&J_Q=RhjUNE1zic}2n@gAkO+_xc42{*K z?lnsZ3?BfS^bf#@uG;u*FITxuYLRS}%~q}Om)$H&KT!P#Ag1u?k9ob;Uq}^+bQ{SD zj+_sGdP6FNcAS%}+;=>fMU%f>mLGuiOSj>*>uHuR zoM~^;FflIu*^Xz!A=%$9udw|~>ywp*@Qm2z z*x=@tQct;y_>(Sde*Ur_#O>mu<-Z$U$cma7z9!hnxUhMY54NPEH(FR5tE5kjr*GJ} zRpl?Kw+%+!>y;)NAP~Pc_%+`kn(;{0yIFU0^2R8sCnk2u{D>Iwb-#@1i|Sw`*8)L=523Y5w`n^Z`hTef;wS4}TulFQgS9iFD1yRy!2$ z1-hl?kl1E);Mo6WUGWZkV9ba4tdc-@Hb#);Ws*=$@62(KE!|N&qLIP_yd5If*Vwli zBS`i#NT{Z}RXebkj_vJ8_R>hKroDCRc@yxp`H?X4Ht1&H>NpC^g=4O?J6F)jFpH(6 z;0oIR(jFZp=*KE)^4(5(4SF?w1(&&Z;!71$g^%QXN_@e&K*TLD=AG%c1J1&pc8ASU zl=V%eK1carqvv2X-ohSUhf!&fI^pj~=aocE52xv7FW})LHwD z*DQEPQ30F(`anXiar1Qpn`EuC`xRaz6O*wB-p@;(e2bXDqkbKVMg(J_H=Dw~!>Y~aAH!cXm?=Gw8`1>yV5W*4>|naetGQ>hPKCOw93nl{p_+${&8SpR zm2ExngJpkE_E3@V*{dNNw^KUMmjbn~W%S`}dKf&}tD%#3(4+G;f`x+Ku71xCNIer& zondK{liyptN$5QU+5}Gj`~WPj%e$1m^EdWPR=&zz+Qk1zYl>?;7`HT)@o690C&W{0 zv1wHfS-30;@5pp2bL7no&Qq57y&n*5z3DUcB|}$#i+%}Bf|#7OhC~xtYG>C@x|-6}UKNsy&XPsy zPxVEPi-t$Jz0uS{#ruZDxOVDFy1e=%%jwsw++Wg7UCk|1a$aY~wLJR5%TqJxTiqE; z^LFT>upONl(1y)%4bY0~6Q3S`t03FuR=1a53tM1$*ujqt?^l+FL0u1~5@wDe475I9$NG&==sx2TeLY?4ACieIw$UL(-X(Y zh7_dqa7X`zE2hbTxuuCULFbx8OU%2L7wnhL`x^yMZGGbtXt`?gugz0vfA8NyFV?%= zmr-GRx+iVadN2btaVHcUz6>k9usk3p2V{N#zywHNQ!z^`#jNR+v?Jy}1K$6}RLil5 z;gasqww#J2V=e^wN_ut@Ps+GyFeuY~;q5F|QDoSEB}<>Oh>zklBSaa)j2JMk)vQpb z`yYv+4?u+N#Pf-^dUBD~|108tlyZslQ=s&n7R2g3w6Ic^K4^uogQde@iFW_RJpdMw za!b>^uGg0UBF^CuJJzz0>M524Xw|=!kW|a6DQs%3?<3D}?KI;*}r~ojCe+o6QkWdg1Q2*2; zK6^zVNYPn@l#HC>AyF~N0`f?hS%sAoYG(wU6MM*w19z_3L=?ZM+^{wJc@mPD_Jp zjyZ=OpJFJzJk*H-SmKZFzZKT4rtcv|%0CXwa}oA)BWNyF z**AC0sPOz*^o4k3w=Y6g+NU+?h;TMLOXqeUyS|>C;26smsE~iSlc^)$l}b$Y%X1j(3IQt5a=Xqf`Me$cerT&pBwTIAB<&AvwoZ zU=Yx!;aQ7I5B8`wYK0j}aFTb#PnPhk1_eASn8f6X0J&BM;T#g2ns!0r6uU<%SMcsQ zS4lbSM{P;5yC^1Dx$+qc8Ylw!x2mfs{nDk>Cg213(k4-Mv`urWqw%dAcYkd2w#MRB zHFy5xsl@_7vNoZrkHwOkm{`RDyFuL2#hc)#7M^N^F57B+E@B<9LS^qldUHvs1WBG$ zgSY7fNO{mTrCD_uEM22ui|h)J7pgTKwG}=`oXvvlrzC``JJ8)E+YM?u!9J}Z-|0)* z{f1|{=;01R1{!P*Zit$Y&@foG!jL)#aMV{=*ol>b0 zt_FE#lZKtph#jduO;dBO?tnR!r#PiOuoP^!e2%XwgDty%vBkcw2zgbF)gvkyhHwW3 zyii=LSQ%#~Qym`D*_c5Z*$CL>U!-qo6{dUU0&vJz@V)5N(e z7RTUv7*waSh94M1TbB_zbecZB(W^q)`y5gd@nuiSV@2~)tQwdpUK8rgU!;oyrB~2n zWd|#Fg`}NTr38~+dL;m=PFeelsqIt~=r6x7eSCzSP^1v-JC{0m4q4*f9uinM7+p3= zJ;IuOtFnA8E>t=c5MzxJVR&V}=lB&{vO&`{fPB}kHwW@urr}x*h;L&`-s3Ll*fxYw ztS^-sWsw9z4iWUUj1+u^wh9QcH&ByXc@<+O@i$)=*wD!8riRT}QNEQWfHy+R1*Q>M z66o}3c7TC>)9K{N=9KRz-y6|pbbx#0GD&q)6V2?4v5LNSKE5CO&3k+W;nbgb_w6KU z?k8(_T`|s)72$zo_f|=uO`82>|5FSKu$ZOE3=N@!6Bj-xHY!AzyMlq}hjcbsS-@c# zcg|s3$p_$ooo&FQL!Agd+-X&firtT;4bUz#POeCp+Y3Vn+Juz$F|?=cMK(Q5WYuVm%$XjM|IDrc3;HQRZLpfWD&IU z7V2OWqKz8>IV+GO+ZhmYsK{cuq|yC{kRbbkZIFiRYrW|#$ys{X4(%inbLP7l6n7&HFr;id z>!c$}n$gv#N}SU=XEUAwLR43w4?cWXt|R%Ibhb|QIO9SL`R;3ps^j!HP57vb+%K`= z5ja6yc?v7nY4KT1O~5Ipmg}jJIuau`PS^17f!S=4n6mxn;wcO>y}UKD9*(7XSaG4} zKnHJDT%a2cnn!|*PCa@c7d|~pZd!2vEa6Z&1z#RJuSFYM^nhw#=Y z*;j}g(GX?NFFT=HEkOU+I}4RmleY@@#4A z(o>cxu3ey!G2jG1FY<#9l=m9mmh0f-=Ssxj9#4nK){MxQb_b;$-??OIz9I~X#G!k@ z)!E5W6<4bxhaqBsMv0Ymq2OhH{eC$njH4!Vk@a+1 zWEkPa6c_2-ZL@IM<^h^QpU$d)8Ybnfk@#wb_EX2rhT7BAG13HEkgm%#&t*&OBzxP$ z!ID&LLCI%?p=~@MdV0=27BH6XfDeGP7xhw?-u@HB+tvz}c=}%rF{b711G$L|k=Pjx z?jJA+KnRrn1rj@v_6h`$1)}+mn*#EibgsN9wUO%~Cd=y2L6c)-}iG%i_}=3l~5d{k;I ze3wNfmewU2jGGLt$`GF^7Q6cwh9PuYlnFDT zmTx4tKf>mrIiNI3`&y(=(A}V#Ws47+$x&5+ToW4o@+xM*9{@$oOm1l_m?(Pn#@kzQ zutyPjtJXIyAcT8A1Rm6F9dz+yJX{lrK1B>rzUKpwEVyOWoZ#Uv*pP!cYfXITOdTV} zb2K2M@MVuLc~QwRojlOEcv31eneKoh7vB=0a5*!}bW{`xuD6VNj;FOX1TDWr#3HiIu#<_(E;BJ^lOhMNbZFYR1{E?laCITfLxXf z45(b8?RQe>udt9!!rgE^6OJC>spAAD1P>{OOqEYaAEG%#CyF!&(YC}ai@>hoR>EhU z)OTTqt(YF7ak~7J3MtVfM+q)|#t)G}rt0a7hhj(AFH3`-GxP#EBMY^{$uabm(Jt$9 zi0eX~mvIGC{c2+PJ2UWVudz*!!8{dVPYx<`NsjL~9iHIK)Vh&^gN-FUjz-pS!!xVlSW>7m%E#_q6fL9b=`-@^p1|}+g zPcbnLV!TQXzf(2QqCx#u*2UvCYTGYSR6hehvj~}&KB{7dB4}|;)RC0ic#oXsO^PDJ zkv+38!Zr6T?w(a*5xGujf#vc>SbOKf_OyA=`JO-}_Y}DlCcJBZ-xnM5I!ugM{7JsA zYW=Z{&x72!?v#{R8twX&U&&5o+*X3V_+-M?zE*ZOK!dMN-RV|b4|~^NzODTSg{WKG zE9PkH3$W0!uIk+?Z(_q3Bz}|6Ma6;yZj&vVV84I#S;v!mEP4ADePEGK^N__4w&)A< zjA6QXaKSN~e$-%Y!G0R-)3+n9^#D0ruL$lWNZpzVZ%=xI_=g&X{o!JUkRLpPh9rHT zd2-FIH>R|!L`?etT&MXwC}3d#+W2iXSLu{eHcRS0YlE4H^Cfpv&upfKcB@RRzDp(J zK~?($0IRCm)1rr7zm`yXvTC2Rn<>TT-K|G3b*RYY2C;~$V`et*onKJQ9P|rQk$GU% zyLw*OQ3LLFgHo``a?4O?7`np%4g;8Nsi%^IDW&WTGXO@5a4E?7ow>}3h8 z|ML(mcuGF~O$Kgv9`IJS60OE8Tm+wvv|Y|0>J?YcvFB{}RDyOdYk`UueC7a`J1J^e zcM9ubXvPw{~$6; zRGTMED6t5sAzgkOG7Eaka|QjT(F0bGV*jTgJ%>hZ>r)B5g?o_4z%V`Sl3Mtlc>0HW z;a0wUKTBhTw>rU1>0j7@;@?WpA8NIKl+pS|@Fo3J>W8T1H&{2BwV$fo#35g`R%Os&yvYqp2U%G zxO>*yFLQ-My`1rOfi!nN*7J9>mAA3_Kff&QNdF(=x<9|n?&xtubm>pVf3W-4(Z*0n zMCFH z3TzIIaD}^G!OyD^Z0zrFPCmtBZRA034#V+z-x8dWhk{eU? z5}B;BkiYW{-4GE~TW2CxAq>ydVy|bPqnu#E;`mJ3`S%Hkf#pqIipzob0~-Y%8f5Iw z8=9FjAqHre=%YYrJElaEp*%&MN1TTH;m9SkGz~3WY!ttcX5eNn0J~du6F+di!S&ZJ z&^03y1o0=*b)3q%3E8ghY!EC+d<(VISxpgrqLw=us0KYogdz(D6^H#C)5-9yk09Ky zw`5&s)!Gclx6OQsS4?R~T8DrZ66iow@xs^n#wFLsc;tDTODdkVJn09(ffe)th!>Du&>8xvS-%Fi-wi`Xe;$EiLo9chfEWeM zx(xET-C$wiGy1kOP}_lMAPQEdcHg&W(|^>XKFT?g2+ld`M8CMyn2)If$-VDv74#L2 z(W%`3oN!&MBvTn$bYlr3T+O!z_J+mxml`5-53@@qGv@|>ACYKK6H~4F7#bVP`s8ao z9e=)~)^X2!~{x`NbGhrwCSe(UTr03iv1q7!vXfbIfB zjNP`%1_RR-Dq`p%Mi_FME#jEtcnP`>C+jP2Lg_6ZFlq2`o^3z#^N#$b;YoUog<5#} z%Pqa-DgYniz-ir{pWoFa5Gji@a%CLT2-GB=GWCy6b!9=ZSFGhIpGkfKXufx~v(&5r z4OJzJzmhkby&v*PQz#CZG_3teP47*vmn|b&&AMiJ@(&+&pvSf20+OZc2cXn^4P)d& z2-y{2PpazK|0cl@Zx?>aUl5dgY|Pv6;6*Qk-e3FQpn)^(C+v0v5zz#dApITE82?M@ z0$pfR+z_=GXlF+rixD-`odKNS!Aq9RdZ$(57Z3mW_*9r z)SvW4swQERu(aKiieguA7mT5@Xd8W*=W1o!R4m45U%f%hu+SDF@oHL3kK&}8+c}$d z8gjK7sM{UBWSaeW5Y>8SlY3J20f!_Y9 zUSXFh&jfcys0y3ed2N0MVHu9G$_aObYw9vG@d<_(wE1bNpL^=};Z8O(l9C3K^kp@BMf$Z*(8axOIWskk57A=9}pXj9_uAUS0Z3 zYGcPMIQ$O)A+_lyH#sz$d{o0qBTor5{`GoRR`;wiMlkEd`e(~Xu)r&=QS*7`rPbu& z|5tQ0csevo!o6v1RKOZW8SLR79?vX3GY{sHg#;x!ut!-IQ+D|?Cw);K5Ngy_;_L_m zO~yqHyO!_R*JKM~Pt4`R6z^t9_c09*SHlMkT3uY3egA783L5*?@>tXo#R+I8PZgWq zGxU2UJSuCBqc&WCg`g}5RjPqRYc_UGF-gSTRrml{axn!Di^6nC zu>t>g!f(_SWas08-Ap-0`{(a3*^;A2J=UC@71z-perEqik-C{I&tOjI@$3_>6S8CC zb{p+q3Ew3#fzqqCF#N?UF-3a5OR|NB%`N0TTG ziqyTce_HJ6P(PPu{{b+F|M-Cq41h-ZuXWmh_`KSk8&u|=nQK83#sBOW7eIBsu0(w4 z7GpNnGdq3LvC5-#
    DJ7(FWBcqtzGN^Os41_1ty0T=|8cG8Kj`^EWb~j)}4#`17W>=~*JmA#b)uHTjPr?pm!bZ1MRw>gyzEhQD`$xqlb)4VvDpyB20sYBu`NJ&Hu07nWhi zV|^o#_D8^nzw=8aFpu~rdDK{1gpka83jWOd;r2IgTROU57h@UMsc1>lQ*oc6z1{a3JiU> z1>q~<4HkEQ-o|5YvnlV1(c-XNevQBtQSdQmFLNKQf;9XYuW0+_doddOF|i^8T%;C} z114?we1%7*4r>`#LT7;U%^|AFAjFpoON=AT=iu#P;MHa-X-8Y_LSU+Yqf7?azT`G| zxwbf5@ap2Wn`DmyqkOgzpeQq`XFavRWKT&b8Q||5S8g~&euyds%~l;{nd1M~arDFl zGhi2PVzwAWo@v5Mg%d$KjTv#bjshZF9?CYOQ}{x+(GU8ynxi}z>koPjHUD zYq62Iq%bN&U&`#YMlNWO59)QRqSD}LAlv7C{=Jy89hZ{_4g~}~mTs20#;leg+6b9g z^BzKe3&160a!B1pw_j6xo#;^%fwO*@=<($eV8;-c@aldB*8R>kGSZaR6_DCOp-t@9 zbs<`5SBa*l4g$7qtk}fFP>LrFnBe#$3ZohJ!1L&o{t?6-%?2yKFjQs)j`>*)!HyzK>ayHBEz{49;O4br-uL?ZsGOs|(sn@Xls*n49WvFX zENB^G+zRF16jc|YNk+Px+#oa&w~ct-Yvf^!HIZ>mzgUKo6T4 zB0Ia*2Vh}1kh3xWtcx3g$Qv_2+xB0;e4oJy$@`inHdyY>$j?hk*}%%(2{+ZB~cQC?C#*vq9>z=<~$KCXN z;yj`UsemWwgyp0#Z*eK81c;e`(T~d)bYl)m)$~LCxdZ&|C#t?WR2n{{j&9J>Sv*G< zm{(XFDHcM;2pO3ALT5NZpg-)Xy;7=KG@0zAE9TM;d6fD(*33Kkjv9OyUhqtv&S((- zJGr+l=Zm%mlkzQ_ysLLlFwnYQTckUke{8xq!%)cHiXKHKl?O)=|NC=@G=2GOCyOmK zL6OZMsiOhRYqGzdk{Pk`t9-6rhc0f(C5rwTgIi zrl0uiX)5bjuizn`d?~$aH9qvtsOi7%sj28s$#$Tk^0v@(wAnSMI8{qEt5%I0L&lAd z*^D>5$pUUKt8jj!ZJH4r7l>i|<85^I!Ts_9{oQH#%lP8Fk%-#N|KkpQ>;0LX+Xp3!fG zvWRi%q57@chcq=@D4>!7myUrN0ppRY?jfN(c*(b;~vhbFIn1o84>mwLGT)+5y|iB(;tfiKzvmz)89x z<(QFa$~Q4eqk^}3+6y*#lFPOfH4!6)I$)C}es&Pk=pRdiFQpa7^*y_fe}ZJQZND9u z?3fi&Q}2+QjR#wO3!Xh$w zzSwN0Xz+Jt%&3^t^w~9GV3niT$E@OHLFS>50!q8H-)zDl5!-2yPYG=GdtO9`^#^4# z_8nK~=FLI)GA|Y4A@i*?TT$^Mm_1VyyX3+y2B<2cC3JMq zwZfanlism`W5OdOrWC5l7GW8%KXv>#=+zoK_U^#&j>Lallgm8=5e6@uXwZgfwjzJe zs$>!&6XOO5%B;K_1$0=&)*)7lIez&C-n@&Wa}SpV7rgvk7XkRgzRClJ24Y(k)tD8^ zWOR|zK!ZH@#BuI()vZi|2~wK~c=9c!Xpo)(hS<45oxAQvR<;nUG1g4D=X#DQKaxip z68Csbk%AnG21UpQd8zJXE9}1JMq<}Ke01PN)p5)O{AgGG*9QQ~)VbWr@xiGv)Us(j zV3wc@5@8xPAKohk1D64ND9wnVSXiZ8+)d&kx|awe*57~FKrQI0%tD+o`DNg#L7Vru zYDfB~a{b1-mAj!LAGv?ZfC^sjIOEfog|dn3ac!DOpM57f=fT=I}?+APnho{qA$I! zCeJ}-W=Bx}JIEPw4rXB4y9}j-aHl#19@1B$0p6AJ#o-4vgUrMnwo13vU-K2UxY$fV zuCbAO)W4{s$1iLMm~%C{zM^XQOqd7<5{Zh&3$D1yC~;^}hvRO3By2ok50*H>{!-fKJ70SsU-DxmZtH0P##Th@q>$@iiF1 zvH^wj>cQXVbY_uKE$(#ozBM09=Hz+a1$H+1IfD|J2JbG+$gW-QO$0PO7x%-6zLkkY zBO!Ohu5nq{Dcq4heZ*J0r$<-#CBC;u`HDQ`$$H!RXZi2QC@k-v1?AYl7oPPSstWEu zVR?x%{(s3>;#55HZys<|s7Zo-s>tu1Xuh+Et_K+fo)f`}s#>;x=OVPZ)_RVBsqNSt zTlW#9w9q-Fx}6{2uwL@R0JHbPk@?=QIG#MMfC-;6SCqu*_x(5%5e2{XgoUu9yP9YVVd2vG!*7c_| zS+5H&wq;!h@RR$6YH%aMn-UX9lYoB!Oz_f;3hTRWXi@WYQfIA4Oq>(P_JSBZC5>RF zSj3nQ6n3?)2$xw7cU>`?%73^4&f7Nm-3Z6fi!^rFRO*Dy9-J!zQlRK-jHI7(Fi5oa zBQbbU72t?e5!$Nbmr`T`s$Of8DCB-CNaNbr~g< zRo{7@Bksfbve>YbEHDA;&x@UP1`MmR;28g^=~4=hOT(Ka34HqmDiO@%0#UB3AiA5!Sj8 zyF;H?b{jvJD#x1ln~Xt)SK4B?S)k%@z5Gh@<+G(aZ#kpGs4BSN5ZC0HWI1f`y89yy z@jKOS8BO!lQL49>!s|=GYl1K?ERB>MPXhCNgHTkKQJsT{F)$h+JU)Vin8mnT=2RG< zT~D)gPEzIKZfD%^DekcG!VB*`u^o=5uP6FH%GW`~xM9GrEy5sxryDm(kaT(#@Wa~} zO@cx)Q_mh~QUX;~Ky48jQ(@UzttVPIGtzoD>X7g^jL=Q~TF^d6{ivY~sst3FKvC0e z{*lCR*+?H^Kzc^4qkbHp>Kh!BvHs^ISt|?MpuDl}Ky%B(+_lrzwkq3FR0q*!BeSuo zwO&1arP93egX+O#u;cgLT_`>_VGlRH#gdxCDXvLNC0=OEROS&!)H|5*EJS_Et}Qtj*)H#T3u%tqkcHidKP{(XyLkT#z>GYC>)I*lJ0c~ z`a0N`-!*fzOj{qCMvIBLn>%ZunvwIXM^x@_k zYfc>h^xWRlY2;?sc9*a9>b!Pa5y#Znh*Jna-eE1&y0MU*=Cy1+2SRAhCdbj8Xe{z~ zOxG(7u9QieUH%SQ3eT+*ni1g=eo3h@xzBQ1HED02P~jsJd}0M=fil7#l#T9$nH$x$ zf&ZidP3KwDs`Q0xuPn5yN;zFyH3m#^I!Q=&!)%<)QC_dS<70_dbTXOq1pu@CeWK@D zoK*$n)4cmnk(A$^88bRekgz!-AKpQ~5Sq{bjxP93t|s6|wtLRDyxl>6IZjY(vOLSf zF#LVPzP&f+hA|E)4fTe>YtgIn`t3VnSrK3CyO38YDZ^vMPY}NO=+&WK58m;5(#Top zLjJq0OA==Y?yT1)xs0&!W}z)kUch*fvcNm^n=hi{*7I0?53(rz7Iz5-B8n9bx^`*( z+N`lR{#4BF<1>HC1u5R6vPt4anG}`negr$BhOs&8)qa|@45yKNfz0Va`9q-JglOrU zTkkhBHk)~ljY%g{b*8V;ATC|~5OkpNz)BoPlM^v6BWA;Uit=6%Q_ZyPhRFlr048y9 z!(uk`l1coiZ{}-}cv^N8SXsJ4W;21*$dR{w*2VZb1$LP_nx>$CLSxI~u`y2ag+2x6WXLP;^S&Mq1+cHvr~@*}OLB_NtIEXu>| zhPIN1YWd~EcPw~(n7MiSebKa=DJYUIW9#njt?k9hLTeS4<|W(xB+7+Sw)vKhRD-AB zuB3?c7v4?XDzzr;(H}HWQP$OMxlU3s4VbEW29S^wUOS%?DI2qDs=5RApVAN5eAMLb zAh!g26eREe?7TrF&BtGJ49YwiGf^&5*&mQoQKEmi3kmTo^4i%TFexqMDtZHIh^R}D zZVstI6*C0I)qh6)DZJT{tryhW#EeHJDysroyo>i1ZWoxdEd84BSx18;tKKz|GEBU$ zvDr5$(i7d+(J@&urM`ERClSdnppl6zrQ?9ZKH~ktE^mV!D^*xFN*t?-QG!Z(D_^Q) z2}gh>%;XzNt|E+3r01H5r*V_G%8ypjDar}8hs+x>p+6%q#9%WoPCfu|OPUlnN8eM{ zdu2uNx{MJ+E|8N^eQ@s?@LC7Z6C)!?OJ7vKx0OlR2Nx9fjjP{(1{kwU@h)mA)l^&6 z7&}J3qZx|O#tH1@bb?TyW5Ocz3ASS+1qicq`d?dlOW>o}74uZ#OsFLGeQ_Js7aPFW zjqA2@bJwsN0E~PxZsr|<6Rjer+_dH4^kYl#rgt@S{vlvk`7+SOFY3X&Fv*u~)*iE| z9rz50t;T9@FMqpfSg9vK1zaf9M^pLf(OE}p37kbF!e68S8NO@+>XgQ6AL>3ocE(e7 za_d%ZZ5~w@7>*}d)y~AX_+EPkznFjcCsH+SaP<^#AOBGHVq4%jpdva)^Gi1)29JX$ z{&>d@i(#)KFwBo_AO9b`IHYq9_9oOZ%Z;!^PFX5G<~0JCBQxlDN}BG+ zF@`EUiSS05rV)VtD2uVf82I2IkDsLQIP_68r%qs8s=_%E8r`-CGytCU7CI-}jDI&9 zc-{8;iJ=z$}C&UV+rdRr`%0<*^oOK-=q0!qc3hz zdc}$_h3CM?GW$4hsV;y%c0Gd5qS^;lD=62}A-ASr3UExnswhA^En`c)ilO>_Z4*nq zbrq;>|1>B69=kwCkY|&y5|_R+;UPA@r!7E^Jk_`Qd+_@83L4ytr=A6^ zT6EV<2Ls!EYktSXGdjU5v{7}zTebWq68wvbXqzG1A;)L$z3vgjtjfk_J8CE3I_ zUZba_)BM(t%&0raf0Jchksx-C9`7^WlpRY&;~%#?0(k4gKUkdGVct|QzyNmO41-Vz z-G4(LE>V956xJMh5m(}AlC-gE9%!j^p?UgBph|8oVAQ+eKNRh+5E-rS@Iqzv?L_)9 zT~z7se4zDjsL2ZS0Zq6{vHb=3o$R$G8BYA9b6yc54V?QShEg0(L5*RY%*>mGE!n-d ziCH*sG?8(-=;ma9J}ozO+O#Mp!Au_W8l3rt^WyGcAB5vJm=X{AjrC$b7MeUwo9Ulu zZlams#FoZM|KqwzT&+u!{>e54#F zSv|q}-J4vmraQR1%{enQteeNN7}2-rto!9FkF-edBYoIw(k&kPZ5`fX#Ama^D_zHZ z8`PGP$G3bwzFU2m+$9|=j0jQC&f165_y7@WQ*xbik*n(QWukTO& zk{Cc6cB8^0C~>@tJDwy~RwKUs#QAv=WSt%m-fOQltLe5*<~tUb$x-TXjgOn^K(l49Qz%en|J{LCTKS-#VI3Y)?l%Jg*5u5V^UYqLdk)y#UsGrxk1@2b z?0^Xr zky?ASZjf9*LvmZ2so&y0Wau~(H2A{gv3t5)4=P|H?~^_bi9`%nFt10m?9WJvV@NTq z7_*n1$SYl8O*@Hq{;(ap96QmOe73qGJTQ>=|O1Wk?_uLeXNBLO11a9Lw(G&X!n70{w8gbma&tlI~ zG2Q0TmV5??PKwi@6G(P7p#Nbcxla$_$3dC!%T5$zo^5%fJKjW^ejDzlF;4?=ta^;G zF6?(Kr#`$$}D;rq<7$+V;BUtHvU{Q$|pvb#7FA~1s~BhW<`qK|k$ z+mkh>?5!G7=qnZ|B3RhYHY1jbI%VIzV*#vxOTZh$Xjl&}vW+5yOPL*JbN~|6>0;%^ zf05zh<57foU5dC~76UunEBeLBmzogBIWz;#A~KT&_}{{3`VJ^G(eY8{9VtV^$rX?t zLVR)Gb6jL#tA)A_NcEPd!o9ReVYq+T@_c+AU5r7t%ocQuFZ-%-7O|tkThXfO2XjWG z{eyQ|(Vw`q5m*Clv?P9+i9{$3o6ruwo^v2C;e8l?`8YDWd&3*cI7D}UBkwoIcX;9q zGe0=Ae~a}kX&KA6a2{rn$3mY0RcIks|L)cLpTxL^;n>I3&VMo18jNouQ8lYfHwNZw`+*xebLC&gVn*l*MJ-9(T??(AC=+ z^)>R}-JcjvQV&H1CfjF#yO}3d;U%Y>VB)&hIX;^ip}9>{1iFnwsi!(GeDC-)aZ>m5 zI`&XboeAqd?c|5FhpnD2jdQ}MTgV~R18b7*x0b=;?V8j)Pf6691JKG|dl;)HV@=My z(PF_-XF=5+OF?WfE60PB{GmqxW8`PKF;?iNz4`oMEaZ^H+l%Cm?PAoG2r(NAo4a`N zDrA_xrn6!4t?9g0u4~%~>-spL+~Lk{g@Pt&Q5HOT?{ybk%!CxO&Gibr)riD=@}cxs z>w?9_3VdLLd}ir-{cZSb9BJe@Y=Rz5;9l3q=xE+MG{Yt>KXGo_9S2*@*}xQq)g3=u z43GF#mb^?K(v`$((QE+*Mwf!9qG{@n3&AVZiFC6R)iw2_u17Ude$Kuuz_8VR&xUsg zoffuQpKA9H2ai0mn(m>^Ev%P`UoVXYv!N$u+ zHQ6ZHx9C^&I&qMOF37BnvSL_vL$7(~Ka?yth!O}{pYh~=)2fP9dj<&IzsJI=4tWL; zJ_FLV_)N(QYsNyngPLCv3Q~S2rfgs?n;G;1{o3;L2*<71(B4KweqHYCA{f=}<9JJP z72UH5*u9DABpU;s(B;I_MHsc7bJuNJ_9Z?80p;zfC8D@cEy9#vm9gnMEz z=&lbWE}sDv&j5)P+`9WqvX@6PPi8j-mychbf?HlJ&VO_M!T;toUS4F^4W2|?yxcge z6j@Sn&wypi7_7{C=bKD9xlC=-YzERel&LQ2dSbVbpWV1WBYr`EXwlWQsYa-smI?0&~xpzo4?bewbdGv z>e7n5xEDL^l@dpCnRuD#`dfBq3BhjWHXr2KgHsBA@ZHKIYhCd?PRF<2l3Ng%lIOsd z4xa(zBmKx!oRRXiiQSfJ?9i}vY~5{e(*o3jYItT8qcly~T$r=n{x3)2tWeD9OlVVm z9SdbRAS#O)s8SuDZjPPZCU`h}NvPOvqf5E7ssbC4B>s(yAA;41hLXKMu9HV<_$poq zk+|XT;>qq9Q-46XT=36)WWEJKSy@I8B^?_G-%=;imo`tN9z|$1i?P{PvnXj8hL-U< z<*MC=8}=fN=t8)o6I61|>iZRu<7OnL<>no3VlLM8v^BMilHK`1Ouy@YEx8!8J9CPM zp!T~Z{S?t}zj1wuGM<<)QH2(a*k=HP#1`wr3v2qZ;Ed==I(S$%{t@%V#gk^e*9&wT>o4U$JE^L8pzCXAAC}->yT^DjaDiQ>T2xnIjSg}lMBx|gq!CnjMua=4yCud?VfY?zo>f92(%tD(LGC;tRv-kpy2rpfO7Tw~EkMo1?z0esxy4mjt;(P~B=^Bk; zpA+h%hBtZ4bS8+t$IzF|SLfr4&<)|UuV2B-u97mzpBgUhM&5AAcw;mD>Mt#RlVEpn z`9_b~Q9KLjq^iDT8X1c`=4X{GYDVvg{pLji6djH=znvXToKv6&E>HhJbzv13b3@eL z4v=30yXES)E&teEJ;64dHUGP6{1zuyXUJBmF1YBh4%(&IEI;H+@itT&mFQUPO8}_x z?En`EHuUg-l-CNK6Yw|IZ+@$}?)|St>_Eb5r&kx}+`dSK$U>!Bb_9XrP^&=8&DA*@ z2L5;f!h6dk9$*^>%pJ4j)95g^mn2V^4toqHvF$PnCZ=ShA_HSrRB-Y8jGmR*@+_fE zEQ`8mtM-vd7It(@ksCH&7t+cC3PMJH2<$*cKKI2d7C^)4q|)$8%? zZ2J!jZ}+98`O^FFns^xarf6Rd$YeSX5KPNnxo{_0 z2+v?<0!dF!xmnWP;CW>`6mD}(yv&8+ZGg$IQKbk72Yx&QZnaF6gIjOMhjh5R zSc#d-s<{;QC$W|&W3w*k#U_@{si4!hQ)b5_$QWWNy8Xt9M=qho1c#*u9B7g{^-Mx) zvFw}i`lgvRFWrIjm&Cfa{5rWH5!xgd3Q<6>gJryHdOxe$(r4FJ+L(q@TdS)BvvY@R zkmByzU?jDDD2m8OdK`o2u^ zpaua%?uFRku07C2JqEY6qMp!c_XcSoU5aAN{V#@EKpbv)z5^jm!A18w8!lq>#Q{1J z%tNp10VP78G5tH)CKEMtGP{?*=C42_k=LDXu-Q;0+f1!;Vz4vMe*W}}t|t`%#;N0+ z`ez}fI3eX6CC{N&Y@=-7)$Jcn+6M6b%pbq9`y!U}zn_@3J_81^t~S0e&erih7MxeO zu8w`!F}mhmYtMWfZ#}l@@r2yx{13$>Dbox_`q?jWho{egS;J_%FwrgyP9&^{IN;V( z=)c^^?LdN1e%eKWO#^xM)~~$)V*G2hK{CtoOw~RKdJ=QWqqe><@@zGe>`epPs{H8e zNYRdr6{K`9nqQFIyZV`EgkTdf%w?0uAXl@4wKI)0(dxd%2TLr85Sc5?PREVFwu>|1 zsq}I`UF5gQ=OmC5Y@qQ7clEArkB3shd*aK_7@iQo6eSB66ZfAx`@%Z?tg<#~LW7Zw z4`6m7!i=*{SHlrfc0$dP(aU!AcrRsgD-kbtR305C;AZ30IyJq^q32MAbPCeE&d(z| zmO?3bWZpuGx2RKW#3C0MnZ&$nH~F~79IxH@P%(!%DOF(p(^b!0r4-kh*)xD8rhzRY zOTRbY-+TwKe|JnY(CAMO$74>SV z$)`ZI7G^AnUZq z>96xGDO~tnCR`X)$7R3&G%s1AE@^4lbccszg8t>g)T@Q5F%wz)sKqFp90^7A@}r-MsC`{^=iR6rAQi9V=2->uaM$xGWW5^PCNnT7Mjw zA#&8c(9e2nXUyUiglgBI($7_bj;wZwyP*pmBi9RX7aS2aTuYK9l+k2pXwv~nERDoE z*z>1o+PipYtm@fHNMq9qOKHYcdQ(Le?)Lzs^yvd{5I>M?sf<{f$WF_o>v0BOjh;-5 zcv*e+vi73COWa6=b--N`ZBji<6~2~;b(5veCu#ZGlx7eml^pzSAYr|A?dwbxL_1;Kl`GI84YxQ%`@PR^H@ziE+ z(4_QTem-3|98>8^)-=ypo3Y(X$jFe9ea{dAS8`+$lk+7$5mSGcG_>Wednqvj9!nn- zKm6r5pQJu9^7uX8UR%wNIQKw7pPDq6OQk|pFQ*5u$nV%i2r0se@C?LDoV;uyUbedC-2Ljt2#F~TH;A?bn{-m-$A9l`I=ErT&=Ec zB^bzD-gzwCbOshY@%K0!q>>*MWGGv05pY`1t<^Xa9`?xCg31R_u+>KG7=cwrj*WTN z+?7~+*TUBN1{+{QmCzBQ`9aOi6 zrR>LMsnRO#;K5qCGW2#rl?p%Z?Z#<$o>ir8y!}hqU9jE7a?i@W1bqH!ZJ8Bwzs`fG zxlf~^InUs9G&uCHL#^3fQZ*emtiyT!viRYlBQO_M!`a{{)4^PUmZxxb$p~g=_-4jg zL|RcQ_DZ|jpeG*Oc^)iS0BWf4A#|RuxSS!jFOCD3LYXgmTE{BI-^dR2Qq7*b~^pLQI z>5Y^BOazqwNQ4Zg`SL>tc}Q0C8`^3FtZw#Xl2_Vx<&N3QH8SS_!tVgJY+-8@ITG@1p)e)5*IcX5<_y&{C__4!)mKYjuGn;b$q+rt7!| zAF8NXu=IdtosNkamtbvF;Xf2uy>n<9y$8$5UgAZCJ5)x^cA1ezZ0X}4)PFwfX^|F< z5Pw29$Inz3mC0NJu^+~ARq7DGN~bHjHE^qe=EB0t)RCfkz^JzNVPlPyM1Wjc!&-Q`VsQqKqPG5@4HyB1r& z|5wahCNNc91a0=#Ihw{Gn~7{tynB1|p3>Ti>4?h6?@3L>p*C(rnb*u^3nDPCOEi=5 z4Crk-jT$U})4wO|2!8xF7k#a{O*Rwzrenea;vo-J!!qrlbr$!d8g1k;q2Lze&ePMd+L0)(KhY3VP5G4N=@InGK`3ykDc4Yb z)Yme6kns0cG?q2Yr&m8jg{L+GwHiOaA=cFvY(8XhR--jaXz$%D^Q|3dB3alhBb$i} zpWt0AJZl%U@!}xwYjUS8u$6N@a8cDN702l=K1nV8>8O&BngAO&D6T@xWobETmS)4F z5g(gDcS&kE28Edh+x+{$pj+aUuO_500n1EZYnD^XtOoh{rKQe1GIu#pigN(2$2OT@ zJK+$cBAjVhxt)fxT|PgN)h;tC$n@>l2eNz&OSyPjQdjpt^VQtGP70ygKkp$L+QEpt zAmE9rPatOA$JCP{PmGznI5v{atKXU}SJYNuL&p;RkX-Yoo+&_?kAOOPd68 zmOD@5qDMp`e8H;XqM;ASv0I(PxAZ_D*T|byxXU#-av^w4SeLq(q>^PhNIH?+(0nDD z#X5B8UarT|nU~TIRLc9asvW<6PBQ&ekZFts(+Y$!R#0%_vD?r2lI{9Vw{h(HmC?1{_CQJ5tH45o`3!yJ&0B} literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/assets/live_stream_2.jpg b/docusaurus/docs/Flutter/assets/live_stream_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..02a35132976c34f12e4ca177d7f80a59c34570bc GIT binary patch literal 30178 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 U1;#p%x(K-`k=XX#mwzk&4*=jNBLDyZ literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/guides/adding_chat_to_video_livestreams.mdx b/docusaurus/docs/Flutter/guides/adding_chat_to_video_livestreams.mdx new file mode 100644 index 00000000..b5bdca6c --- /dev/null +++ b/docusaurus/docs/Flutter/guides/adding_chat_to_video_livestreams.mdx @@ -0,0 +1,76 @@ +--- +id: adding_chat_to_video_livestreams +sidebar_position: 7 +title: Adding Chat To Video Livestreams +--- + +Adding Chat To Video Livestreams + +### Introduction + +There are two common scenarios in live-streaming applications. Let's explore creating both types: + +### Split-screen + +In the split-screen implementation, we have a visual split between the video and the message list. + +![](../assets/live_stream_1.jpg) + +``` +Scaffold( + body: Column( + children: [ + Expanded( + child: // Your video implementation here, + ), + Expanded( + child: Column( + children: [ + Expanded( + child: MessageListView(), + ), + MessageInput(), + ], + ), + ), + ], + ), +) +``` + +### Overlapping chat with a transparency gradient + +The second type looks like this: + +![](../assets/live_stream_2.jpg) + +We can use a `Stack` for achieving this: + +``` +Scaffold( + body: Stack( + children: [ + // Add your video implementation here + ShaderMask( + shaderCallback: (rect) { + return LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [Colors.black, Colors.transparent], + stops: [0.4, 0.65] + ).createShader(Rect.fromLTRB(0, 0, rect.width, rect.height)); + }, + blendMode: BlendMode.dstIn, + child: Column( + children: [ + Expanded( + child: MessageListView(), + ), + MessageInput(), + ], + ), + ), + ], + ), + ) +``` From ffb7c91c81a671d248d203df7074541551391dd2 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 29 Jul 2021 18:23:51 +0530 Subject: [PATCH 096/113] added code highlighting --- .../docs/Flutter/guides/customize_text_messages.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/customize_text_messages.mdx b/docusaurus/docs/Flutter/guides/customize_text_messages.mdx index f0fb1ac0..a2d37333 100644 --- a/docusaurus/docs/Flutter/guides/customize_text_messages.mdx +++ b/docusaurus/docs/Flutter/guides/customize_text_messages.mdx @@ -24,7 +24,7 @@ separately, follow the same process without the `.copyWith` and use the default First, add a `MessageListView` in the appropriate place where you intend to display messages from a channel. -``` +```dart MessageListView( ... ) @@ -41,7 +41,7 @@ however, this quickly becomes hard to maintain as more parameters and customizat default message implementation as aforementioned. ::: -``` +```dart MessageListView( ... messageBuilder: (context, messageDetails, messageList, defaultWidget) { @@ -52,7 +52,7 @@ MessageListView( We use `.copyWith()` to customize the widget: -``` +```dart MessageListView( ... messageBuilder: (context, messageDetails, messageList, defaultWidget) { @@ -69,7 +69,7 @@ If you intend to simply change the theme for the text, you need not recreate 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) { @@ -86,7 +86,7 @@ MessageListView( 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) { @@ -103,7 +103,7 @@ MessageListView( To add elements like hashtags, we can override the `textBuilder` in the MessageWidget: -``` +```dart MessageListView( ... messageBuilder: (context, messageDetails, messageList, defaultWidget) { From 47fe22e03d5f921857cbd7dbbd120e9b4b6245b2 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Thu, 29 Jul 2021 14:54:57 +0200 Subject: [PATCH 097/113] docs: additional localization examples --- packages/stream_chat_localizations/README.md | 6 ++++- .../example/README.md | 27 ++++++++++++++++++- .../example/lib/main.dart | 2 ++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md index 59cf6810..c9adc7ef 100644 --- a/packages/stream_chat_localizations/README.md +++ b/packages/stream_chat_localizations/README.md @@ -15,7 +15,11 @@ - [Chat UI Kit](https://getstream.io/chat/ui-kit/) - [UI Docs](https://getstream.io/chat/docs/sdk/flutter/stream_chat_flutter/introduction/) -This package provides localized strings for the stream chat widgets for many languages. +## 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. ### Changelog diff --git a/packages/stream_chat_localizations/example/README.md b/packages/stream_chat_localizations/example/README.md index 07e5ac18..003f56ee 100644 --- a/packages/stream_chat_localizations/example/README.md +++ b/packages/stream_chat_localizations/example/README.md @@ -1,2 +1,27 @@ # Stream Chat Persistence Example -Please see `lib/` for example code. \ No newline at end of file +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/lib/main.dart b/packages/stream_chat_localizations/example/lib/main.dart index 436553c1..818086df 100644 --- a/packages/stream_chat_localizations/example/lib/main.dart +++ b/packages/stream_chat_localizations/example/lib/main.dart @@ -76,6 +76,8 @@ class MyApp extends StatelessWidget { ], // 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, From f159f4e23ef77d9ad68f2b6aeeb427b6ce9c2855 Mon Sep 17 00:00:00 2001 From: Gordon Date: Thu, 29 Jul 2021 15:54:56 +0200 Subject: [PATCH 098/113] docs: add image --- packages/stream_chat_localizations/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md index c9adc7ef..68796712 100644 --- a/packages/stream_chat_localizations/README.md +++ b/packages/stream_chat_localizations/README.md @@ -15,6 +15,8 @@ - [Chat UI Kit](https://getstream.io/chat/ui-kit/) - [UI Docs](https://getstream.io/chat/docs/sdk/flutter/stream_chat_flutter/introduction/) +![localization_support](https://user-images.githubusercontent.com/13705472/127504329-a9690184-ce0f-4442-adb4-a33b5e3a3bf1.png) + ## 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**). From afc636bcdac4ec4aae76167c34e9362be257bba7 Mon Sep 17 00:00:00 2001 From: Gordon Date: Thu, 29 Jul 2021 15:56:44 +0200 Subject: [PATCH 099/113] docs: move header --- packages/stream_chat_localizations/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md index 68796712..c88106bc 100644 --- a/packages/stream_chat_localizations/README.md +++ b/packages/stream_chat_localizations/README.md @@ -15,6 +15,10 @@ - [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. + ![localization_support](https://user-images.githubusercontent.com/13705472/127504329-a9690184-ce0f-4442-adb4-a33b5e3a3bf1.png) ## What is Localization? @@ -23,10 +27,6 @@ If you deploy your app to users who speak another language, you'll need to inter 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. -### Changelog - -Check out the [changelog on pub.dev](https://pub.dev/packages/stream_chat_localizations/changelog) to see the latest changes in the package. - ## Supported languages At the moment we support the following languages: From 0dea582ee78a5a15245e4fd68a2aa614d55a450c Mon Sep 17 00:00:00 2001 From: Gordon Date: Thu, 29 Jul 2021 16:29:05 +0200 Subject: [PATCH 100/113] docs: update description, image and grammar --- .../Flutter/guides/adding_localization.mdx | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/adding_localization.mdx b/docusaurus/docs/Flutter/guides/adding_localization.mdx index 5645e811..d7e10cb1 100644 --- a/docusaurus/docs/Flutter/guides/adding_localization.mdx +++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx @@ -10,6 +10,14 @@ Adding Localization To UI Widgets 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). +![localization_support](https://user-images.githubusercontent.com/13705472/127504329-a9690184-ce0f-4442-adb4-a33b5e3a3bf1.png) + +## 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: @@ -22,17 +30,17 @@ More languages will be added in the future. Feel free to [contribute](https://gi ### Add dependency -Add this to your package's pubspec.yaml file, use the latest version [![Pub](https://img.shields.io/pub/v/stream_chat_localizations.svg)](https://pub.dartlang.org/packages/stream_chat_localizations) +Add this to your package's `pubspec.yaml` file, use the latest version [![Pub](https://img.shields.io/pub/v/stream_chat_localizations.svg)](https://pub.dartlang.org/packages/stream_chat_localizations) ```yaml dependencies: stream_chat_localizations: ^latest_version ``` -You should then run `flutter packages get` +Then run `flutter packages get` ### Usage -Flutter generally and the Stream Chat SDK will use the system locale of the users device, if that locale is supported (see below), if the locale is not supported we will default to `en`. +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`. Make sure to read more about localization in the [official Flutter docs](https://flutter.dev/docs/development/accessibility-and-localization/internationalization). ```dart @@ -70,17 +78,42 @@ class MyApp extends StatelessWidget { } ``` +## 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. -Checkout [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. +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 exisiting languages +### 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. -Checkout [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. +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 @@ -114,7 +147,7 @@ Here is an example of how that would look like: } } - // if we didn't find a supported language, we return the italian language + // if we didn't find a supported language, we return the Italian language return const Locale('it'); }, // Add GlobalStreamChatLocalizations.delegates @@ -123,7 +156,7 @@ Here is an example of how that would look like: ``` -In this case we're using italian as the default language. +In this case, we're using Italian as the default language. ### ⚠️ Note on **iOS** From 276f6a1efc82c7e5615312096f3b024b6d2a469c Mon Sep 17 00:00:00 2001 From: Gordon Date: Thu, 29 Jul 2021 16:34:57 +0200 Subject: [PATCH 101/113] docs: spelling and grammar --- packages/stream_chat_localizations/README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md index c88106bc..bf648e45 100644 --- a/packages/stream_chat_localizations/README.md +++ b/packages/stream_chat_localizations/README.md @@ -45,7 +45,7 @@ dependencies: stream_chat_localizations: ^latest_version ``` -You should then run `flutter packages get` +Then run `flutter packages get` ### Usage @@ -88,13 +88,13 @@ class MyApp extends StatelessWidget { 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. -Checkout [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. +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 exisiting languages +### 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. -Checkout [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. +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** @@ -115,8 +115,7 @@ Example: ## 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 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. From dd5bf733e37aee268da2beedbf628647ed954350 Mon Sep 17 00:00:00 2001 From: Gordon Date: Thu, 29 Jul 2021 16:40:10 +0200 Subject: [PATCH 102/113] docs: add localization image --- .../Flutter/assets/localization_support.jpg | Bin 0 -> 88489 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docusaurus/docs/Flutter/assets/localization_support.jpg diff --git a/docusaurus/docs/Flutter/assets/localization_support.jpg b/docusaurus/docs/Flutter/assets/localization_support.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50d8b761ee059126f48669a9dfb409c69be48bcb GIT binary patch literal 88489 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 Date: Thu, 29 Jul 2021 16:42:43 +0200 Subject: [PATCH 103/113] docs: update path to image --- docusaurus/docs/Flutter/guides/adding_localization.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/adding_localization.mdx b/docusaurus/docs/Flutter/guides/adding_localization.mdx index d7e10cb1..7e6cb7e2 100644 --- a/docusaurus/docs/Flutter/guides/adding_localization.mdx +++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx @@ -10,7 +10,7 @@ Adding Localization To UI Widgets 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). -![localization_support](https://user-images.githubusercontent.com/13705472/127504329-a9690184-ce0f-4442-adb4-a33b5e3a3bf1.png) +![](../assets/localization_support.jpg) ## What is Localization? From 501d646349b04153eb7d610a385fb8f3e76b36ca Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 29 Jul 2021 20:42:26 +0530 Subject: [PATCH 104/113] added details --- .../guides/adding_chat_to_video_livestreams.mdx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/adding_chat_to_video_livestreams.mdx b/docusaurus/docs/Flutter/guides/adding_chat_to_video_livestreams.mdx index b5bdca6c..a34227a4 100644 --- a/docusaurus/docs/Flutter/guides/adding_chat_to_video_livestreams.mdx +++ b/docusaurus/docs/Flutter/guides/adding_chat_to_video_livestreams.mdx @@ -8,15 +8,23 @@ Adding Chat To Video Livestreams ### Introduction +Video livestreams are usually complemented with a chat section to make the livestream more interactive +and encourage retention. + +This guide details multiple ways of adding chat functionality to your video livestream. + +### Implementing Chat + There are two common scenarios in live-streaming applications. Let's explore creating both types: ### Split-screen In the split-screen implementation, we have a visual split between the video and the message list. +This allows the content to be unobstructed by chat and have a clear separation of boundaries. ![](../assets/live_stream_1.jpg) -``` +```dart Scaffold( body: Column( children: [ @@ -40,13 +48,17 @@ Scaffold( ### Overlapping chat with a transparency gradient +Another way to add chat is to overlay the video content with messages which progressively fade out +as we go to the top of the screen. This gives the content a more rich feel as it takes the whole +screen and allows the chat to be more homogeneously integrated with the content. + The second type looks like this: ![](../assets/live_stream_2.jpg) We can use a `Stack` for achieving this: -``` +```dart Scaffold( body: Stack( children: [ From 419864cd908083617f4f642cd1a64088d9dab288 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 30 Jul 2021 11:08:21 +0200 Subject: [PATCH 105/113] docs(localization): minor fix --- docusaurus/docs/Flutter/guides/adding_localization.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/adding_localization.mdx b/docusaurus/docs/Flutter/guides/adding_localization.mdx index 7e6cb7e2..c08f081c 100644 --- a/docusaurus/docs/Flutter/guides/adding_localization.mdx +++ b/docusaurus/docs/Flutter/guides/adding_localization.mdx @@ -40,7 +40,7 @@ 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`. +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 From 7561125a057eef0d7da50ffdb67113fecaf08b0b Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 30 Jul 2021 14:57:26 +0530 Subject: [PATCH 106/113] fix(ui): add null-check in currentUserMember Signed-off-by: xsahil03x --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 5ae6f9ea..17ad24c6 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -965,7 +965,7 @@ class _MessageListViewState extends State { final currentUser = StreamChat.of(context).currentUser; final members = StreamChannel.of(context).channel.state?.members ?? []; final currentUserMember = - members.firstWhere((e) => e.user!.id == currentUser!.id); + members.firstWhereOrNull((e) => e.user!.id == currentUser!.id); Widget messageWidget = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), @@ -1072,7 +1072,8 @@ class _MessageListViewState extends State { } FocusScope.of(context).unfocus(); }, - showPinButton: widget.pinPermissions.contains(currentUserMember.role), + showPinButton: currentUserMember != null && + widget.pinPermissions.contains(currentUserMember.role), ); if (widget.messageBuilder != null) { From 0837c5576d2f6e6e41daaa3955d4ec9b1523556d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 30 Jul 2021 15:03:19 +0530 Subject: [PATCH 107/113] chore(ui): update changelog Signed-off-by: xsahil03x --- packages/stream_chat_flutter/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index de55a3ca..27110e50 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +🐞 Fixed + +- [#590](https://github.com/GetStream/stream-chat-flutter/issues/590): livestream use case, no members when sending message + ## 2.1.1 - Updated core dependency From 8399a97c2952713f3616fc885c2243b7c4d9c2ef Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 30 Jul 2021 15:38:36 +0530 Subject: [PATCH 108/113] added details --- .../Flutter/guides/adding_chat_to_video_livestreams.mdx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/Flutter/guides/adding_chat_to_video_livestreams.mdx b/docusaurus/docs/Flutter/guides/adding_chat_to_video_livestreams.mdx index a34227a4..9e2afb44 100644 --- a/docusaurus/docs/Flutter/guides/adding_chat_to_video_livestreams.mdx +++ b/docusaurus/docs/Flutter/guides/adding_chat_to_video_livestreams.mdx @@ -9,13 +9,18 @@ Adding Chat To Video Livestreams ### Introduction Video livestreams are usually complemented with a chat section to make the livestream more interactive -and encourage retention. +and encourage retention. There are several ways to show the chat interface on the screen and requires +some design choices. This guide details multiple ways of adding chat functionality to your video livestream. ### Implementing Chat -There are two common scenarios in live-streaming applications. Let's explore creating both types: +There are two common scenarios in live-streaming applications depending how well integrated the two +components (video + chat) are allowed to be on the screen. Two common types are split-screen and a +chat overlay that fades in. + +Let's explore creating both types: ### Split-screen From be24604e5395d153e9e3a96dd02183588c45dbfa Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 30 Jul 2021 17:57:45 +0530 Subject: [PATCH 109/113] added added new guide --- .../guides/customize_message_actions.mdx | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 docusaurus/docs/Flutter/guides/customize_message_actions.mdx diff --git a/docusaurus/docs/Flutter/guides/customize_message_actions.mdx b/docusaurus/docs/Flutter/guides/customize_message_actions.mdx new file mode 100644 index 00000000..10b79fc1 --- /dev/null +++ b/docusaurus/docs/Flutter/guides/customize_message_actions.mdx @@ -0,0 +1,79 @@ +--- +id: customize_message_actions +sidebar_position: 8 +title: Customize Message Actions +--- + +Customizing Message Actions + +### Introduction + +Message actions pop up in message overlay, when you long-press a message. +We have provided granular control over these actions. + +By default we render the following message actions: + +* edit message + +* delete message + +* reply + +* thread reply + +* copy message + +* flag message + +* pin message + +:::note +Edit and delete message are only available on messages sent by the user. +Additionally, pinning a message requires you to add the roles which are allowed to pin messages. +::: + +### Partially remove some message actions + +For example, if you only want to keep "copy message" and "delete message", +here is how to do it using the `messageBuilder` with our `MessageWidget`. + +```dart +MessageListView( + messageBuilder: (context, details, messages, defaultMessage) { + return defaultMessage.copyWith( + showFlagButton: false, + showEditMessage: false, + showCopyMessage: true, + showDeleteMessage: details.isMyMessage, + showReplyMessage: false, + showThreadReplyMessage: false, + ); + }, +) +``` + +### Add a new custom message action + +The SDK also allows you to add new actions into the dialog. + +For example, let's suppose you want to introduce a new message action - "Demo Action": + +We use the `customActions` parameter of the `MessageWidget` to add extra actions. + +```dart +MessageListView( + messageBuilder: (context, details, messages, defaultMessage) { + return MessageWidget( + customActions: [ + MessageAction( + leading: Icon(Icons.add), + title: Text('Demo Action'), + onTap: (message) { + /// Complete action here + }, + ), + ], + ); + }, +) +``` From bfcf827787f6afa25c4afc433fd35d608ef7c7f9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 30 Jul 2021 18:06:18 +0530 Subject: [PATCH 110/113] added image --- .../docs/Flutter/assets/message_actions.png | Bin 0 -> 123514 bytes .../guides/customize_message_actions.mdx | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 docusaurus/docs/Flutter/assets/message_actions.png diff --git a/docusaurus/docs/Flutter/assets/message_actions.png b/docusaurus/docs/Flutter/assets/message_actions.png new file mode 100644 index 0000000000000000000000000000000000000000..157f39791fa05143e10a3911a1566e8988b53a06 GIT binary patch literal 123514 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

    YD=5uh~ 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 literal 0 HcmV?d00001 diff --git a/docusaurus/docs/Flutter/guides/customize_message_actions.mdx b/docusaurus/docs/Flutter/guides/customize_message_actions.mdx index 10b79fc1..90237558 100644 --- a/docusaurus/docs/Flutter/guides/customize_message_actions.mdx +++ b/docusaurus/docs/Flutter/guides/customize_message_actions.mdx @@ -9,6 +9,9 @@ Customizing Message Actions ### Introduction Message actions pop up in message overlay, when you long-press a message. + +![](../assets/message_actions.png) + We have provided granular control over these actions. By default we render the following message actions: From 1148fc7d6270ed0de39f8c747ce46b5c27b5fdb7 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 30 Jul 2021 18:16:49 +0530 Subject: [PATCH 111/113] fixed snippet --- docusaurus/docs/Flutter/guides/customize_message_actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/customize_message_actions.mdx b/docusaurus/docs/Flutter/guides/customize_message_actions.mdx index 90237558..b1cc07a0 100644 --- a/docusaurus/docs/Flutter/guides/customize_message_actions.mdx +++ b/docusaurus/docs/Flutter/guides/customize_message_actions.mdx @@ -66,7 +66,7 @@ We use the `customActions` parameter of the `MessageWidget` to add extra actions ```dart MessageListView( messageBuilder: (context, details, messages, defaultMessage) { - return MessageWidget( + return defaultMessage.copyWith( customActions: [ MessageAction( leading: Icon(Icons.add), From 6a32a293119325d95f49cd7feee87452c026f143 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 30 Jul 2021 16:08:56 +0200 Subject: [PATCH 112/113] chore(ui): 2.1.2 --- packages/stream_chat_flutter/CHANGELOG.md | 2 +- packages/stream_chat_flutter/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 27110e50..81e6235e 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming +## 2.1.2 🐞 Fixed diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index f47fa9f5..61146b72 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.1.1 +version: 2.1.2 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues From 4586bfc6eb40c3cc96096bcd65b3a25409bc9c1b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 30 Jul 2021 16:13:49 +0200 Subject: [PATCH 113/113] chore(repo): update pana conditions --- .github/workflows/pana.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pana.yml b/.github/workflows/pana.yml index bef2ec03..a6a7e822 100644 --- a/.github/workflows/pana.yml +++ b/.github/workflows/pana.yml @@ -31,8 +31,7 @@ jobs: TOTAL: ${{ steps.analysis.outputs.total }} TOTAL_MAX: ${{ steps.analysis.outputs.total_max }} run: | - PERCENTAGE=$(( $TOTAL * 100 / $TOTAL_MAX )) - if (( $PERCENTAGE < 90 )) + if (( $TOTAL < 120 )) then echo Score too low! exit 1 @@ -53,7 +52,7 @@ jobs: TOTAL_MAX: ${{ steps.analysis.outputs.total_max }} run: | PERCENTAGE=$(( $TOTAL * 100 / $TOTAL_MAX )) - if (( $PERCENTAGE < 90 )) + if (( $TOTAL < 90 )) then echo Score too low! exit 1 @@ -73,8 +72,7 @@ jobs: TOTAL: ${{ steps.analysis.outputs.total }} TOTAL_MAX: ${{ steps.analysis.outputs.total_max }} run: | - PERCENTAGE=$(( $TOTAL * 100 / $TOTAL_MAX )) - if (( $PERCENTAGE < 100 )) + if (( $TOTAL < 120 )) then echo Score too low! exit 1 @@ -94,8 +92,7 @@ jobs: TOTAL: ${{ steps.analysis.outputs.total }} TOTAL_MAX: ${{ steps.analysis.outputs.total_max }} run: | - PERCENTAGE=$(( $TOTAL * 100 / $TOTAL_MAX )) - if (( $PERCENTAGE < 80 )) + if (( $TOTAL < 100 )) then echo Score too low! exit 1