From d3e9bdafbde52b0fd083d124d60771d2c649183a Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 1 Jul 2021 20:26:01 +0530 Subject: [PATCH 001/145] 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 5161c8e8b..1a80d19ec 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 64acae982..dfffce88a 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 000000000..3ed1a9c33 --- /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 62bfd2999..20b2396ac 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 000000000..1985397a2 --- /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 000000000..936336f9e --- /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 000000000..41cc7d819 --- /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 000000000..49088d477 --- /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 000000000..3ec4d86e1 --- /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 000000000..e69de29bb 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 000000000..25ca28ea4 --- /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 000000000..9c1146d8f --- /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 000000000..349866582 --- /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 000000000..9ea48cb5f --- /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/145] 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 d35228869..c1246a31e 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 408bf9e1c..4eb99d869 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 1a80d19ec..3a495ca58 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 6d0060853..b0bd13caa 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 3ed1a9c33..b1d81fded 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 342914d48..e15f51c78 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 e69de29bb..000000000 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 25ca28ea4..b0ca6cd5a 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); - - final Locale locale; - - final Map 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; - 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 000000000..7566be30d --- /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 9c1146d8f..0cfb60ba6 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 349866582..b755a6dde 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 9ea48cb5f..0324dd17d 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/145] 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 b0ca6cd5a..bef008fd6 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/145] 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 825def58a..ebbaed7d3 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 7ec5fb04f..547c58213 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 1572061ea..1ae03ca1f 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 363fac456..48eea1435 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 eaabb7e2d..9a8a3d1a8 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 16c1f1b7a..a844af1cb 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 07920b952..c4ea92c9a 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 5a4275a69..6c3eea20f 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 b14e0b758..847a265bc 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 4819ecc75..ef5dcdee5 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 231b5f94a..f21625c6b 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 185683be1..e23f02339 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 9a4e0ae25..e62a7cfaa 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 3a495ca58..b185ae3bc 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 f86ac60b4..6810816f1 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 0a6571ccc..3d12bf94a 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 000000000..b2a1bd5e6 --- /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 000000000..651f97f8a --- /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 296b1e591..b7ba46554 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 392111167..8bea53139 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 e6bf7a178..72a2a3146 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 0292a05a1..f87b87409 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 1f8a31eac..0444d37fb 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 1a67702fd..9d6d36d67 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 b0bd13caa..6d0060853 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 7c95bf0ae..03c77a441 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 b1d81fded..000000000 --- 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 bfde9a4ca..8caf5ada5 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 4c0ad19fe..48752f072 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 e9d9f7c07..4b42eb6ae 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 f64f3c765..3ad1a3371 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 d923b7527..e6fda9910 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 20b2396ac..af8e66d7c 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 bef008fd6..4dba59480 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 7566be30d..66abf0ce2 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 68db4bfee4f3cf7611c34fd3fe7826651b84b3b6 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Fri, 16 Jul 2021 12:15:04 -0400 Subject: [PATCH 005/145] chore: Allow MessageListView background color to be customized via theme This commit adds the MessageListViewTheme and MessageListViewTheme data classes. The customization is enabled in message_list_view.dart via the addition of a ColoredBox widget that looks up the widget tree for the background color set by MessageListViewThemeData --- .../lib/src/message_list_view.dart | 365 +++++++++--------- .../lib/src/stream_chat_theme.dart | 122 +++++- 2 files changed, 305 insertions(+), 182 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 822a2f5a7..fee3c8eeb 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -386,203 +386,206 @@ class _MessageListViewState extends State { 1 // parent message ; - return Stack( - alignment: Alignment.center, - children: [ - ConnectionStatusBuilder( - statusBuilder: (context, status) { - var statusString = ''; - var showStatus = true; - switch (status) { - case ConnectionStatus.connected: - statusString = 'Connected'; - showStatus = false; - break; - case ConnectionStatus.connecting: - statusString = 'Reconnecting...'; - break; - case ConnectionStatus.disconnected: - statusString = 'Disconnected'; - break; - } + return ColoredBox( + color: MessageListViewTheme.of(context).backgroundColor!, + child: Stack( + alignment: Alignment.center, + children: [ + ConnectionStatusBuilder( + statusBuilder: (context, status) { + var statusString = ''; + var showStatus = true; + switch (status) { + case ConnectionStatus.connected: + statusString = 'Connected'; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = 'Reconnecting...'; + break; + case ConnectionStatus.disconnected: + statusString = 'Disconnected'; + break; + } - return InfoTile( - showMessage: widget.showConnectionStateTile && showStatus, - tileAnchor: Alignment.topCenter, - childAnchor: Alignment.topCenter, - message: statusString, - child: LazyLoadScrollView( - onPageScrollStart: () { - FocusScope.of(context).unfocus(); - }, - onStartOfPage: () async { - _inBetweenList = false; - if (!_upToDate) { - _topPaginationActive = false; - _bottomPaginationActive = true; + return InfoTile( + showMessage: widget.showConnectionStateTile && showStatus, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: statusString, + child: LazyLoadScrollView( + onPageScrollStart: () { + FocusScope.of(context).unfocus(); + }, + onStartOfPage: () async { + _inBetweenList = false; + if (!_upToDate) { + _topPaginationActive = false; + _bottomPaginationActive = true; + return _paginateData( + streamChannel, + QueryDirection.bottom, + ); + } + }, + onEndOfPage: () async { + _inBetweenList = false; + _topPaginationActive = true; + _bottomPaginationActive = false; return _paginateData( streamChannel, - QueryDirection.bottom, + QueryDirection.top, ); - } - }, - onEndOfPage: () async { - _inBetweenList = false; - _topPaginationActive = true; - _bottomPaginationActive = false; - return _paginateData( - streamChannel, - QueryDirection.top, - ); - }, - onInBetweenOfPage: () { - _inBetweenList = true; - }, - child: ScrollablePositionedList.separated( - key: ValueKey(initialIndex! + initialAlignment!), - itemPositionsListener: _itemPositionListener, - initialScrollIndex: initialIndex ?? 0, - initialAlignment: initialAlignment ?? 0, - physics: widget.scrollPhysics, - itemScrollController: _scrollController, - reverse: true, - addAutomaticKeepAlives: false, - itemCount: itemCount, - - // Item Count -> 8 (1 parent, 2 header+footer, 2 top+bottom, 3 messages) - // eg: |Type| rev(|Index(item)|) rev(|Index(separator)|) |Index(item)| |Index(separator)| - // ParentMessage -> 7 (count-1) - // Separator(ThreadSeparator) -> 6 (count-2) - // Header -> 6 (count-2) - // Separator(Header -> 8??T -> 0||52) -> 5 (count-3) - // TopLoader -> 5 (count-3) - // Separator(0) -> 4 (count-4) - // Message -> 4 (count-4) - // Separator(2||8) -> 3 (count-5) - // Message -> 3 (count-5) - // Separator(2||8) -> 2 (count-6) - // Message -> 2 (count-6) - // Separator(0) -> 1 (count-7) - // BottomLoader -> 1 (count-7) - // Separator(Footer -> 8??30) -> 0 (count-8) - // Footer -> 0 (count-8) - - separatorBuilder: (context, i) { - if (i == itemCount - 2) { - if (widget.parentMessage == null) { - return const Offstage(); + }, + onInBetweenOfPage: () { + _inBetweenList = true; + }, + child: ScrollablePositionedList.separated( + key: ValueKey(initialIndex! + initialAlignment!), + itemPositionsListener: _itemPositionListener, + initialScrollIndex: initialIndex ?? 0, + initialAlignment: initialAlignment ?? 0, + physics: widget.scrollPhysics, + itemScrollController: _scrollController, + reverse: true, + addAutomaticKeepAlives: false, + itemCount: itemCount, + + // Item Count -> 8 (1 parent, 2 header+footer, 2 top+bottom, 3 messages) + // eg: |Type| rev(|Index(item)|) rev(|Index(separator)|) |Index(item)| |Index(separator)| + // ParentMessage -> 7 (count-1) + // Separator(ThreadSeparator) -> 6 (count-2) + // Header -> 6 (count-2) + // Separator(Header -> 8??T -> 0||52) -> 5 (count-3) + // TopLoader -> 5 (count-3) + // Separator(0) -> 4 (count-4) + // Message -> 4 (count-4) + // Separator(2||8) -> 3 (count-5) + // Message -> 3 (count-5) + // Separator(2||8) -> 2 (count-6) + // Message -> 2 (count-6) + // Separator(0) -> 1 (count-7) + // BottomLoader -> 1 (count-7) + // Separator(Footer -> 8??30) -> 0 (count-8) + // Footer -> 0 (count-8) + + separatorBuilder: (context, i) { + if (i == itemCount - 2) { + if (widget.parentMessage == null) { + return const Offstage(); + } + return _buildThreadSeparator(); } - return _buildThreadSeparator(); - } - if (i == itemCount - 3) { - if (widget.headerBuilder == null) { - if (_isThreadConversation) return const Offstage(); - return const SizedBox(height: 52); + if (i == itemCount - 3) { + if (widget.headerBuilder == null) { + if (_isThreadConversation) return const Offstage(); + return const SizedBox(height: 52); + } + return const SizedBox(height: 8); } - return const SizedBox(height: 8); - } - if (i == 0) { - if (widget.footerBuilder == null) { - return const SizedBox(height: 30); + if (i == 0) { + if (widget.footerBuilder == null) { + return const SizedBox(height: 30); + } + return const SizedBox(height: 8); } - return const SizedBox(height: 8); - } - if (i == 1 || i == itemCount - 4) return const Offstage(); - - final message = messages[i - 1]; - final nextMessage = messages[i - 2]; - if (!Jiffy(message.createdAt.toLocal()).isSame( - nextMessage.createdAt.toLocal(), - Units.DAY, - )) { - final divider = widget.dateDividerBuilder != null - ? widget.dateDividerBuilder!( - nextMessage.createdAt.toLocal(), - ) - : DateDivider( - dateTime: nextMessage.createdAt.toLocal(), - ); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: divider, + if (i == 1 || i == itemCount - 4) return const Offstage(); + + final message = messages[i - 1]; + final nextMessage = messages[i - 2]; + if (!Jiffy(message.createdAt.toLocal()).isSame( + nextMessage.createdAt.toLocal(), + Units.DAY, + )) { + final divider = widget.dateDividerBuilder != null + ? widget.dateDividerBuilder!( + nextMessage.createdAt.toLocal(), + ) + : DateDivider( + dateTime: nextMessage.createdAt.toLocal(), + ); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: divider, + ); + } + final timeDiff = + Jiffy(nextMessage.createdAt.toLocal()).diff( + message.createdAt.toLocal(), + Units.MINUTE, ); - } - final timeDiff = - Jiffy(nextMessage.createdAt.toLocal()).diff( - message.createdAt.toLocal(), - Units.MINUTE, - ); - - final isNextUserSame = - message.user!.id == nextMessage.user?.id; - final isThread = message.replyCount! > 0; - final isDeleted = message.isDeleted; - if (timeDiff >= 1 || - !isNextUserSame || - isThread || - isDeleted) { - return const SizedBox(height: 8); - } - return const SizedBox(height: 2); - }, - itemBuilder: (context, i) { - if (i == itemCount - 1) { - if (widget.parentMessage == null) return const Offstage(); - return buildParentMessage(widget.parentMessage!); - } - if (i == itemCount - 2) { - return widget.headerBuilder?.call(context) ?? - const Offstage(); - } - - if (i == itemCount - 3) { - return _buildLoadingIndicator( - streamChannel!, - QueryDirection.top, - ); - } + final isNextUserSame = + message.user!.id == nextMessage.user?.id; + final isThread = message.replyCount! > 0; + final isDeleted = message.isDeleted; + if (timeDiff >= 1 || + !isNextUserSame || + isThread || + isDeleted) { + return const SizedBox(height: 8); + } + return const SizedBox(height: 2); + }, + itemBuilder: (context, i) { + if (i == itemCount - 1) { + if (widget.parentMessage == null) return const Offstage(); + return buildParentMessage(widget.parentMessage!); + } - if (i == 1) { - return _buildLoadingIndicator( - streamChannel!, - QueryDirection.bottom, - ); - } + if (i == itemCount - 2) { + return widget.headerBuilder?.call(context) ?? + const Offstage(); + } - if (i == 0) { - return widget.footerBuilder?.call(context) ?? - const Offstage(); - } + if (i == itemCount - 3) { + return _buildLoadingIndicator( + streamChannel!, + QueryDirection.top, + ); + } - const bottomMessageIndex = 2; // 1 -> loader // 0 -> footer + if (i == 1) { + return _buildLoadingIndicator( + streamChannel!, + QueryDirection.bottom, + ); + } - final message = messages[i - 2]; - Widget messageWidget; + if (i == 0) { + return widget.footerBuilder?.call(context) ?? + const Offstage(); + } - if (i == bottomMessageIndex) { - messageWidget = _buildBottomMessage( - context, - message, - messages, - streamChannel!, - i - 2, - ); - } else { - messageWidget = buildMessage(message, messages, i - 2); - } - return messageWidget; - }, + const bottomMessageIndex = 2; // 1 -> loader // 0 -> footer + + final message = messages[i - 2]; + Widget messageWidget; + + if (i == bottomMessageIndex) { + messageWidget = _buildBottomMessage( + context, + message, + messages, + streamChannel!, + i - 2, + ); + } else { + messageWidget = buildMessage(message, messages, i - 2); + } + return messageWidget; + }, + ), ), - ), - ); - }, - ), - if (widget.showScrollToBottom) _buildScrollToBottom(), - if (widget.showFloatingDateDivider) - _buildFloatingDateDivider(itemCount), - ], + ); + }, + ), + if (widget.showScrollToBottom) _buildScrollToBottom(), + if (widget.showFloatingDateDivider) + _buildFloatingDateDivider(itemCount), + ], + ), ); } diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index c62e92ecd..6bf722dc1 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -60,6 +60,7 @@ class StreamChatThemeData { List? reactionIcons, GalleryHeaderThemeData? imageHeaderTheme, GalleryFooterThemeData? imageFooterTheme, + MessageListViewThemeData? messageListViewTheme, }) { brightness ??= colorTheme?.brightness ?? Brightness.light; final isDark = brightness == Brightness.dark; @@ -83,6 +84,7 @@ class StreamChatThemeData { reactionIcons: reactionIcons, galleryHeaderTheme: imageHeaderTheme, galleryFooterTheme: imageFooterTheme, + messageListViewTheme: messageListViewTheme, ); return defaultData.merge(customizedData); @@ -111,6 +113,7 @@ class StreamChatThemeData { required this.reactionIcons, required this.galleryHeaderTheme, required this.galleryFooterTheme, + required this.messageListViewTheme, }); /// Create a theme from a Material [Theme] @@ -166,6 +169,9 @@ class StreamChatThemeData { /// Assets used for rendering reactions final List reactionIcons; + /// + final MessageListViewThemeData messageListViewTheme; + /// Creates a copy of [StreamChatThemeData] with specified attributes /// overridden. StreamChatThemeData copyWith({ @@ -182,6 +188,7 @@ class StreamChatThemeData { List? reactionIcons, GalleryHeaderThemeData? galleryHeaderTheme, GalleryFooterThemeData? galleryFooterTheme, + MessageListViewThemeData? messageListViewTheme, }) => StreamChatThemeData.raw( channelListHeaderTheme: @@ -199,6 +206,7 @@ class StreamChatThemeData { reactionIcons: reactionIcons ?? this.reactionIcons, galleryHeaderTheme: galleryHeaderTheme ?? this.galleryHeaderTheme, galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme, + messageListViewTheme: messageListViewTheme ?? this.messageListViewTheme, ); /// Merge themes @@ -219,6 +227,8 @@ class StreamChatThemeData { reactionIcons: other.reactionIcons, galleryHeaderTheme: galleryHeaderTheme.merge(other.galleryHeaderTheme), galleryFooterTheme: galleryFooterTheme.merge(other.galleryFooterTheme), + messageListViewTheme: + messageListViewTheme.merge(other.messageListViewTheme), ); } @@ -438,6 +448,9 @@ class StreamChatThemeData { bottomSheetPhotosTextStyle: textTheme.headlineBold, bottomSheetCloseIconColor: colorTheme.textHighEmphasis, ), + messageListViewTheme: MessageListViewThemeData( + backgroundColor: colorTheme.barsBg, + ), ); } } @@ -1651,7 +1664,7 @@ class GalleryFooterThemeData with Diagnosticable { a.bottomSheetCloseIconColor, b.bottomSheetCloseIconColor, t), ); - /// Merges one [GalleryFooterThemeData] with the another + /// Merges one [GalleryFooterThemeData] with another. GalleryFooterThemeData merge(GalleryFooterThemeData? other) { if (other == null) return this; return copyWith( @@ -1708,3 +1721,110 @@ class GalleryFooterThemeData with Diagnosticable { 'bottomSheetCloseIconColor', bottomSheetCloseIconColor)); } } + +/// Overrides the default style of [MessageListView] descendants. +/// +/// See also: +/// +/// * [MessageListViewThemeData], which is used to configure this theme. +class MessageListViewTheme extends InheritedTheme { + /// Creates a [MessageListViewTheme]. + /// + /// The [data] parameter must not be null. + const MessageListViewTheme({ + Key? key, + required this.data, + required Widget child, + }) : super(key: key, child: child); + + /// The configuration of this theme. + final MessageListViewThemeData data; + + /// The closest instance of this class that encloses the given context. + /// + /// If there is no enclosing [MessageListViewTheme] widget, then + /// [StreamChatThemeData.messageListViewTheme] is used. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// MessageListViewTheme theme = ImageFooterTheme.of(context); + /// ``` + static MessageListViewThemeData of(BuildContext context) { + final messageListViewTheme = + context.dependOnInheritedWidgetOfExactType(); + return messageListViewTheme?.data ?? + StreamChatTheme.of(context).messageListViewTheme; + } + + @override + Widget wrap(BuildContext context, Widget child) => + MessageListViewTheme(data: data, child: child); + + @override + bool updateShouldNotify(MessageListViewTheme oldWidget) => + data != oldWidget.data; +} + +/// A style that overrides the default appearance of [MessageListView]s when +/// used with [MessageListViewTheme] or with the overall [StreamChatTheme]'s +/// [StreamChatThemeData.messageListViewTheme]. +/// +/// See also: +/// +/// * [MessageListViewTheme], the theme which is configured with this class. +/// * [StreamChatThemeData.messageListViewTheme], which can be used to override +/// the default style for [MessageListView]s below the overall +/// [StreamChatTheme]. +class MessageListViewThemeData with Diagnosticable { + /// Creates a [MessageListViewThemeData]. + const MessageListViewThemeData({ + required this.backgroundColor, + }); + + /// The color of the [MessageListView] background. + final Color? backgroundColor; + + /// Copies this [MessageListViewThemeData] to another. + MessageListViewThemeData copyWith({ + Color? backgroundColor, + }) => + MessageListViewThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + ); + + /// Linearly interpolate between two [MessageListView] themes. + /// + /// All the properties must be non-null. + MessageListViewThemeData lerp( + MessageListViewThemeData a, + MessageListViewThemeData b, + double t, + ) => + MessageListViewThemeData( + backgroundColor: Color.lerp(a.backgroundColor, a.backgroundColor, t)); + + /// Merges one [MessageListViewThemeData] with another. + MessageListViewThemeData merge(MessageListViewThemeData? other) { + if (other == null) return this; + return copyWith( + backgroundColor: other.backgroundColor, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is MessageListViewThemeData && + runtimeType == other.runtimeType && + backgroundColor == other.backgroundColor; + + @override + int get hashCode => backgroundColor.hashCode; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + } +} From 0eb350b756119fc16bc340586099d6a965c0bdbb Mon Sep 17 00:00:00 2001 From: groovinchip Date: Fri, 16 Jul 2021 15:00:12 -0400 Subject: [PATCH 006/145] test: remove a duplicated test in gallery_header_theme_test.dart --- .../test/src/gallery_header_theme_test.dart | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/gallery_header_theme_test.dart b/packages/stream_chat_flutter/test/src/gallery_header_theme_test.dart index cbcc07a6c..aefbb3197 100644 --- a/packages/stream_chat_flutter/test/src/gallery_header_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/gallery_header_theme_test.dart @@ -47,13 +47,6 @@ void main() { _galleryHeaderThemeDataDarkControl); }); - test('Merging dark and light themes results in a dark theme', () { - expect( - _galleryHeaderThemeDataDarkControl - .merge(_galleryHeaderThemeDataControl), - _galleryHeaderThemeDataControl); - }); - testWidgets( 'Passing no GalleryHeaderThemeData returns default light theme values', (WidgetTester tester) async { From be804b211cd18beb9800f99509dfb308984a5c54 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Fri, 16 Jul 2021 15:03:14 -0400 Subject: [PATCH 007/145] un-require backgroundColor for MessageListViewThemeData class per conventions --- packages/stream_chat_flutter/lib/src/stream_chat_theme.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index 6bf722dc1..51bbede81 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -1779,7 +1779,7 @@ class MessageListViewTheme extends InheritedTheme { class MessageListViewThemeData with Diagnosticable { /// Creates a [MessageListViewThemeData]. const MessageListViewThemeData({ - required this.backgroundColor, + this.backgroundColor, }); /// The color of the [MessageListView] background. From 45d2f5c6b3ec4aea9bd80bad0688e507defe5c24 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Fri, 16 Jul 2021 15:08:26 -0400 Subject: [PATCH 008/145] fix MessageListViewThemeData lerp --- packages/stream_chat_flutter/lib/src/stream_chat_theme.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index 51bbede81..ee941222b 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -1802,7 +1802,7 @@ class MessageListViewThemeData with Diagnosticable { double t, ) => MessageListViewThemeData( - backgroundColor: Color.lerp(a.backgroundColor, a.backgroundColor, t)); + backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t)); /// Merges one [MessageListViewThemeData] with another. MessageListViewThemeData merge(MessageListViewThemeData? other) { From c427b1201bb09f049ceacaa42b440a76a17daf31 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Fri, 16 Jul 2021 19:24:08 -0400 Subject: [PATCH 009/145] test: add tests for MessageListViewThemeData --- .../src/message_list_view_theme_test.dart | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart diff --git a/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart new file mode 100644 index 000000000..651e28af4 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'mocks.dart'; + +class MockStreamChatClient extends Mock implements StreamChatClient {} + +void main() { + test('MessageListViewThemeData copyWith, ==, hashCode basics', () { + expect(const MessageListViewThemeData(), + const MessageListViewThemeData().copyWith()); + expect(const MessageListViewThemeData().hashCode, + const MessageListViewThemeData().copyWith().hashCode); + }); + + test( + '''Light MessageListViewThemeData lerps completely to dark MessageListViewThemeData''', + () { + expect( + const MessageListViewThemeData().lerp(_messageListViewThemeDataControl, + _messageListViewThemeDataControlDark, 1), + _messageListViewThemeDataControlDark); + }); + + test( + '''Light MessageListViewThemeData lerps halfway to dark MessageListViewThemeData''', + () { + expect( + const MessageListViewThemeData().lerp(_messageListViewThemeDataControl, + _messageListViewThemeDataControlDark, 0.5), + _messageListViewThemeDataControlHalfLerp); + }); + + test( + '''Dark MessageListViewThemeData lerps completely to light MessageListViewThemeData''', + () { + expect( + const MessageListViewThemeData().lerp( + _messageListViewThemeDataControlDark, + _messageListViewThemeDataControl, + 1), + _messageListViewThemeDataControl); + }); + + test('Merging dark and light themes results in a dark theme', () { + expect( + _messageListViewThemeDataControl + .merge(_messageListViewThemeDataControlDark), + _messageListViewThemeDataControlDark); + }); + + testWidgets( + 'Passing no MessageListViewThemeData returns default light theme values', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockStreamChatClient(), + child: child, + ), + home: Builder( + builder: (BuildContext context) { + _context = context; + return Scaffold( + body: StreamChannel( + channel: MockChannel(), + child: const MessageListView(), + ), + ); + }, + ), + ), + ); + + final messageListViewTheme = MessageListViewTheme.of(_context); + expect(messageListViewTheme.backgroundColor, + _messageListViewThemeDataControl.backgroundColor); + }); + + testWidgets( + 'Passing no MessageListViewThemeData returns default dark theme values', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockStreamChatClient(), + streamChatThemeData: StreamChatThemeData.dark(), + child: child, + ), + home: Builder( + builder: (BuildContext context) { + _context = context; + return Scaffold( + body: StreamChannel( + channel: MockChannel(), + child: const MessageListView(), + ), + ); + }, + ), + ), + ); + + final messageListViewTheme = MessageListViewTheme.of(_context); + expect(messageListViewTheme.backgroundColor, + _messageListViewThemeDataControlDark.backgroundColor); + }); + + // default dark theme values +} + +final _messageListViewThemeDataControl = MessageListViewThemeData( + backgroundColor: ColorTheme.light().barsBg, +); + +const _messageListViewThemeDataControlHalfLerp = MessageListViewThemeData( + backgroundColor: Color(0xff87898b), +); + +final _messageListViewThemeDataControlDark = MessageListViewThemeData( + backgroundColor: ColorTheme.dark().barsBg, +); From 9fbbe976baa748f7ae3d9564bb8fd6c7a914ca3b Mon Sep 17 00:00:00 2001 From: groovinchip Date: Fri, 16 Jul 2021 19:41:32 -0400 Subject: [PATCH 010/145] feat: add macos support to stream_chat_flutter example app --- .../example/macos/.gitignore | 6 + .../macos/Flutter/Flutter-Debug.xcconfig | 2 + .../macos/Flutter/Flutter-Release.xcconfig | 2 + .../macos/Runner.xcodeproj/project.pbxproj | 635 ++++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 89 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../example/macos/Runner/AppDelegate.swift | 9 + .../AppIcon.appiconset/Contents.json | 68 ++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 46993 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 3276 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 1429 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 5933 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1243 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 14800 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 1874 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 339 ++++++++++ .../macos/Runner/Configs/AppInfo.xcconfig | 14 + .../macos/Runner/Configs/Debug.xcconfig | 2 + .../macos/Runner/Configs/Release.xcconfig | 2 + .../macos/Runner/Configs/Warnings.xcconfig | 13 + .../macos/Runner/DebugProfile.entitlements | 14 + .../example/macos/Runner/Info.plist | 32 + .../macos/Runner/MainFlutterWindow.swift | 15 + .../example/macos/Runner/Release.entitlements | 8 + .../example/test/widget_test.dart | 30 + 27 files changed, 1306 insertions(+) create mode 100644 packages/stream_chat_flutter/example/macos/.gitignore create mode 100644 packages/stream_chat_flutter/example/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 packages/stream_chat_flutter/example/macos/Flutter/Flutter-Release.xcconfig create mode 100644 packages/stream_chat_flutter/example/macos/Runner.xcodeproj/project.pbxproj create mode 100644 packages/stream_chat_flutter/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/stream_chat_flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/stream_chat_flutter/example/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/stream_chat_flutter/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/stream_chat_flutter/example/macos/Runner/AppDelegate.swift create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Configs/Debug.xcconfig create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Configs/Release.xcconfig create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Configs/Warnings.xcconfig create mode 100644 packages/stream_chat_flutter/example/macos/Runner/DebugProfile.entitlements create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Info.plist create mode 100644 packages/stream_chat_flutter/example/macos/Runner/MainFlutterWindow.swift create mode 100644 packages/stream_chat_flutter/example/macos/Runner/Release.entitlements create mode 100644 packages/stream_chat_flutter/example/test/widget_test.dart diff --git a/packages/stream_chat_flutter/example/macos/.gitignore b/packages/stream_chat_flutter/example/macos/.gitignore new file mode 100644 index 000000000..d2fd37723 --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/.gitignore @@ -0,0 +1,6 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/xcuserdata/ diff --git a/packages/stream_chat_flutter/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/stream_chat_flutter/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 000000000..4b81f9b2d --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/stream_chat_flutter/example/macos/Flutter/Flutter-Release.xcconfig b/packages/stream_chat_flutter/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 000000000..5caa9d157 --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/project.pbxproj b/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..f8c105bbd --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,635 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 7A465D4E5940248C04D2D4E3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5ED1F4FA50EB1433A201473C /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2BCA7399119839DE435DACD6 /* 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 = ""; }; + 2C2B248A2BB89C8B353A7D81 /* 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 = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5ED1F4FA50EB1433A201473C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + C4CD72858CD59598795BB48E /* 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 */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7A465D4E5940248C04D2D4E3 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 35E4E72D48C70FBCFEDFB30C /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 35E4E72D48C70FBCFEDFB30C /* Pods */ = { + isa = PBXGroup; + children = ( + 2C2B248A2BB89C8B353A7D81 /* Pods-Runner.debug.xcconfig */, + C4CD72858CD59598795BB48E /* Pods-Runner.release.xcconfig */, + 2BCA7399119839DE435DACD6 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5ED1F4FA50EB1433A201473C /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 72C2655B408A295073A1CEB6 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + F8C1BBEE8C9F4830ECBA88A2 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 0930; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 72C2655B408A295073A1CEB6 /* [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; + }; + F8C1BBEE8C9F4830ECBA88A2 /* [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; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..ae8ff59d9 --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/stream_chat_flutter/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/stream_chat_flutter/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/stream_chat_flutter/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/stream_chat_flutter/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/stream_chat_flutter/example/macos/Runner/AppDelegate.swift b/packages/stream_chat_flutter/example/macos/Runner/AppDelegate.swift new file mode 100644 index 000000000..d53ef6437 --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..a2ec33f19 --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..3c4935a7ca84f0976aca34b7f2895d65fb94d1ea GIT binary patch literal 46993 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 literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..ed4cc16421680a50164ba74381b4b35ceaa0ccfc GIT binary patch literal 3276 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 N;OXk;vd$@?2>?>Ex^Vyi literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000000000000000000000000000000000000..bcbf36df2f2aaaa0a63c7dabc94e600184229d0d GIT binary patch literal 5933 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 f$KZHO*s&vt|9^w-6P?|#0pRK8NSwWJ?9znhg z3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fg!ItFh?!xdN1Q+aGJ{c&& zS>O>_%)r1c48n{Iv*t(u1=&kHeO=ifbFy+6aSK)V_AxLppYn8Z42d|rc6w}vOsL55 z`t&mC&y2@JTEyg!eDiFX^k#CC!jq%>erB=yHqUP0XcDOTw6ko}L zX;EmMrq(fKk*eygEuA616;0)>@A{TK|55PV@70 z$OfzS*(VJxQev3J?yY?O=ul(v`fp}?u9z`JK3ugibK>)DyCwImZOF4d{xK%%Ks1*} zv$oa)9anR%lXIBUqYnhLmT>VOzHfNP?ZwJNZ!5$s9M08RynIvaXw>@G^T9@r9^KH1 zVy??F&uuk)bH9Y4pQY!hP58i_H6 znl-NcuCpLV6ZWU;4C zu@9exF&OZi`Bovq_m%T+WhU2kvkz@^_LpycBvqm3bMpLw8X-Or5sL>0AKE1$(k_L=_Zc=CUq#=x1-QZf)G7nHu@fmsQ1eN_N3+nTEz`4HI4Z6uVlE zJH+X&det8JU?tO?upcM4Z=cV!JV;yF>FfL5Q$M|W_2Z!P`S=}Wzp|_1^#d%e?_H`> zV@%vA$+bFVqhw9`U;TfP|5|PD{||OiYdor8P*i??|NJcb%kzT_73*7WE?Ua5hAnR2 z=7WE=PhTlJ#ZeRznjTUb;`E(wkMZrj4e|Hilz-mK>9cZHQY**5TUPw~u}k;u73KI}xAx!0m-)GVia|x^d3p~s_9gh83jA&Ra<8rM%`>U3x69t&NzbwWY}7Ar?)FK#IZ0z|d0H0EkRO w3{9;}4Xg|ebq&m|3=9_N6z8I7$jwj5OsmAL;bP(Gi$Dzwp00i_>zopr02+f8CIA2c literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/stream_chat_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000000000000000000000000000000000000..e71a726136a47ed24125c7efc79d68a4a01961b4 GIT binary patch literal 14800 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/stream_chat_flutter/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/stream_chat_flutter/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 000000000..cf9be60ca --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. diff --git a/packages/stream_chat_flutter/example/macos/Runner/Configs/Debug.xcconfig b/packages/stream_chat_flutter/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 000000000..36b0fd946 --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/stream_chat_flutter/example/macos/Runner/Configs/Release.xcconfig b/packages/stream_chat_flutter/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 000000000..dff4f4956 --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/stream_chat_flutter/example/macos/Runner/Configs/Warnings.xcconfig b/packages/stream_chat_flutter/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 000000000..42bcbf478 --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/stream_chat_flutter/example/macos/Runner/DebugProfile.entitlements b/packages/stream_chat_flutter/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 000000000..e585d0e0f --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/packages/stream_chat_flutter/example/macos/Runner/Info.plist b/packages/stream_chat_flutter/example/macos/Runner/Info.plist new file mode 100644 index 000000000..4789daa6a --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/stream_chat_flutter/example/macos/Runner/MainFlutterWindow.swift b/packages/stream_chat_flutter/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 000000000..2722837ec --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/stream_chat_flutter/example/macos/Runner/Release.entitlements b/packages/stream_chat_flutter/example/macos/Runner/Release.entitlements new file mode 100644 index 000000000..852fa1a47 --- /dev/null +++ b/packages/stream_chat_flutter/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/packages/stream_chat_flutter/example/test/widget_test.dart b/packages/stream_chat_flutter/example/test/widget_test.dart new file mode 100644 index 000000000..747db1da3 --- /dev/null +++ b/packages/stream_chat_flutter/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} From c803b133a0d03e8053939ca01d260bfd08f7c65a Mon Sep 17 00:00:00 2001 From: groovinchip Date: Sun, 18 Jul 2021 12:31:21 -0400 Subject: [PATCH 011/145] chore: allow ChannelListView background color to be themed via StreamChatThemeData This commit adds the ChannelListViewTheme and ChannelListViewThemeData classes, and updates ChannelListView to set its background color accordingly --- .../lib/src/channel_list_view.dart | 2 +- .../lib/src/stream_chat_theme.dart | 128 +++++++++++++++++- 2 files changed, 126 insertions(+), 4 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 83f196dfb..c6a045006 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -573,7 +573,7 @@ class _ChannelListViewState extends State { ], child: DecoratedBox( decoration: BoxDecoration( - color: chatThemeData.colorTheme.appBg, + color: chatThemeData.channelListViewTheme.backgroundColor, ), child: widget.channelPreviewBuilder?.call(context, channel) ?? ChannelPreview( diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index ee941222b..417878328 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -61,6 +61,7 @@ class StreamChatThemeData { GalleryHeaderThemeData? imageHeaderTheme, GalleryFooterThemeData? imageFooterTheme, MessageListViewThemeData? messageListViewTheme, + ChannelListViewThemeData? channelListViewTheme, }) { brightness ??= colorTheme?.brightness ?? Brightness.light; final isDark = brightness == Brightness.dark; @@ -85,6 +86,7 @@ class StreamChatThemeData { galleryHeaderTheme: imageHeaderTheme, galleryFooterTheme: imageFooterTheme, messageListViewTheme: messageListViewTheme, + channelListViewTheme: channelListViewTheme, ); return defaultData.merge(customizedData); @@ -114,6 +116,7 @@ class StreamChatThemeData { required this.galleryHeaderTheme, required this.galleryFooterTheme, required this.messageListViewTheme, + required this.channelListViewTheme, }); /// Create a theme from a Material [Theme] @@ -169,9 +172,12 @@ class StreamChatThemeData { /// Assets used for rendering reactions final List reactionIcons; - /// + /// Theme configuration for the [MessageListView] widget. final MessageListViewThemeData messageListViewTheme; + /// Theme configuration for the [ChannelListView] widget. + final ChannelListViewThemeData channelListViewTheme; + /// Creates a copy of [StreamChatThemeData] with specified attributes /// overridden. StreamChatThemeData copyWith({ @@ -189,6 +195,7 @@ class StreamChatThemeData { GalleryHeaderThemeData? galleryHeaderTheme, GalleryFooterThemeData? galleryFooterTheme, MessageListViewThemeData? messageListViewTheme, + ChannelListViewThemeData? channelListViewTheme, }) => StreamChatThemeData.raw( channelListHeaderTheme: @@ -207,6 +214,7 @@ class StreamChatThemeData { galleryHeaderTheme: galleryHeaderTheme ?? this.galleryHeaderTheme, galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme, messageListViewTheme: messageListViewTheme ?? this.messageListViewTheme, + channelListViewTheme: channelListViewTheme ?? this.channelListViewTheme, ); /// Merge themes @@ -229,6 +237,8 @@ class StreamChatThemeData { galleryFooterTheme: galleryFooterTheme.merge(other.galleryFooterTheme), messageListViewTheme: messageListViewTheme.merge(other.messageListViewTheme), + channelListViewTheme: + channelListViewTheme.merge(other.channelListViewTheme), ); } @@ -451,6 +461,9 @@ class StreamChatThemeData { messageListViewTheme: MessageListViewThemeData( backgroundColor: colorTheme.barsBg, ), + channelListViewTheme: ChannelListViewThemeData( + backgroundColor: colorTheme.appBg, + ), ); } } @@ -1748,7 +1761,7 @@ class MessageListViewTheme extends InheritedTheme { /// Typical usage is as follows: /// /// ```dart - /// MessageListViewTheme theme = ImageFooterTheme.of(context); + /// MessageListViewTheme theme = MessageListViewTheme.of(context); /// ``` static MessageListViewThemeData of(BuildContext context) { final messageListViewTheme = @@ -1802,7 +1815,8 @@ class MessageListViewThemeData with Diagnosticable { double t, ) => MessageListViewThemeData( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t)); + backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + ); /// Merges one [MessageListViewThemeData] with another. MessageListViewThemeData merge(MessageListViewThemeData? other) { @@ -1828,3 +1842,111 @@ class MessageListViewThemeData with Diagnosticable { properties.add(ColorProperty('backgroundColor', backgroundColor)); } } + +/// Overrides the default style of [ChannelListView] descendants. +/// +/// See also: +/// +/// * [ChannelListViewThemeData], which is used to configure this theme. +class ChannelListViewTheme extends InheritedTheme { + /// Creates a [ChannelListViewTheme]. + /// + /// The [data] parameter must not be null. + const ChannelListViewTheme({ + Key? key, + required this.data, + required Widget child, + }) : super(key: key, child: child); + + /// The configuration of this theme. + final ChannelListViewThemeData data; + + /// The closest instance of this class that encloses the given context. + /// + /// If there is no enclosing [ChannelListViewTheme] widget, then + /// [StreamChatThemeData.channelListViewTheme] is used. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// ChannelListViewTheme theme = ChannelListViewTheme.of(context); + /// ``` + static ChannelListViewThemeData of(BuildContext context) { + final channelListViewTheme = + context.dependOnInheritedWidgetOfExactType(); + return channelListViewTheme?.data ?? + StreamChatTheme.of(context).channelListViewTheme; + } + + @override + Widget wrap(BuildContext context, Widget child) => + ChannelListViewTheme(data: data, child: child); + + @override + bool updateShouldNotify(ChannelListViewTheme oldWidget) => + data != oldWidget.data; +} + +/// A style that overrides the default appearance of [ChannelListView]s when +/// used with [ChannelListViewTheme] or with the overall [StreamChatTheme]'s +/// [StreamChatThemeData.channelListViewTheme]. +/// +/// See also: +/// +/// * [ChannelListViewTheme], the theme which is configured with this class. +/// * [StreamChatThemeData.channelListViewTheme], which can be used to override +/// the default style for [ChannelListView]s below the overall +/// [StreamChatTheme]. +class ChannelListViewThemeData with Diagnosticable { + /// Creates a [ChannelListViewThemeData]. + ChannelListViewThemeData({ + this.backgroundColor, + }); + + /// The color of the [ChannelListView] background. + final Color? backgroundColor; + + /// Copies this [ChannelListViewThemeData] to another. + ChannelListViewThemeData copyWith({ + Color? backgroundColor, + }) => + ChannelListViewThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + ); + + /// Linearly interpolate between two [ChannelListViewThemeData] themes. + /// + /// All the properties must be non-null. + ChannelListViewThemeData lerp( + ChannelListViewThemeData a, + ChannelListViewThemeData b, + double t, + ) => + ChannelListViewThemeData( + backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + ); + + /// Merges one [ChannelListViewThemeData] with another. + ChannelListViewThemeData merge(ChannelListViewThemeData? other) { + if (other == null) return this; + return copyWith( + backgroundColor: other.backgroundColor, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ChannelListViewThemeData && + runtimeType == other.runtimeType && + backgroundColor == other.backgroundColor; + + @override + int get hashCode => backgroundColor.hashCode; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + } +} From eb6d0ad5b053da6419363619b54889603fa62e22 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Sun, 18 Jul 2021 12:35:14 -0400 Subject: [PATCH 012/145] fix: make ChannelListViewThemeData constructor const --- packages/stream_chat_flutter/lib/src/stream_chat_theme.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index 417878328..08d0de0ec 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -1899,7 +1899,7 @@ class ChannelListViewTheme extends InheritedTheme { /// [StreamChatTheme]. class ChannelListViewThemeData with Diagnosticable { /// Creates a [ChannelListViewThemeData]. - ChannelListViewThemeData({ + const ChannelListViewThemeData({ this.backgroundColor, }); From 6bc70ba40ab5dbbd62c1c9e05e8011f9ebf4d1c1 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Sun, 18 Jul 2021 12:45:40 -0400 Subject: [PATCH 013/145] run dartfmt on message_list_view_theme_test.dart --- .../src/message_list_view_theme_test.dart | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart index 651e28af4..9785fd2fb 100644 --- a/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart @@ -81,33 +81,33 @@ void main() { testWidgets( 'Passing no MessageListViewThemeData returns default dark theme values', - (WidgetTester tester) async { - late BuildContext _context; - await tester.pumpWidget( - MaterialApp( - builder: (context, child) => StreamChat( - client: MockStreamChatClient(), - streamChatThemeData: StreamChatThemeData.dark(), - child: child, - ), - home: Builder( - builder: (BuildContext context) { - _context = context; - return Scaffold( - body: StreamChannel( - channel: MockChannel(), - child: const MessageListView(), - ), - ); - }, - ), - ), - ); + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockStreamChatClient(), + streamChatThemeData: StreamChatThemeData.dark(), + child: child, + ), + home: Builder( + builder: (BuildContext context) { + _context = context; + return Scaffold( + body: StreamChannel( + channel: MockChannel(), + child: const MessageListView(), + ), + ); + }, + ), + ), + ); - final messageListViewTheme = MessageListViewTheme.of(_context); - expect(messageListViewTheme.backgroundColor, - _messageListViewThemeDataControlDark.backgroundColor); - }); + final messageListViewTheme = MessageListViewTheme.of(_context); + expect(messageListViewTheme.backgroundColor, + _messageListViewThemeDataControlDark.backgroundColor); + }); // default dark theme values } From f7b61706cb74bf7d96dc05d44d49e32d4133da02 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Sun, 18 Jul 2021 12:47:02 -0400 Subject: [PATCH 014/145] test: add tests for ChannelListViewThemeData --- .../src/channel_list_view_theme_test.dart | 119 ++++++++++++++++++ .../src/message_list_view_theme_test.dart | 2 - 2 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 packages/stream_chat_flutter/test/src/channel_list_view_theme_test.dart diff --git a/packages/stream_chat_flutter/test/src/channel_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/channel_list_view_theme_test.dart new file mode 100644 index 000000000..332f4c226 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/channel_list_view_theme_test.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'mocks.dart'; + +void main() { + test('ChannelListViewThemeData copyWith, ==, hashCode basics', () { + expect(const ChannelListViewThemeData(), + const ChannelListViewThemeData().copyWith()); + }); + + test( + '''List ChannelListViewThemeData lerps completely to dark ChannelListViewThemeData''', + () { + expect( + const ChannelListViewThemeData().lerp(_channelListViewThemeDataControl, + _channelListViewThemeDataControlDark, 1), + _channelListViewThemeDataControlDark); + }); + + test( + '''Light ChannelListViewThemeData lerps halfway to dark ChannelListViewThemeData''', + () { + expect( + const ChannelListViewThemeData().lerp(_channelListViewThemeDataControl, + _channelListViewThemeDataControlDark, 0.5), + _channelListViewThemeDataControlHalfLerp); + }); + + test( + '''Dark ChannelListViewThemeData lerps completely to light ChannelListViewThemeData''', + () { + expect( + const ChannelListViewThemeData().lerp( + _channelListViewThemeDataControlDark, + _channelListViewThemeDataControl, + 1), + _channelListViewThemeDataControl); + }); + + test('Merging dark and light themes results in a dark theme', () { + expect( + _channelListViewThemeDataControl + .merge(_channelListViewThemeDataControlDark), + _channelListViewThemeDataControlDark); + }); + + testWidgets( + 'Passing no ChannelListViewThemeData returns default light theme values', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockClient(), + child: child, + ), + home: Builder( + builder: (BuildContext context) { + _context = context; + return Scaffold( + body: StreamChannel( + channel: MockChannel(), + child: const ChannelListView(), + ), + ); + }, + ), + ), + ); + + final channelListViewTheme = ChannelListViewTheme.of(_context); + expect(channelListViewTheme.backgroundColor, + _channelListViewThemeDataControl.backgroundColor); + }); + + testWidgets( + 'Passing no ChannelListViewThemeData returns default dark theme values', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockClient(), + streamChatThemeData: StreamChatThemeData.dark(), + child: child, + ), + home: Builder( + builder: (BuildContext context) { + _context = context; + return Scaffold( + body: StreamChannel( + channel: MockChannel(), + child: const MessageListView(), + ), + ); + }, + ), + ), + ); + + final channelListViewTheme = ChannelListViewTheme.of(_context); + expect(channelListViewTheme.backgroundColor, + _channelListViewThemeDataControlDark.backgroundColor); + }); +} + +final _channelListViewThemeDataControl = ChannelListViewThemeData( + backgroundColor: ColorTheme.light().appBg, +); + +const _channelListViewThemeDataControlHalfLerp = ChannelListViewThemeData( + backgroundColor: Color(0xff818384), +); + +final _channelListViewThemeDataControlDark = ChannelListViewThemeData( + backgroundColor: ColorTheme.dark().appBg, +); diff --git a/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart index 9785fd2fb..252eb944d 100644 --- a/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart @@ -108,8 +108,6 @@ void main() { expect(messageListViewTheme.backgroundColor, _messageListViewThemeDataControlDark.backgroundColor); }); - - // default dark theme values } final _messageListViewThemeDataControl = MessageListViewThemeData( From df62e8ba6cde939e751cdcb74aff553a02ab2f44 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Jul 2021 10:53:44 +0200 Subject: [PATCH 015/145] update photo_manager package --- 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 590ed055e..75fd6fac1 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -30,7 +30,7 @@ dependencies: lottie: ^1.0.1 meta: ^1.3.0 path_provider: ^2.0.1 - photo_manager: ^1.1.6 + photo_manager: ^1.2.6+1 photo_view: ^0.11.1 rxdart: ^0.27.0 scrollable_positioned_list: ^0.2.0-nullsafety.0 From f50ea0bac49e3d845e248b0bd42b080224a803e1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Jul 2021 12:05:57 +0200 Subject: [PATCH 016/145] add paginationLimit to message list core and ui package --- .../lib/src/message_input.dart | 1 + .../lib/src/message_list_view.dart | 9 ++++++- .../lib/src/message_list_core.dart | 25 ++++++++++++++++--- .../lib/src/stream_channel.dart | 15 ++++++++--- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 5a4f68222..a24cb95b8 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -2309,6 +2309,7 @@ class __PickerWidgetState extends State<_PickerWidget> { return InkWell( onTap: () async { + print('on tap'); PhotoManager.openSetting(); }, child: Container( 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 822a2f5a7..1a20212b9 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -166,11 +166,15 @@ class MessageListView extends StatefulWidget { this.showFloatingDateDivider = true, this.threadSeparatorBuilder, this.messageListController, + this.paginationLimit = 20, }) : super(key: key); /// Function used to build a custom message widget final MessageBuilder? messageBuilder; + /// Limit used during pagination + final int paginationLimit; + /// Function used to build a custom system message widget final SystemMessageBuilder? systemMessageBuilder; @@ -328,6 +332,7 @@ class _MessageListViewState extends State { @override Widget build(BuildContext context) => MessageListCore( + paginationLimit: widget.paginationLimit, messageFilter: widget.messageFilter, loadingBuilder: widget.loadingBuilder ?? (context) => const Center( @@ -640,7 +645,9 @@ class _MessageListViewState extends State { ); Future _paginateData( - StreamChannelState? channel, QueryDirection direction) => + StreamChannelState? channel, + QueryDirection direction, + ) => _messageListController.paginateData!(direction: direction); int? _getTopElementIndex(Iterable values) { 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 dc90cc91c..4acf0ba72 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -71,6 +71,7 @@ class MessageListCore extends StatefulWidget { this.parentMessage, this.messageListController, this.messageFilter, + this.paginationLimit = 20, }) : super(key: key); /// A [MessageListController] allows pagination. @@ -86,6 +87,9 @@ class MessageListCore extends StatefulWidget { /// Function used to build an empty widget final WidgetBuilder emptyBuilder; + /// Limit used to paginate messages + final int paginationLimit; + /// Callback triggered when an error occurs while performing the given /// request. /// @@ -163,13 +167,20 @@ class MessageListCoreState extends State { /// Fetches more messages with updated pagination and updates the widget. /// /// Optionally pass the fetch direction, defaults to [QueryDirection.top] + /// Optionally pass a limit, defaults to 20 Future paginateData({ QueryDirection direction = QueryDirection.top, }) { if (!_isThreadConversation) { - return _streamChannel!.queryMessages(direction: direction); + return _streamChannel!.queryMessages( + direction: direction, + limit: widget.paginationLimit, + ); } else { - return _streamChannel!.getReplies(widget.parentMessage!.id); + return _streamChannel!.getReplies( + widget.parentMessage!.id, + limit: widget.paginationLimit, + ); } } @@ -179,7 +190,10 @@ class MessageListCoreState extends State { if (newStreamChannel != _streamChannel) { if (_streamChannel == null /*only first time*/ && _isThreadConversation) { - newStreamChannel.getReplies(widget.parentMessage!.id); + newStreamChannel.getReplies( + widget.parentMessage!.id, + limit: widget.paginationLimit, + ); } _streamChannel = newStreamChannel; } @@ -197,7 +211,10 @@ class MessageListCoreState extends State { if (widget.parentMessage?.id != widget.parentMessage?.id) { if (_isThreadConversation) { - _streamChannel!.getReplies(widget.parentMessage!.id); + _streamChannel!.getReplies( + widget.parentMessage!.id, + limit: widget.paginationLimit, + ); } } } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart index 1dcf9c3c6..fb7b79f2e 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -147,9 +147,18 @@ class StreamChannelState extends State { } /// Calls [channel.query] updating [queryMessage] stream - Future queryMessages({QueryDirection? direction = QueryDirection.top}) { - if (direction == QueryDirection.top) return _queryTopMessages(); - return _queryBottomMessages(); + Future queryMessages({ + QueryDirection? direction = QueryDirection.top, + int limit = 20, + }) { + if (direction == QueryDirection.top) { + return _queryTopMessages( + limit: limit, + ); + } + return _queryBottomMessages( + limit: limit, + ); } /// Calls [channel.getReplies] updating [queryMessage] stream From 57594c403c09e76305efe676013936e183ea35bc Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Jul 2021 12:10:20 +0200 Subject: [PATCH 017/145] add test --- .../test/message_list_core_test.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index b038411bd..1a8948904 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -151,7 +151,9 @@ void main() { (tester) async { const messageListCoreKey = Key('messageListCore'); final controller = MessageListController(); + final paginationLimit = 10; final messageListCore = MessageListCore( + paginationLimit: paginationLimit, key: messageListCoreKey, messageListBuilder: (_, __) => const Offstage(), loadingBuilder: (BuildContext context) => const Offstage(), @@ -165,10 +167,6 @@ void main() { final mockChannel = MockChannel(); when(() => mockChannel.state.isUpToDate).thenReturn(true); - // when(() => mockChannel.query( - // messagesPagination: any(named: 'messagesPagination'), - // preferOffline: any(named: 'preferOffline'), - // )).thenAnswer((_) => mockChannel.state); final messages = _generateMessages(); when(() => mockChannel.state.messages).thenReturn(messages); when(() => mockChannel.state.messagesStream) @@ -191,7 +189,10 @@ void main() { await coreState.paginateData(); verify(() => mockChannel.query( - messagesPagination: any(named: 'messagesPagination'), + messagesPagination: any( + named: 'messagesPagination', + that: wrapMatcher((it) => it.limit == paginationLimit), + ), preferOffline: any(named: 'preferOffline'), )).called(1); }, From 6c08c5a784f773a047680abf47e50402eeaf5f5d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 19 Jul 2021 12:11:34 +0200 Subject: [PATCH 018/145] add test --- .../stream_chat_flutter_core/test/message_list_core_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index 1a8948904..e61d0abb9 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -151,7 +151,7 @@ void main() { (tester) async { const messageListCoreKey = Key('messageListCore'); final controller = MessageListController(); - final paginationLimit = 10; + const paginationLimit = 10; final messageListCore = MessageListCore( paginationLimit: paginationLimit, key: messageListCoreKey, From 67a36d9c35a34b7ca07460cc2cc04ec37eff8117 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 19 Jul 2021 16:51:49 +0530 Subject: [PATCH 019/145] 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 91d441766..de2433ab3 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 7377ba20f..f57e1ee60 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 287612adb..13ec3b3b9 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 ed8c39f98..03d575aeb 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 6c4ecfeef..e08528002 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 651f97f8a..9b270bd58 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 702cfe4e4..456357cef 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 0444d37fb..de6c5e750 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 6d0060853..8fcf331ce 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 11c80300d..297810b36 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 8caf5ada5..a5eb62dbe 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 66abf0ce2..91f41b643 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 020/145] 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 5ef8d706c..0977f6623 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 021/145] 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 0c224d200..9b64cce72 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 b185ae3bc..5d67ee507 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 2f37d6374..3e0afe755 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 456357cef..e5e6ca480 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 2c250eaa9..ac124b467 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 32e4794bd..440382827 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 3f2107420..59a49ac7a 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 953434de1..3aec707b7 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 f904b3ed829e3cc459ab1a3315e48851ed440a0e Mon Sep 17 00:00:00 2001 From: groovinchip Date: Mon, 19 Jul 2021 09:05:50 -0400 Subject: [PATCH 022/145] remove unused import --- .../test/src/channel_list_view_theme_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/channel_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/channel_list_view_theme_test.dart index 332f4c226..8f948db39 100644 --- a/packages/stream_chat_flutter/test/src/channel_list_view_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_list_view_theme_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + import 'mocks.dart'; void main() { From 423581c9d12cef218c17db4e832ccd755b8ecd61 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Mon, 19 Jul 2021 12:16:48 -0400 Subject: [PATCH 023/145] fix: theme channel_list_view.dart background color in the proper place --- .../lib/src/channel_list_view.dart | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 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 c6a045006..1cec9d5c5 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -231,9 +231,12 @@ class _ChannelListViewState extends State { ); } - return LazyLoadScrollView( - onEndOfPage: () => _channelListController.paginateData!(), - child: child, + return ColoredBox( + color: ChannelListViewTheme.of(context).backgroundColor!, + child: LazyLoadScrollView( + onEndOfPage: () => _channelListController.paginateData!(), + child: child, + ), ); } @@ -571,18 +574,13 @@ class _ChannelListViewState extends State { }, ), ], - child: DecoratedBox( - decoration: BoxDecoration( - color: chatThemeData.channelListViewTheme.backgroundColor, - ), - child: widget.channelPreviewBuilder?.call(context, channel) ?? - ChannelPreview( - onLongPress: widget.onChannelLongPress, - channel: channel, - onImageTap: () => widget.onImageTap?.call(channel), - onTap: (channel) => onTap(channel, widget.channelWidget), - ), - ), + child: widget.channelPreviewBuilder?.call(context, channel) ?? + ChannelPreview( + onLongPress: widget.onChannelLongPress, + channel: channel, + onImageTap: () => widget.onImageTap?.call(channel), + onTap: (channel) => onTap(channel, widget.channelWidget), + ), ), ); } From 4d3e2bce9adc9f4e93e0e08372c82ed1c014cc22 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Mon, 19 Jul 2021 12:19:52 -0400 Subject: [PATCH 024/145] chore: theme user_list_view.dart background color from theme --- .../lib/src/stream_chat_theme.dart | 120 ++++++++++++++++++ .../lib/src/user_list_view.dart | 53 ++++---- 2 files changed, 148 insertions(+), 25 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index 08d0de0ec..d632c10da 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -62,6 +62,7 @@ class StreamChatThemeData { GalleryFooterThemeData? imageFooterTheme, MessageListViewThemeData? messageListViewTheme, ChannelListViewThemeData? channelListViewTheme, + UserListViewThemeData? userListViewTheme, }) { brightness ??= colorTheme?.brightness ?? Brightness.light; final isDark = brightness == Brightness.dark; @@ -87,6 +88,7 @@ class StreamChatThemeData { galleryFooterTheme: imageFooterTheme, messageListViewTheme: messageListViewTheme, channelListViewTheme: channelListViewTheme, + userListViewTheme: userListViewTheme, ); return defaultData.merge(customizedData); @@ -117,6 +119,7 @@ class StreamChatThemeData { required this.galleryFooterTheme, required this.messageListViewTheme, required this.channelListViewTheme, + required this.userListViewTheme, }); /// Create a theme from a Material [Theme] @@ -178,6 +181,9 @@ class StreamChatThemeData { /// Theme configuration for the [ChannelListView] widget. final ChannelListViewThemeData channelListViewTheme; + /// Theme configuration for the [UserListView] widget. + final UserListViewThemeData userListViewTheme; + /// Creates a copy of [StreamChatThemeData] with specified attributes /// overridden. StreamChatThemeData copyWith({ @@ -196,6 +202,7 @@ class StreamChatThemeData { GalleryFooterThemeData? galleryFooterTheme, MessageListViewThemeData? messageListViewTheme, ChannelListViewThemeData? channelListViewTheme, + UserListViewThemeData? userListViewTheme, }) => StreamChatThemeData.raw( channelListHeaderTheme: @@ -215,6 +222,7 @@ class StreamChatThemeData { galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme, messageListViewTheme: messageListViewTheme ?? this.messageListViewTheme, channelListViewTheme: channelListViewTheme ?? this.channelListViewTheme, + userListViewTheme: userListViewTheme ?? this.userListViewTheme, ); /// Merge themes @@ -239,6 +247,7 @@ class StreamChatThemeData { messageListViewTheme.merge(other.messageListViewTheme), channelListViewTheme: channelListViewTheme.merge(other.channelListViewTheme), + userListViewTheme: userListViewTheme.merge(other.userListViewTheme), ); } @@ -464,6 +473,9 @@ class StreamChatThemeData { channelListViewTheme: ChannelListViewThemeData( backgroundColor: colorTheme.appBg, ), + userListViewTheme: UserListViewThemeData( + backgroundColor: colorTheme.appBg, + ), ); } } @@ -1950,3 +1962,111 @@ class ChannelListViewThemeData with Diagnosticable { properties.add(ColorProperty('backgroundColor', backgroundColor)); } } + +/// Overrides the default style of [UserListView] descendants. +/// +/// See also: +/// +/// * [UserListViewThemeData], which is used to configure this theme. +class UserListViewTheme extends InheritedTheme { + /// Creates a [UserListViewTheme]. + /// + /// The [data] parameter must not be null. + const UserListViewTheme({ + Key? key, + required this.data, + required Widget child, + }) : super(key: key, child: child); + + /// The configuration of this theme. + final UserListViewThemeData data; + + /// The closest instance of this class that encloses the given context. + /// + /// If there is no enclosing [UserListViewTheme] widget, then + /// [StreamChatThemeData.userListViewTheme] is used. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// UserListViewTheme theme = UserListViewTheme.of(context); + /// ``` + static UserListViewThemeData of(BuildContext context) { + final userListViewTheme = + context.dependOnInheritedWidgetOfExactType(); + return userListViewTheme?.data ?? + StreamChatTheme.of(context).userListViewTheme; + } + + @override + Widget wrap(BuildContext context, Widget child) => + UserListViewTheme(data: data, child: child); + + @override + bool updateShouldNotify(UserListViewTheme oldWidget) => + data != oldWidget.data; +} + +/// A style that overrides the default appearance of [UserListView]s when +/// used with [UserListViewTheme] or with the overall [StreamChatTheme]'s +/// [StreamChatThemeData.userListViewTheme]. +/// +/// See also: +/// +/// * [UserListViewTheme], the theme which is configured with this class. +/// * [StreamChatThemeData.userListViewTheme], which can be used to override +/// the default style for [UserListView]s below the overall +/// [StreamChatTheme]. +class UserListViewThemeData with Diagnosticable { + /// Creates a [UserListViewThemeData]. + const UserListViewThemeData({ + this.backgroundColor, + }); + + /// The color of the [ChannelListView] background. + final Color? backgroundColor; + + /// Copies this [ChannelListViewThemeData] to another. + UserListViewThemeData copyWith({ + Color? backgroundColor, + }) => + UserListViewThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + ); + + /// Linearly interpolate between two [UserListViewThemeData] themes. + /// + /// All the properties must be non-null. + UserListViewThemeData lerp( + UserListViewThemeData a, + UserListViewThemeData b, + double t, + ) => + UserListViewThemeData( + backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + ); + + /// Merges one [UserListViewThemeData] with another. + UserListViewThemeData merge(UserListViewThemeData? other) { + if (other == null) return this; + return copyWith( + backgroundColor: other.backgroundColor, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is UserListViewThemeData && + runtimeType == other.runtimeType && + backgroundColor == other.backgroundColor; + + @override + int get hashCode => backgroundColor.hashCode; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + } +} 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 923e03344..0be283bde 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -160,33 +160,36 @@ class _UserListViewState extends State @override Widget build(BuildContext context) { - final child = UserListCore( - errorBuilder: widget.errorBuilder ?? - (BuildContext context, Object err) => _buildError(err), - emptyBuilder: widget.emptyBuilder ?? (context) => _buildEmpty(), - loadingBuilder: widget.loadingBuilder ?? - (context) => LayoutBuilder( - builder: (context, viewportConstraints) => - SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: const Center( - child: CircularProgressIndicator(), + final child = ColoredBox( + color: UserListViewTheme.of(context).backgroundColor!, + child: UserListCore( + errorBuilder: widget.errorBuilder ?? + (BuildContext context, Object err) => _buildError(err), + emptyBuilder: widget.emptyBuilder ?? (context) => _buildEmpty(), + loadingBuilder: widget.loadingBuilder ?? + (context) => LayoutBuilder( + builder: (context, viewportConstraints) => + SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: const Center( + child: CircularProgressIndicator(), + ), ), ), - ), - ), - listBuilder: - widget.listBuilder ?? (context, list) => _buildListView(list), - pagination: widget.pagination, - sort: widget.sort, - filter: widget.filter, - presence: widget.presence, - groupAlphabetically: widget.groupAlphabetically, - userListController: _userListController, + ), + listBuilder: + widget.listBuilder ?? (context, list) => _buildListView(list), + pagination: widget.pagination, + sort: widget.sort, + filter: widget.filter, + presence: widget.presence, + groupAlphabetically: widget.groupAlphabetically, + userListController: _userListController, + ), ); if (!widget.pullToRefresh) { From d6ef2d5a3fa9829fc39e22751397afa671236c58 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Mon, 19 Jul 2021 13:38:43 -0400 Subject: [PATCH 025/145] fix typo in test name --- .../test/src/channel_list_view_theme_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/test/src/channel_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/channel_list_view_theme_test.dart index 8f948db39..80a21d257 100644 --- a/packages/stream_chat_flutter/test/src/channel_list_view_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_list_view_theme_test.dart @@ -11,7 +11,7 @@ void main() { }); test( - '''List ChannelListViewThemeData lerps completely to dark ChannelListViewThemeData''', + '''Light ChannelListViewThemeData lerps completely to dark ChannelListViewThemeData''', () { expect( const ChannelListViewThemeData().lerp(_channelListViewThemeDataControl, From 674fc83e62eb47d728b11ddec48b5d29b0069a14 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Mon, 19 Jul 2021 13:39:17 -0400 Subject: [PATCH 026/145] test: add tests for UserListViewThemeData --- .../test/src/user_list_view_theme_test.dart | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 packages/stream_chat_flutter/test/src/user_list_view_theme_test.dart diff --git a/packages/stream_chat_flutter/test/src/user_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/user_list_view_theme_test.dart new file mode 100644 index 000000000..666affd84 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/user_list_view_theme_test.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + test('UserListViewThemeData copyWith, ==, hashCode basics', () { + expect(const UserListViewThemeData(), + const UserListViewThemeData().copyWith()); + }); + + test( + '''Light UserListViewThemeData lerps completely to dark UserListViewThemeData''', + () { + expect( + const UserListViewThemeData().lerp(_userListViewThemeDataControl, + _userListViewThemeDataControlDark, 1), + _userListViewThemeDataControlDark); + }); + + test( + '''Light UserListViewThemeData lerps halfway to dark UserListViewThemeData''', + () { + expect( + const UserListViewThemeData().lerp(_userListViewThemeDataControl, + _userListViewThemeDataControlDark, 0.5), + _userListViewThemeDataControlHalfLerp); + }); + + test( + '''Dark UserListViewThemeData lerps completely to light UserListViewThemeData''', + () { + expect( + const UserListViewThemeData().lerp(_userListViewThemeDataControlDark, + _userListViewThemeDataControl, 1), + _userListViewThemeDataControl); + }); + + test('Merging dark and light themes results in a dark theme', () { + expect( + _userListViewThemeDataControl.merge(_userListViewThemeDataControlDark), + _userListViewThemeDataControlDark); + }); + + testWidgets( + 'Passing no ChannelListViewThemeData returns default light theme values', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockClient(), + child: child, + ), + home: Builder( + builder: (BuildContext context) { + _context = context; + return const Scaffold( + body: UsersBloc( + child: UserListView(), + ), + ); + }, + ), + ), + ); + + final userListViewTheme = UserListViewTheme.of(_context); + expect(userListViewTheme.backgroundColor, + _userListViewThemeDataControl.backgroundColor); + }); + + testWidgets( + 'Passing no ChannelListViewThemeData returns default dark theme values', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockClient(), + streamChatThemeData: StreamChatThemeData.dark(), + child: child, + ), + home: Builder( + builder: (BuildContext context) { + _context = context; + return const Scaffold( + body: UsersBloc( + child: UserListView(), + ), + ); + }, + ), + ), + ); + + final userListViewTheme = UserListViewTheme.of(_context); + expect(userListViewTheme.backgroundColor, + _userListViewThemeDataControlDark.backgroundColor); + }); +} + +final _userListViewThemeDataControl = UserListViewThemeData( + backgroundColor: ColorTheme.light().appBg, +); + +const _userListViewThemeDataControlHalfLerp = UserListViewThemeData( + backgroundColor: Color(0xff818384), +); + +final _userListViewThemeDataControlDark = UserListViewThemeData( + backgroundColor: ColorTheme.dark().appBg, +); From e25db48a07f1066aab7c4bbba318d872217f6f65 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Mon, 19 Jul 2021 14:20:22 -0400 Subject: [PATCH 027/145] chore: set MessageSeachListView bg color via theme and stop setting scaffold bg color New classes: * MessageSearchListViewTheme * MessageSearchListViewThemeData --- .../lib/src/message_search_list_view.dart | 95 ++++++------ .../lib/src/stream_chat.dart | 1 - .../lib/src/stream_chat_theme.dart | 139 +++++++++++++++++- 3 files changed, 180 insertions(+), 55 deletions(-) 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 f364d0f22..0a1c388b5 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 @@ -144,58 +144,61 @@ class _MessageSearchListViewState extends State { widget.messageSearchListController ?? _defaultController; @override - Widget build(BuildContext context) => MessageSearchListCore( - filters: widget.filters, - sortOptions: widget.sortOptions, - messageQuery: widget.messageQuery, - paginationParams: widget.paginationParams, - messageFilters: widget.messageFilters, - messageSearchListController: _messageSearchListController, - emptyBuilder: widget.emptyBuilder ?? - (context) => LayoutBuilder( - builder: (context, viewportConstraints) => - SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: const Center( - child: Text('There are no messages currently'), + Widget build(BuildContext context) => ColoredBox( + color: MessageSearchListViewTheme.of(context).backgroundColor!, + child: MessageSearchListCore( + filters: widget.filters, + sortOptions: widget.sortOptions, + messageQuery: widget.messageQuery, + paginationParams: widget.paginationParams, + messageFilters: widget.messageFilters, + messageSearchListController: _messageSearchListController, + emptyBuilder: widget.emptyBuilder ?? + (context) => LayoutBuilder( + builder: (context, viewportConstraints) => + SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: const Center( + child: Text('There are no messages currently'), + ), ), ), ), - ), - errorBuilder: widget.errorBuilder ?? - (BuildContext context, dynamic error) { - if (error is Error) { - print(error.stackTrace); - } - return InfoTile( - showMessage: widget.showErrorTile, - tileAnchor: Alignment.topCenter, - childAnchor: Alignment.topCenter, - message: 'An error occurred.', - child: Container(), - ); - }, - loadingBuilder: widget.loadingBuilder ?? - (context) => LayoutBuilder( - builder: (context, viewportConstraints) => - SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: const Center( - child: CircularProgressIndicator(), + errorBuilder: widget.errorBuilder ?? + (BuildContext context, dynamic error) { + if (error is Error) { + print(error.stackTrace); + } + return InfoTile( + showMessage: widget.showErrorTile, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: 'An error occurred.', + child: Container(), + ); + }, + loadingBuilder: widget.loadingBuilder ?? + (context) => LayoutBuilder( + builder: (context, viewportConstraints) => + SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: const Center( + child: CircularProgressIndicator(), + ), ), ), ), - ), - childBuilder: widget.childBuilder ?? _buildListView, - ); + childBuilder: widget.childBuilder ?? _buildListView, + ), + ); Widget _separatorBuilder(BuildContext context, int index) => Container( height: 1, diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index c050d8cbd..66add1339 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -102,7 +102,6 @@ class StreamChatState extends State { data: materialTheme.copyWith( primaryIconTheme: streamTheme.primaryIconTheme, accentColor: streamTheme.colorTheme.accentPrimary, - scaffoldBackgroundColor: streamTheme.colorTheme.barsBg, ), child: StreamChatCore( client: client, diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index d632c10da..94ba50e4f 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -63,6 +63,7 @@ class StreamChatThemeData { MessageListViewThemeData? messageListViewTheme, ChannelListViewThemeData? channelListViewTheme, UserListViewThemeData? userListViewTheme, + MessageSearchListViewThemeData? messageSearchListViewTheme, }) { brightness ??= colorTheme?.brightness ?? Brightness.light; final isDark = brightness == Brightness.dark; @@ -89,6 +90,7 @@ class StreamChatThemeData { messageListViewTheme: messageListViewTheme, channelListViewTheme: channelListViewTheme, userListViewTheme: userListViewTheme, + messageSearchListViewTheme: messageSearchListViewTheme, ); return defaultData.merge(customizedData); @@ -120,6 +122,7 @@ class StreamChatThemeData { required this.messageListViewTheme, required this.channelListViewTheme, required this.userListViewTheme, + required this.messageSearchListViewTheme, }); /// Create a theme from a Material [Theme] @@ -184,6 +187,9 @@ class StreamChatThemeData { /// Theme configuration for the [UserListView] widget. final UserListViewThemeData userListViewTheme; + /// Theme configuration for the [] widget. + final MessageSearchListViewThemeData messageSearchListViewTheme; + /// Creates a copy of [StreamChatThemeData] with specified attributes /// overridden. StreamChatThemeData copyWith({ @@ -203,6 +209,7 @@ class StreamChatThemeData { MessageListViewThemeData? messageListViewTheme, ChannelListViewThemeData? channelListViewTheme, UserListViewThemeData? userListViewTheme, + MessageSearchListViewThemeData? messageSearchListViewTheme, }) => StreamChatThemeData.raw( channelListHeaderTheme: @@ -223,6 +230,8 @@ class StreamChatThemeData { messageListViewTheme: messageListViewTheme ?? this.messageListViewTheme, channelListViewTheme: channelListViewTheme ?? this.channelListViewTheme, userListViewTheme: userListViewTheme ?? this.userListViewTheme, + messageSearchListViewTheme: + messageSearchListViewTheme ?? this.messageSearchListViewTheme, ); /// Merge themes @@ -248,6 +257,8 @@ class StreamChatThemeData { channelListViewTheme: channelListViewTheme.merge(other.channelListViewTheme), userListViewTheme: userListViewTheme.merge(other.userListViewTheme), + messageSearchListViewTheme: + messageSearchListViewTheme.merge(other.messageSearchListViewTheme), ); } @@ -476,6 +487,9 @@ class StreamChatThemeData { userListViewTheme: UserListViewThemeData( backgroundColor: colorTheme.appBg, ), + messageSearchListViewTheme: MessageSearchListViewThemeData( + backgroundColor: colorTheme.appBg, + ), ); } } @@ -1993,7 +2007,7 @@ class UserListViewTheme extends InheritedTheme { /// ``` static UserListViewThemeData of(BuildContext context) { final userListViewTheme = - context.dependOnInheritedWidgetOfExactType(); + context.dependOnInheritedWidgetOfExactType(); return userListViewTheme?.data ?? StreamChatTheme.of(context).userListViewTheme; } @@ -2038,10 +2052,10 @@ class UserListViewThemeData with Diagnosticable { /// /// All the properties must be non-null. UserListViewThemeData lerp( - UserListViewThemeData a, - UserListViewThemeData b, - double t, - ) => + UserListViewThemeData a, + UserListViewThemeData b, + double t, + ) => UserListViewThemeData( backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), ); @@ -2057,9 +2071,118 @@ class UserListViewThemeData with Diagnosticable { @override bool operator ==(Object other) => identical(this, other) || - other is UserListViewThemeData && - runtimeType == other.runtimeType && - backgroundColor == other.backgroundColor; + other is UserListViewThemeData && + runtimeType == other.runtimeType && + backgroundColor == other.backgroundColor; + + @override + int get hashCode => backgroundColor.hashCode; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + } +} + +/// Overrides the default style of [MessageSearchListView] descendants. +/// +/// See also: +/// +/// * [UserListViewThemeData], which is used to configure this theme. +class MessageSearchListViewTheme extends InheritedTheme { + /// Creates a [UserListViewTheme]. + /// + /// The [data] parameter must not be null. + const MessageSearchListViewTheme({ + Key? key, + required this.data, + required Widget child, + }) : super(key: key, child: child); + + /// The configuration of this theme. + final MessageSearchListViewThemeData data; + + /// The closest instance of this class that encloses the given context. + /// + /// If there is no enclosing [MessageSearchListView] widget, then + /// [StreamChatThemeData.messageSearchListViewTheme] is used. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// MessageSearchListViewTheme theme = MessageSearchListViewTheme.of(context); + /// ``` + static MessageSearchListViewThemeData of(BuildContext context) { + final messageSearchListViewTheme = context + .dependOnInheritedWidgetOfExactType(); + return messageSearchListViewTheme?.data ?? + StreamChatTheme.of(context).messageSearchListViewTheme; + } + + @override + Widget wrap(BuildContext context, Widget child) => + MessageSearchListViewTheme(data: data, child: child); + + @override + bool updateShouldNotify(MessageSearchListViewTheme oldWidget) => + data != oldWidget.data; +} + +/// A style that overrides the default appearance of [MessageSearchListView]s +/// when used with [MessageSearchListView] or with the overall +/// [StreamChatTheme]'s [StreamChatThemeData.messageSearchListViewTheme]. +/// +/// See also: +/// +/// * [MessageSearchListViewTheme], the theme which is configured with this +/// class. +/// * [StreamChatThemeData.messageSearchListViewTheme], which can be used to +/// override the default style for [UserListView]s below the overall +/// [StreamChatTheme]. +class MessageSearchListViewThemeData with Diagnosticable { + /// Creates a [MessageSearchListViewThemeData]. + const MessageSearchListViewThemeData({ + this.backgroundColor, + }); + + /// The color of the [MessageSearchListView] background. + final Color? backgroundColor; + + /// Copies this [MessageSearchListViewThemeData] to another. + MessageSearchListViewThemeData copyWith({ + Color? backgroundColor, + }) => + MessageSearchListViewThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + ); + + /// Linearly interpolate between two [UserListViewThemeData] themes. + /// + /// All the properties must be non-null. + MessageSearchListViewThemeData lerp( + MessageSearchListViewThemeData a, + MessageSearchListViewThemeData b, + double t, + ) => + MessageSearchListViewThemeData( + backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + ); + + /// Merges one [MessageSearchListViewThemeData] with another. + MessageSearchListViewThemeData merge(MessageSearchListViewThemeData? other) { + if (other == null) return this; + return copyWith( + backgroundColor: other.backgroundColor, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is MessageSearchListViewThemeData && + runtimeType == other.runtimeType && + backgroundColor == other.backgroundColor; @override int get hashCode => backgroundColor.hashCode; From 10d2480a00e02b3a51b895daba3977cc113966ca Mon Sep 17 00:00:00 2001 From: groovinchip Date: Mon, 19 Jul 2021 14:41:39 -0400 Subject: [PATCH 028/145] test: add tests for MessageSearchListViewThemeData --- .../src/message_list_view_theme_test.dart | 3 +- .../message_search_list_view_theme_test.dart | 129 ++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 packages/stream_chat_flutter/test/src/message_search_list_view_theme_test.dart diff --git a/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart index 252eb944d..ffe54dd04 100644 --- a/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/message_list_view_theme_test.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:flutter_test/flutter_test.dart'; + import 'mocks.dart'; class MockStreamChatClient extends Mock implements StreamChatClient {} diff --git a/packages/stream_chat_flutter/test/src/message_search_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/message_search_list_view_theme_test.dart new file mode 100644 index 000000000..e485e0d6d --- /dev/null +++ b/packages/stream_chat_flutter/test/src/message_search_list_view_theme_test.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + test('MessageSearchListViewThemeData copyWith, ==, hashCode basics', () { + expect(const MessageSearchListViewThemeData(), + const MessageSearchListViewThemeData().copyWith()); + expect(const MessageSearchListViewThemeData().hashCode, + const MessageSearchListViewThemeData().copyWith().hashCode); + }); + + test( + '''Light MessageSearchListViewThemeData lerps completely to dark MessageSearchListViewThemeData''', + () { + expect( + const MessageSearchListViewThemeData().lerp( + _messageSearchListViewThemeDataControl, + _messageSearchListViewThemeDataControlDark, + 1), + _messageSearchListViewThemeDataControlDark); + }); + + test( + '''Light MessageSearchListViewThemeData lerps halfway to dark MessageSearchListViewThemeData''', + () { + expect( + const MessageSearchListViewThemeData().lerp( + _messageSearchListViewThemeDataControl, + _messageSearchListViewThemeDataControlDark, + 0.5), + _messageSearchListViewThemeDataControlHalfLerp); + }); + + test( + '''Dark MessageSearchListViewThemeData lerps completely to light MessageSearchListViewThemeData''', + () { + expect( + const MessageSearchListViewThemeData().lerp( + _messageSearchListViewThemeDataControlDark, + _messageSearchListViewThemeDataControl, + 1), + _messageSearchListViewThemeDataControl); + }); + + test('Merging dark and light themes results in a dark theme', () { + expect( + _messageSearchListViewThemeDataControl + .merge(_messageSearchListViewThemeDataControlDark), + _messageSearchListViewThemeDataControlDark); + }); + + testWidgets( + '''Passing no MessageSearchListViewThemeData returns default light theme values''', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockClient(), + child: child, + ), + home: Builder( + builder: (BuildContext context) { + _context = context; + return Scaffold( + body: MessageSearchBloc( + child: MessageSearchListView( + filters: Filter.in_('members', const ['test_id']), + ), + ), + ); + }, + ), + ), + ); + + final messageSearchListViewTheme = MessageSearchListViewTheme.of(_context); + expect(messageSearchListViewTheme.backgroundColor, + _messageSearchListViewThemeDataControl.backgroundColor); + }); + + testWidgets( + '''Passing no MessageSearchListViewThemeData returns default dark theme values''', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockClient(), + streamChatThemeData: StreamChatThemeData.dark(), + child: child, + ), + home: Builder( + builder: (BuildContext context) { + _context = context; + return Scaffold( + body: MessageSearchBloc( + child: MessageSearchListView( + filters: Filter.in_('members', const ['test_id']), + ), + ), + ); + }, + ), + ), + ); + + final messageSearchListViewTheme = MessageSearchListViewTheme.of(_context); + expect(messageSearchListViewTheme.backgroundColor, + _messageSearchListViewThemeDataControlDark.backgroundColor); + }); +} + +final _messageSearchListViewThemeDataControl = MessageSearchListViewThemeData( + backgroundColor: ColorTheme.light().appBg, +); + +const _messageSearchListViewThemeDataControlHalfLerp = + MessageSearchListViewThemeData( + backgroundColor: Color(0xff818384), +); + +final _messageSearchListViewThemeDataControlDark = + MessageSearchListViewThemeData( + backgroundColor: ColorTheme.dark().appBg, +); From ddf5d45c4a0f9737e05337ce0c9e3f5a790891dd Mon Sep 17 00:00:00 2001 From: groovinchip Date: Mon, 19 Jul 2021 14:44:04 -0400 Subject: [PATCH 029/145] fix: dartfmt on a couple of files --- .../lib/src/message_list_view.dart | 6 ++- .../lib/src/message_search_list_view.dart | 6 +-- .../lib/src/user_list_view.dart | 12 ++--- .../test/src/user_list_view_theme_test.dart | 50 +++++++++---------- 4 files changed, 38 insertions(+), 36 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 fee3c8eeb..fe8960581 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -530,7 +530,8 @@ class _MessageListViewState extends State { }, itemBuilder: (context, i) { if (i == itemCount - 1) { - if (widget.parentMessage == null) return const Offstage(); + if (widget.parentMessage == null) + return const Offstage(); return buildParentMessage(widget.parentMessage!); } @@ -558,7 +559,8 @@ class _MessageListViewState extends State { const Offstage(); } - const bottomMessageIndex = 2; // 1 -> loader // 0 -> footer + const bottomMessageIndex = + 2; // 1 -> loader // 0 -> footer final message = messages[i - 2]; Widget messageWidget; 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 0a1c388b5..93488ba48 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 @@ -145,8 +145,8 @@ class _MessageSearchListViewState extends State { @override Widget build(BuildContext context) => ColoredBox( - color: MessageSearchListViewTheme.of(context).backgroundColor!, - child: MessageSearchListCore( + color: MessageSearchListViewTheme.of(context).backgroundColor!, + child: MessageSearchListCore( filters: widget.filters, sortOptions: widget.sortOptions, messageQuery: widget.messageQuery, @@ -198,7 +198,7 @@ class _MessageSearchListViewState extends State { ), childBuilder: widget.childBuilder ?? _buildListView, ), - ); + ); Widget _separatorBuilder(BuildContext context, int index) => Container( height: 1, 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 0be283bde..ad5abb240 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -164,12 +164,12 @@ class _UserListViewState extends State color: UserListViewTheme.of(context).backgroundColor!, child: UserListCore( errorBuilder: widget.errorBuilder ?? - (BuildContext context, Object err) => _buildError(err), + (BuildContext context, Object err) => _buildError(err), emptyBuilder: widget.emptyBuilder ?? (context) => _buildEmpty(), loadingBuilder: widget.loadingBuilder ?? - (context) => LayoutBuilder( - builder: (context, viewportConstraints) => - SingleChildScrollView( + (context) => LayoutBuilder( + builder: (context, viewportConstraints) => + SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: ConstrainedBox( constraints: BoxConstraints( @@ -180,9 +180,9 @@ class _UserListViewState extends State ), ), ), - ), + ), listBuilder: - widget.listBuilder ?? (context, list) => _buildListView(list), + widget.listBuilder ?? (context, list) => _buildListView(list), pagination: widget.pagination, sort: widget.sort, filter: widget.filter, diff --git a/packages/stream_chat_flutter/test/src/user_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/user_list_view_theme_test.dart index 666affd84..e9b7985a2 100644 --- a/packages/stream_chat_flutter/test/src/user_list_view_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/user_list_view_theme_test.dart @@ -73,32 +73,32 @@ void main() { testWidgets( 'Passing no ChannelListViewThemeData returns default dark theme values', - (WidgetTester tester) async { - late BuildContext _context; - await tester.pumpWidget( - MaterialApp( - builder: (context, child) => StreamChat( - client: MockClient(), - streamChatThemeData: StreamChatThemeData.dark(), - child: child, - ), - home: Builder( - builder: (BuildContext context) { - _context = context; - return const Scaffold( - body: UsersBloc( - child: UserListView(), - ), - ); - }, - ), - ), - ); + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockClient(), + streamChatThemeData: StreamChatThemeData.dark(), + child: child, + ), + home: Builder( + builder: (BuildContext context) { + _context = context; + return const Scaffold( + body: UsersBloc( + child: UserListView(), + ), + ); + }, + ), + ), + ); - final userListViewTheme = UserListViewTheme.of(_context); - expect(userListViewTheme.backgroundColor, - _userListViewThemeDataControlDark.backgroundColor); - }); + final userListViewTheme = UserListViewTheme.of(_context); + expect(userListViewTheme.backgroundColor, + _userListViewThemeDataControlDark.backgroundColor); + }); } final _userListViewThemeDataControl = UserListViewThemeData( From 7fc288b39798b357431857e3f79f00a5a9d637c5 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Mon, 19 Jul 2021 14:48:54 -0400 Subject: [PATCH 030/145] remove example app test folder that got generated with the macos project --- .../example/test/widget_test.dart | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 packages/stream_chat_flutter/example/test/widget_test.dart diff --git a/packages/stream_chat_flutter/example/test/widget_test.dart b/packages/stream_chat_flutter/example/test/widget_test.dart deleted file mode 100644 index 747db1da3..000000000 --- a/packages/stream_chat_flutter/example/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:example/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} From a1f0afe83f73bcb63b7cdd5b6e450b7a7b4bebae Mon Sep 17 00:00:00 2001 From: groovinchip Date: Mon, 19 Jul 2021 14:54:25 -0400 Subject: [PATCH 031/145] fix: curly brace lint --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 fe8960581..b843f3b41 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -530,8 +530,9 @@ class _MessageListViewState extends State { }, itemBuilder: (context, i) { if (i == itemCount - 1) { - if (widget.parentMessage == null) + if (widget.parentMessage == null) { return const Offstage(); + } return buildParentMessage(widget.parentMessage!); } From d76f182f226f032b43486dd7653fbad3bcc56b59 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Jul 2021 09:23:30 +0200 Subject: [PATCH 032/145] added changelog entry --- packages/stream_chat_flutter/CHANGELOG.md | 5 +++++ packages/stream_chat_flutter_core/CHANGELOG.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index e606d32db..9a00c3128 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,8 @@ +## Upcoming + +✅ Added +- Added `MessageListView.paginationLimit` + ## 2.0.0 🛑️ Breaking Changes from `1.5.4` diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 2f7abfe22..a823a0dbe 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,8 @@ +## Upcoming + +✅ Added +- Added `MessageListCore.paginationLimit` + ## 2.0.0 🛑️ Breaking Changes from `1.5.3` From 1939cc1bc8ef1f225e68cb69879f23b605452782 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 20 Jul 2021 13:46:53 +0530 Subject: [PATCH 033/145] feat: Added gradient avatar --- .../lib/src/gradient_avatar.dart | 199 ++++++++++++++++++ .../lib/src/stream_chat_theme.dart | 8 +- 2 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/gradient_avatar.dart diff --git a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart new file mode 100644 index 000000000..afc02fb67 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart @@ -0,0 +1,199 @@ +import 'dart:math'; +import 'dart:ui'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; + +class GradientAvatar extends StatefulWidget { + final String name; + final String userId; + + const GradientAvatar({ + Key? key, + required this.name, + required this.userId, + }) : super(key: key); + + @override + _GradientAvatarState createState() => _GradientAvatarState(); +} + +class _GradientAvatarState extends State { + @override + Widget build(BuildContext context) { + return Center( + child: RepaintBoundary( + child: Container( + width: 100.0, + height: 100.0, + child: Stack( + alignment: Alignment.center, + children: [ + CustomPaint( + painter: DemoPainter(widget.userId), + child: SizedBox.expand(), + ), + FittedBox( + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Opacity( + opacity: 0.8, + child: Text( + getShortenedName(widget.name), + style: TextStyle( + color: Colors.white, + fontSize: 112.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); + } + + String getShortenedName(String name) { + List parts = name.split(' ')..removeWhere((e) => e == ''); + + if (parts.length > 2) { + parts = parts.take(2).toList(); + } + + String result = ''; + + for (int i = 0; i < parts.length; i++) { + result = result + parts[i][0]; + } + + return result; + } +} + +class DemoPainter extends CustomPainter { + static const int rowCount = 5; + static const int columnCount = 5; + + String userId; + + DemoPainter(this.userId); + + @override + void paint(Canvas canvas, Size size) { + var rowUnit = size.width / columnCount; + var columnUnit = size.height / rowCount; + var rand = Random(userId.length); + + List squares = []; + Set points = {}; + List gradient = colorGradients[rand.nextInt(colorGradients.length)]; + + for (int i = 0; i < rowCount; i++) { + for (int j = 0; j < columnCount; j++) { + var off1 = Offset(rowUnit * j, columnUnit * i); + var off2 = Offset(rowUnit * (j + 1), columnUnit * i); + var off3 = Offset(rowUnit * (j + 1), columnUnit * (i + 1)); + var off4 = Offset(rowUnit * j, columnUnit * (i + 1)); + + points.addAll([off1, off2, off3, off4]); + + var p1 = points.toList().indexOf(off1); + var p2 = points.toList().indexOf(off2); + var p3 = points.toList().indexOf(off3); + var p4 = points.toList().indexOf(off4); + + squares.add( + Offset4(p1, p2, p3, p4, i, j, rowCount, columnCount, gradient)); + } + } + + var list = transformPoints(points, size); + squares.forEach((e) => e.draw(canvas, list)); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return false; + } + + List transformPoints(Set points, Size size) { + List transformedList = []; + var orgList = points.toList(); + var rand = Random(userId.length); + + for (int i = 0; i < points.length; i++) { + var orgDx = orgList[i].dx; + var orgDy = orgList[i].dy; + + if (orgDx == 0 || + orgDy == 0 || + orgDx == size.width || + orgDy == size.height) { + transformedList.add(Offset(orgDx, orgDy)); + continue; + } + + int sign1 = rand.nextInt(2) == 1 ? 1 : -1; + int sign2 = rand.nextInt(2) == 1 ? 1 : -1; + + double dx = 0.6 * sign1 * rand.nextInt(size.width ~/ columnCount); + double dy = 0.6 * sign2 * rand.nextInt(size.height ~/ rowCount); + + transformedList.add(Offset(orgDx + dx, orgDy + dy)); + } + + return transformedList; + } +} + +class Offset4 { + int p1; + int p2; + int p3; + int p4; + int row; + int column; + int rowSize; + int colSize; + List gradient; + + Offset4(this.p1, this.p2, this.p3, this.p4, this.row, this.column, + this.rowSize, this.colSize, this.gradient); + + void draw(Canvas canvas, List points) { + Paint paint = Paint() + ..color = Color.fromARGB(255, Random().nextInt(255), + Random().nextInt(255), Random().nextInt(255)) + ..shader = ui.Gradient.linear( + points[p1], + points[p3], + gradient, + ); + + final backgroundPath = Path() + ..moveTo(points[p1].dx, points[p1].dy) + ..lineTo(points[p2].dx, points[p2].dy) + ..lineTo(points[p3].dx, points[p3].dy) + ..lineTo(points[p4].dx, points[p4].dy) + ..lineTo(points[p1].dx, points[p1].dy) + ..close(); + + canvas.drawPath(backgroundPath, paint); + } +} + +const colorGradients = [ + [Color(0xffffafbd), Color(0xffffc3a0)], + [Color(0xff2193b0), Color(0xff6dd5ed)], + [Color(0xffcc2b5e), Color(0xff753a88)], + [Color(0xffee9ca7), Color(0xffffdde1)], + [Color(0xff42275a), Color(0xff734b6d)], + [Color(0xffde6262), Color(0xffffb88c)], + [Color(0xff56ab2f), Color(0xffa8e063)], + [Color(0xff614385), Color(0xff516395)], + [Color(0xffeacda3), Color(0xffd6ae7b)], + [Color(0xff02aab0), Color(0xff00cdac)], +]; diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index c62e92ecd..74976e07a 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/channel_header.dart'; import 'package:stream_chat_flutter/src/channel_preview.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/gradient_avatar.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/reaction_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; @@ -270,10 +271,9 @@ class StreamChatThemeData { colorTheme: colorTheme, primaryIconTheme: iconTheme, defaultUserImage: (context, user) => Center( - child: CachedNetworkImage( - filterQuality: FilterQuality.high, - imageUrl: getRandomPicUrl(user), - fit: BoxFit.cover, + child: GradientAvatar( + name: user.name, + userId: user.id, ), ), channelPreviewTheme: channelPreviewTheme, From ec1c2fce8cefae5a7336b69edc00c021c2b34b72 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 20 Jul 2021 13:53:33 +0530 Subject: [PATCH 034/145] feat: Added docs and cleanup --- .../lib/src/gradient_avatar.dart | 180 +++++++++++------- 1 file changed, 109 insertions(+), 71 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart index afc02fb67..aef80712e 100644 --- a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart @@ -4,68 +4,71 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; +/// Fallback user avatar with a polygon gradient overlayed with text class GradientAvatar extends StatefulWidget { - final String name; - final String userId; - + /// Constructor for [GradientAvatar] const GradientAvatar({ Key? key, required this.name, required this.userId, }) : super(key: key); + /// Name of user to shorten and display + final String name; + + /// ID of user to be used for key + final String userId; + @override _GradientAvatarState createState() => _GradientAvatarState(); } class _GradientAvatarState extends State { @override - Widget build(BuildContext context) { - return Center( - child: RepaintBoundary( - child: Container( - width: 100.0, - height: 100.0, - child: Stack( - alignment: Alignment.center, - children: [ - CustomPaint( - painter: DemoPainter(widget.userId), - child: SizedBox.expand(), - ), - FittedBox( - child: Padding( - padding: const EdgeInsets.all(32.0), - child: Opacity( - opacity: 0.8, - child: Text( - getShortenedName(widget.name), - style: TextStyle( - color: Colors.white, - fontSize: 112.0, - fontWeight: FontWeight.bold, + Widget build(BuildContext context) => Center( + child: RepaintBoundary( + child: SizedBox( + width: 100, + height: 100, + child: Stack( + alignment: Alignment.center, + children: [ + CustomPaint( + painter: DemoPainter(widget.userId), + child: const SizedBox.expand(), + ), + FittedBox( + child: Padding( + padding: const EdgeInsets.all(32), + child: Opacity( + opacity: 0.8, + child: Text( + getShortenedName(widget.name), + style: const TextStyle( + color: Colors.white, + fontSize: 112, + fontWeight: FontWeight.bold, + ), ), ), ), ), - ), - ], + ], + ), ), ), - ), - ); - } + ); String getShortenedName(String name) { - List parts = name.split(' ')..removeWhere((e) => e == ''); + var parts = name.split(' ')..removeWhere((e) => e == ''); if (parts.length > 2) { parts = parts.take(2).toList(); } - String result = ''; + var result = ''; - for (int i = 0; i < parts.length; i++) { + for (var i = 0; i < parts.length; i++) { result = result + parts[i][0]; } @@ -73,60 +76,65 @@ class _GradientAvatarState extends State { } } +/// Painter for bg polygon gradient class DemoPainter extends CustomPainter { + /// Constructor for [DemoPainter] + DemoPainter(this.userId); + + /// Init grid row count static const int rowCount = 5; + + /// Init grid column count static const int columnCount = 5; + /// User ID used for key String userId; - DemoPainter(this.userId); - @override void paint(Canvas canvas, Size size) { - var rowUnit = size.width / columnCount; - var columnUnit = size.height / rowCount; - var rand = Random(userId.length); + final rowUnit = size.width / columnCount; + final columnUnit = size.height / rowCount; + final rand = Random(userId.length); - List squares = []; - Set points = {}; - List gradient = colorGradients[rand.nextInt(colorGradients.length)]; + final squares = []; + final points = {}; + final gradient = colorGradients[rand.nextInt(colorGradients.length)]; - for (int i = 0; i < rowCount; i++) { - for (int j = 0; j < columnCount; j++) { - var off1 = Offset(rowUnit * j, columnUnit * i); - var off2 = Offset(rowUnit * (j + 1), columnUnit * i); - var off3 = Offset(rowUnit * (j + 1), columnUnit * (i + 1)); - var off4 = Offset(rowUnit * j, columnUnit * (i + 1)); + for (var i = 0; i < rowCount; i++) { + for (var j = 0; j < columnCount; j++) { + final off1 = Offset(rowUnit * j, columnUnit * i); + final off2 = Offset(rowUnit * (j + 1), columnUnit * i); + final off3 = Offset(rowUnit * (j + 1), columnUnit * (i + 1)); + final off4 = Offset(rowUnit * j, columnUnit * (i + 1)); points.addAll([off1, off2, off3, off4]); - var p1 = points.toList().indexOf(off1); - var p2 = points.toList().indexOf(off2); - var p3 = points.toList().indexOf(off3); - var p4 = points.toList().indexOf(off4); + final p1 = points.toList().indexOf(off1); + final p2 = points.toList().indexOf(off2); + final p3 = points.toList().indexOf(off3); + final p4 = points.toList().indexOf(off4); squares.add( Offset4(p1, p2, p3, p4, i, j, rowCount, columnCount, gradient)); } } - var list = transformPoints(points, size); + final list = transformPoints(points, size); squares.forEach((e) => e.draw(canvas, list)); } @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return false; - } + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; + /// Transforms initial grid into a polygon grid List transformPoints(Set points, Size size) { - List transformedList = []; - var orgList = points.toList(); - var rand = Random(userId.length); + final transformedList = []; + final orgList = points.toList(); + final rand = Random(userId.length); - for (int i = 0; i < points.length; i++) { - var orgDx = orgList[i].dx; - var orgDy = orgList[i].dy; + for (var i = 0; i < points.length; i++) { + final orgDx = orgList[i].dx; + final orgDy = orgList[i].dy; if (orgDx == 0 || orgDy == 0 || @@ -136,11 +144,11 @@ class DemoPainter extends CustomPainter { continue; } - int sign1 = rand.nextInt(2) == 1 ? 1 : -1; - int sign2 = rand.nextInt(2) == 1 ? 1 : -1; + final sign1 = rand.nextInt(2) == 1 ? 1 : -1; + final sign2 = rand.nextInt(2) == 1 ? 1 : -1; - double dx = 0.6 * sign1 * rand.nextInt(size.width ~/ columnCount); - double dy = 0.6 * sign2 * rand.nextInt(size.height ~/ rowCount); + final dx = 0.6 * sign1 * rand.nextInt(size.width ~/ columnCount); + final dy = 0.6 * sign2 * rand.nextInt(size.height ~/ rowCount); transformedList.add(Offset(orgDx + dx, orgDy + dy)); } @@ -149,22 +157,51 @@ class DemoPainter extends CustomPainter { } } +/// Class for storing and drawing four points of a polygon class Offset4 { + /// Constructor for [Offset4] + Offset4( + this.p1, + this.p2, + this.p3, + this.p4, + this.row, + this.column, + this.rowSize, + this.colSize, + this.gradient, + ); + + /// Point 1 int p1; + + /// Point 2 int p2; + + /// Point 3 int p3; + + /// Point 4 int p4; + + /// Position of polygon on grid int row; + + /// Position of polygon on grid int column; + + /// Max row size int rowSize; + + /// Max col size int colSize; - List gradient; - Offset4(this.p1, this.p2, this.p3, this.p4, this.row, this.column, - this.rowSize, this.colSize, this.gradient); + /// Gradient to be applied to polygon + List gradient; + /// Draw the polygon on canvas void draw(Canvas canvas, List points) { - Paint paint = Paint() + final paint = Paint() ..color = Color.fromARGB(255, Random().nextInt(255), Random().nextInt(255), Random().nextInt(255)) ..shader = ui.Gradient.linear( @@ -185,6 +222,7 @@ class Offset4 { } } +/// Gradient list for polygons const colorGradients = [ [Color(0xffffafbd), Color(0xffffc3a0)], [Color(0xff2193b0), Color(0xff6dd5ed)], From 5670e18c0f938156df4aea9c2d4540553d830518 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 20 Jul 2021 13:54:19 +0530 Subject: [PATCH 035/145] fix: remove deps --- packages/stream_chat_flutter/lib/src/stream_chat_theme.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index 74976e07a..d8d500773 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/channel_header.dart'; @@ -7,7 +6,6 @@ import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/gradient_avatar.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/reaction_icon.dart'; -import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; From 7df908f9c21d894e31a0d85a5e8e56033279c184 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 20 Jul 2021 14:14:06 +0530 Subject: [PATCH 036/145] fix: text style correction --- .../lib/src/gradient_avatar.dart | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart index aef80712e..45cc1e96c 100644 --- a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart @@ -27,34 +27,30 @@ class _GradientAvatarState extends State { @override Widget build(BuildContext context) => Center( child: RepaintBoundary( - child: SizedBox( - width: 100, - height: 100, - child: Stack( - alignment: Alignment.center, - children: [ - CustomPaint( - painter: DemoPainter(widget.userId), - child: const SizedBox.expand(), - ), - FittedBox( - child: Padding( - padding: const EdgeInsets.all(32), - child: Opacity( - opacity: 0.8, - child: Text( - getShortenedName(widget.name), - style: const TextStyle( - color: Colors.white, - fontSize: 112, - fontWeight: FontWeight.bold, - ), + child: Stack( + alignment: Alignment.center, + children: [ + CustomPaint( + painter: DemoPainter(widget.userId), + child: const SizedBox.expand(), + ), + Padding( + padding: const EdgeInsets.all(8), + child: Opacity( + opacity: 0.7, + child: FittedBox( + child: Text( + getShortenedName(widget.name), + style: const TextStyle( + color: Colors.white, + fontSize: 120, + fontWeight: FontWeight.w500, ), ), ), ), - ], - ), + ), + ], ), ), ); From 069ec910a6013c3e5302b54c5f7f3689a105132f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Jul 2021 10:54:20 +0200 Subject: [PATCH 037/145] feat: add MessageListView.reverse property --- .../example/android/app/build.gradle | 4 ++-- .../example/android/build.gradle | 4 ++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../lib/src/message_list_view.dart | 21 +++++++++++++++---- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/stream_chat_flutter/example/android/app/build.gradle b/packages/stream_chat_flutter/example/android/app/build.gradle index 9bb36eac5..fbd6268ef 100644 --- a/packages/stream_chat_flutter/example/android/app/build.gradle +++ b/packages/stream_chat_flutter/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 29 + compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -41,7 +41,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.example" minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/packages/stream_chat_flutter/example/android/build.gradle b/packages/stream_chat_flutter/example/android/build.gradle index 9afae7f84..3e0873dec 100644 --- a/packages/stream_chat_flutter/example/android/build.gradle +++ b/packages/stream_chat_flutter/example/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.5.20' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.2' + classpath 'com.android.tools.build:gradle:4.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/packages/stream_chat_flutter/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/stream_chat_flutter/example/android/gradle/wrapper/gradle-wrapper.properties index 493072b3c..3df6b3389 100644 --- a/packages/stream_chat_flutter/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/stream_chat_flutter/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 822a2f5a7..505fc0977 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1,5 +1,6 @@ // ignore_for_file: lines_longer_than_80_chars import 'dart:async'; +import 'dart:math'; import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; @@ -166,11 +167,19 @@ class MessageListView extends StatefulWidget { this.showFloatingDateDivider = true, this.threadSeparatorBuilder, this.messageListController, + this.reverse = false, }) : super(key: key); /// Function used to build a custom message widget final MessageBuilder? messageBuilder; + /// Whether the view scrolls in the reading direction. + /// + /// Defaults to true. + /// + /// See [ScrollView.reverse]. + final bool reverse; + /// Function used to build a custom system message widget final SystemMessageBuilder? systemMessageBuilder; @@ -445,7 +454,7 @@ class _MessageListViewState extends State { initialAlignment: initialAlignment ?? 0, physics: widget.scrollPhysics, itemScrollController: _scrollController, - reverse: true, + reverse: widget.reverse, addAutomaticKeepAlives: false, itemCount: itemCount, @@ -608,7 +617,8 @@ class _MessageListViewState extends State { } Positioned _buildFloatingDateDivider(int itemCount) => Positioned( - top: 20, + top: widget.reverse ? 20 : null, + bottom: widget.reverse ? null : 20, child: BetterStreamBuilder>( initialData: _itemPositionListener.itemPositions.value, stream: _itemPositionStream, @@ -700,8 +710,11 @@ class _MessageListViewState extends State { ); } }, - child: StreamSvgIcon.down( - color: _streamTheme.colorTheme.textHighEmphasis, + child: Transform.rotate( + angle: widget.reverse ? 0 : pi, + child: StreamSvgIcon.down( + color: _streamTheme.colorTheme.textHighEmphasis, + ), ), ), if (showUnreadCount) From a8fc76c85f3c0ec98e76c923ae26eb4eaaf0b5e9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Jul 2021 10:55:06 +0200 Subject: [PATCH 038/145] update changelog --- 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 e606d32db..c4c8e10c1 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +✅ Added + +- Added `MessageListView.reverse` property + ## 2.0.0 🛑️ Breaking Changes from `1.5.4` From dff0ad9af27cd198eb011347df45605253922e83 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Jul 2021 10:55:25 +0200 Subject: [PATCH 039/145] fix default --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 505fc0977..a344ceb43 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -167,7 +167,7 @@ class MessageListView extends StatefulWidget { this.showFloatingDateDivider = true, this.threadSeparatorBuilder, this.messageListController, - this.reverse = false, + this.reverse = true, }) : super(key: key); /// Function used to build a custom message widget From ab35409b15ac7c84462e8dc59e0c2d7776179d8d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Jul 2021 11:17:12 +0200 Subject: [PATCH 040/145] use up svg icon --- .../lib/src/message_list_view.dart | 13 +++++++------ .../lib/src/stream_svg_icon.dart | 12 ++++++++++++ packages/stream_chat_flutter/lib/svgs/Icon_up.svg | 3 +++ 3 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/svgs/Icon_up.svg 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 a344ceb43..749333214 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -710,12 +710,13 @@ class _MessageListViewState extends State { ); } }, - child: Transform.rotate( - angle: widget.reverse ? 0 : pi, - child: StreamSvgIcon.down( - color: _streamTheme.colorTheme.textHighEmphasis, - ), - ), + child: widget.reverse + ? StreamSvgIcon.down( + color: _streamTheme.colorTheme.textHighEmphasis, + ) + : StreamSvgIcon.up( + color: _streamTheme.colorTheme.textHighEmphasis, + ), ), if (showUnreadCount) Positioned( diff --git a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart index fb5017e19..73c33131e 100644 --- a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart +++ b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart @@ -37,6 +37,18 @@ class StreamSvgIcon extends StatelessWidget { height: size, ); + /// [StreamSvgIcon] type + factory StreamSvgIcon.up({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'Icon_up.svg', + color: color, + width: size, + height: size, + ); + /// [StreamSvgIcon] type factory StreamSvgIcon.attach({ double? size, diff --git a/packages/stream_chat_flutter/lib/svgs/Icon_up.svg b/packages/stream_chat_flutter/lib/svgs/Icon_up.svg new file mode 100644 index 000000000..d3d53810a --- /dev/null +++ b/packages/stream_chat_flutter/lib/svgs/Icon_up.svg @@ -0,0 +1,3 @@ + + + From cfa9d2539911d49ff0314a4e8150fd777b044a8c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 20 Jul 2021 15:39:45 +0530 Subject: [PATCH 041/145] fix: Replaced stack text with canvas text --- .../lib/src/gradient_avatar.dart | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart index 45cc1e96c..a7e189831 100644 --- a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart @@ -27,30 +27,12 @@ class _GradientAvatarState extends State { @override Widget build(BuildContext context) => Center( child: RepaintBoundary( - child: Stack( - alignment: Alignment.center, - children: [ - CustomPaint( - painter: DemoPainter(widget.userId), - child: const SizedBox.expand(), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Opacity( - opacity: 0.7, - child: FittedBox( - child: Text( - getShortenedName(widget.name), - style: const TextStyle( - color: Colors.white, - fontSize: 120, - fontWeight: FontWeight.w500, - ), - ), - ), - ), - ), - ], + child: CustomPaint( + painter: DemoPainter( + widget.userId, + getShortenedName(widget.name), + ), + child: const SizedBox.expand(), ), ), ); @@ -75,7 +57,10 @@ class _GradientAvatarState extends State { /// Painter for bg polygon gradient class DemoPainter extends CustomPainter { /// Constructor for [DemoPainter] - DemoPainter(this.userId); + DemoPainter( + this.userId, + this.username, + ); /// Init grid row count static const int rowCount = 5; @@ -86,6 +71,9 @@ class DemoPainter extends CustomPainter { /// User ID used for key String userId; + /// User name to display + String username; + @override void paint(Canvas canvas, Size size) { final rowUnit = size.width / columnCount; @@ -117,6 +105,35 @@ class DemoPainter extends CustomPainter { final list = transformPoints(points, size); squares.forEach((e) => e.draw(canvas, list)); + + final smallerSide = size.width > size.height ? size.width : size.height; + + final textSize = smallerSide / 3; + + final dxShift = (username.length == 2 ? 1.45 : 0.75) * textSize / 2; + final dyShift = (username.length == 2 ? 1.0 : 1.8) * textSize / 2; + + final fontSize = username.length == 2 ? textSize : textSize * 1.5; + + TextPainter( + text: TextSpan( + text: username, + style: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.w500, + color: Colors.white.withOpacity(0.7), + ), + ), + textAlign: TextAlign.center, + textDirection: TextDirection.ltr) + ..layout(maxWidth: size.width) + ..paint( + canvas, + Offset( + (size.width / 2) - dxShift, + (size.height / 2) - dyShift, + ), + ); } @override From 293cb927c12cad0f5a9d2d58abf92ae5bc87546c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Jul 2021 12:14:09 +0200 Subject: [PATCH 042/145] fix icon --- packages/stream_chat_flutter/lib/svgs/Icon_up.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/svgs/Icon_up.svg b/packages/stream_chat_flutter/lib/svgs/Icon_up.svg index d3d53810a..60e6888f7 100644 --- a/packages/stream_chat_flutter/lib/svgs/Icon_up.svg +++ b/packages/stream_chat_flutter/lib/svgs/Icon_up.svg @@ -1,3 +1,3 @@ - - + + From 88ece480a27dd8a0bb633ddf8bee7c04a1fd4e92 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 20 Jul 2021 15:44:47 +0530 Subject: [PATCH 043/145] fix: cleanup --- .../stream_chat_flutter/lib/src/gradient_avatar.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart index a7e189831..0cb8c41c2 100644 --- a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart @@ -93,10 +93,12 @@ class DemoPainter extends CustomPainter { points.addAll([off1, off2, off3, off4]); - final p1 = points.toList().indexOf(off1); - final p2 = points.toList().indexOf(off2); - final p3 = points.toList().indexOf(off3); - final p4 = points.toList().indexOf(off4); + final pointsList = points.toList(); + + final p1 = pointsList.indexOf(off1); + final p2 = pointsList.indexOf(off2); + final p3 = pointsList.indexOf(off3); + final p4 = pointsList.indexOf(off4); squares.add( Offset4(p1, p2, p3, p4, i, j, rowCount, columnCount, gradient)); From 6794bde9e496b567ef2fff3273b8f0c78881fd81 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Jul 2021 12:39:08 +0200 Subject: [PATCH 044/145] fix analyzer --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 1 - 1 file changed, 1 deletion(-) 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 749333214..942d02f51 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1,6 +1,5 @@ // ignore_for_file: lines_longer_than_80_chars import 'dart:async'; -import 'dart:math'; import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; From 2b36f009c2eb43e18b473270d7345b10cf8d02c4 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 20 Jul 2021 18:09:12 +0530 Subject: [PATCH 045/145] 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 de2433ab3..d643dfea4 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 e9c2b7c95..4e860745d 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 432f3354a..4c0d2b424 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 13ec3b3b9..5e498a32e 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 9b270bd58..4344dc8a4 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 99b35df1b..a55693aaf 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 4b4fd9eb4..eb690a9ba 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 4dba59480..826621349 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 91f41b643..88b88840f 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 000000000..5666b77d2 --- /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 b977b46477126e52ef451b00e1df79b7a3bc3cb7 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Tue, 20 Jul 2021 09:08:15 -0400 Subject: [PATCH 046/145] chore: update CHANGELOG.md per PR review --- packages/stream_chat_flutter/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index e606d32db..aff6d7b50 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,6 @@ +## Upcoming +- Allow the various ListView widgets to be themed via ThemeData classes + ## 2.0.0 🛑️ Breaking Changes from `1.5.4` From 1711507b422391259879067a65bb9414ac1ac0d5 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Tue, 20 Jul 2021 09:15:19 -0400 Subject: [PATCH 047/145] Update packages/stream_chat_flutter/CHANGELOG.md Co-authored-by: Salvatore Giordano --- packages/stream_chat_flutter/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index aff6d7b50..89e4ac442 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,4 +1,7 @@ ## Upcoming + +✅ Added + - Allow the various ListView widgets to be themed via ThemeData classes ## 2.0.0 @@ -666,4 +669,4 @@ The property showVideoFullScreen was added mainly because of this issue brianega ## 0.0.1 -- First release \ No newline at end of file +- First release From cdee8c9ef29fbcac6c3a9d52c7bee1b58883a95e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 20 Jul 2021 19:47:56 +0530 Subject: [PATCH 048/145] 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 f55f565d9..5dab2fd5c 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 c3312c2da21c04b60b76299103dc3795f9b03956 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Tue, 20 Jul 2021 14:35:15 -0400 Subject: [PATCH 049/145] 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 e06a213a9..033e91b21 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 deba55f0f..15b64a052 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 d394dbddf..0d2094cb2 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 c49e2a6e9..8ad1b3e19 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 26e4786e8..465faff27 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 39003fb7f..5be554911 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 423445ec3..5df7406bd 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 1aacb6d2d..e89491a2c 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 050/145] 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 1360d14e3..87b4f9aa9 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 befcac037..7b193f04c 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 051/145] 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 ce488cab7..5319c02bb 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 052/145] 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 a5571430f..b5e6ddcc0 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 053/145] 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 0d2094cb2..7244f6170 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 054/145] 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 7244f6170..d54352228 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 055/145] 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 e05599e14..f4a6a136e 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 9e966fa46e8aec42d9447e605925ab2affc0f120 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 21 Jul 2021 10:27:53 +0200 Subject: [PATCH 056/145] fix floating date not working with expanded --- packages/stream_chat_flutter/lib/src/message_list_view.dart | 2 ++ 1 file changed, 2 insertions(+) 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 b843f3b41..cf7be1613 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -615,6 +615,8 @@ class _MessageListViewState extends State { Positioned _buildFloatingDateDivider(int itemCount) => Positioned( top: 20, + left: 0, + right: 0, child: BetterStreamBuilder>( initialData: _itemPositionListener.itemPositions.value, stream: _itemPositionStream, From 54ab8f251455629397cac344b695ff77a03ef501 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 21 Jul 2021 10:55:00 +0200 Subject: [PATCH 057/145] chore(ui): upgrade photo_view dependency --- 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 590ed055e..6dd29200f 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -31,7 +31,7 @@ dependencies: meta: ^1.3.0 path_provider: ^2.0.1 photo_manager: ^1.1.6 - photo_view: ^0.11.1 + photo_view: ^0.12.0 rxdart: ^0.27.0 scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 From c2f375c64c9a3ae401ccef2256db68bd507576fa Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 21 Jul 2021 11:14:41 +0200 Subject: [PATCH 058/145] chore(repo): update pr title action --- .github/workflows/pr_title.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr_title.yml b/.github/workflows/pr_title.yml index 2a53261b2..732c41644 100644 --- a/.github/workflows/pr_title.yml +++ b/.github/workflows/pr_title.yml @@ -12,13 +12,14 @@ jobs: main: runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@v2.1.0 + - uses: amannn/action-semantic-pull-request@v3.4.0 with: scopes: | llc persistence core ui + repo requireScope: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From f8911bb6e3087898e91f686ded96a3db874d3eee Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 21 Jul 2021 15:02:57 +0530 Subject: [PATCH 059/145] feat: Added tests and minor fixes --- .../lib/src/gradient_avatar.dart | 6 +- .../test/src/goldens/gradient_avatar_0.png | Bin 0 -> 35488 bytes .../test/src/goldens/gradient_avatar_1.png | Bin 0 -> 38197 bytes .../test/src/gradient_avatar_test.dart | 82 ++++++++++++++++++ 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 packages/stream_chat_flutter/test/src/goldens/gradient_avatar_0.png create mode 100644 packages/stream_chat_flutter/test/src/goldens/gradient_avatar_1.png create mode 100644 packages/stream_chat_flutter/test/src/gradient_avatar_test.dart diff --git a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart index 0cb8c41c2..dc97fb98e 100644 --- a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart @@ -47,7 +47,7 @@ class _GradientAvatarState extends State { var result = ''; for (var i = 0; i < parts.length; i++) { - result = result + parts[i][0]; + result = result + parts[i][0].toUpperCase(); } return result; @@ -112,8 +112,8 @@ class DemoPainter extends CustomPainter { final textSize = smallerSide / 3; - final dxShift = (username.length == 2 ? 1.45 : 0.75) * textSize / 2; - final dyShift = (username.length == 2 ? 1.0 : 1.8) * textSize / 2; + final dxShift = (username.length == 2 ? 1.45 : 0.9) * textSize / 2; + final dyShift = (username.length == 2 ? 1.0 : 1.65) * textSize / 2; final fontSize = username.length == 2 ? textSize : textSize * 1.5; diff --git a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_0.png b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_0.png new file mode 100644 index 0000000000000000000000000000000000000000..0906f11aca5a82f0fd4a2f6d9fd4cc712b5696cc GIT binary patch literal 35488 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≫B3&Iwv02P3D1z%;8>US?8W2DYwXeQ_HO8|V9D+*iH2-55n{y>uHEPx>TC zhl1pm;mwiku|uV-JvT9X485#IQ9kC7+XV}VaX>eZgN}w7Aa%`MaaLrKg?^kBspoXL zEuN7!69{H}VHF35c4P_7rz-K^j_y$hNr>F)i)oWn2w;{BsX`JA`{6BX+Ahm_6s5^@ z&Mx`Zu8q&DA{Vx+(WHzpf@YVP1v$P}W5rV-^tvm1u9fwr884i3GJYK$M(7d{1*3Be z&8HxMGYX9CYfI*QqFnPy@vzNw#%3+ke=X!+vAZxt(Nx0d6etI-W=ibrWp<$!DOTY? zIkGUVd|$i1z4@CcXohTD&GPN?27Fmb1(4h{J7?9Ywr^;EZ1e&+bsX_&XXMIIn`gAL z%bDOlGUSq5NXlZpk;Tu2%NcUC%BOCjZgjYXkV&UM){{#FegJ*UGOWD0quz!G%#)$y zy5gzTvP?UAJkHJBCyJD%ll~Mq&ch1|YnF!!BjJQOd)>x7rakXy=r`|T|Ic>t3>Tl~ zNNVe-17R!%?aQlJwtmRi*#3^vB<$iyVyuqrQ(&B*J!dg&l|R`bc!+@CK`ob^v6&s~ zsv<$T=sufRA$L`$*MViBp$;U8*$Job!!C3Qj;v)r8XMZawNpT$7p)qXusYS_3%YY% zgyFli6$Rx%qtv%Rt{tF2ndp!PcQ zg)eB;fs~CV!ps#DW}o8%b~(ThxJI7AdoDq2(c)^xpTWLr^&t>?|)Wmlr z48y>1FYvO5S&@eqSH$9@m6Mhf@( z5o$QnBoEM}eQSXaeKemGs@3B+9M;BmnD|fgVuX(ZLKBfWy@EYdYMfkeMV?%K_%UeS zby(1>t51-zaeIkARYtIAshtZx?AP3e6hJhrRHk*>*5^tgGdNkrk^8JpcTV(R&PH?F z-vfdqE|VyqfprJWcXA}Z$sdgM0Ady-IS<-00u)e~I>&Ji;A+8HK?5n_JfiR^MKjQ> z2*qAB)~hA)i47$ugwt)P?z)aJ&shQto8)l&{g%f(!9lC7F&A>V{Bucy{`bN?e#7U* zsfd+azs_ME{<=9I@r+T<%?hy^t%z|Pt-sN?ON0YfU6a!cUWNgA7RHxS^B7 z<*vRHbfz0vlF3wt-veA0JClN=j&;FB&x~v|SBGmm3gi}c14h~0^{6Memp;{7GM#~c zmN+g8aB`st${*3eYiNXT<*RJlApykp)F3R1&LDOTWfqq zK6=|Ms0k+WntZRIxYcMve<`JSkrbyC6Uh-jz_qFHFz85oMXm4Y?#97tLhbjJGI_1a z#F{sP2cNzW)qWN<#9~XIj`K?VG51BGZR5;k|4wm8Qkwm0vT1|eE_Hz07t|oUyu9n| zl_adrsJN`xZa*(^8$$Rax^;s`HV(iXs(>+K@z^CyH|-JtN_)9q)D3nbv9eNc^?@*h z7QOCDu06UWx)@tKs95{LVoe^=1$-^n7QO$n*3ck&|2}ICUPTvkYfq7g-WMr}KtuHY z31F>MF1pwzAttu1C^4~ZixT_4$uE|V1&oWH8oT?VhI=$X%24Eo!v7LPhqm8y>geaRU{$y|EM{{b$%c_07) literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_1.png b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_1.png new file mode 100644 index 0000000000000000000000000000000000000000..d288abf084fc623592103f82517695b17398972d GIT binary patch literal 38197 zcmeFaXIPWx8#hc_tF+b;1yK;t&>~9^l_9e#0kr}JOacl-KtP!yhLwcaN(I6ye?Tl# zK~xeT4A~}e0I7_ig)j|a2+=@5hzXF82uoqPQ55dW*4 z&9?6qzn77b*>?7fm7|Qzcb8>kw#dqE27XdU7i+>Cdd1S9lqV|`FY=<@_>JCQy5K;iNRW^jLqLc ziN3PO45jr4ggFD0m%rZVr-X&E#XT_Psp%T?@prm`0o@%OPK|H**IhMR)}DGEQdUOn zy%av({du&x=5fxT-VT*+R&8*t`EB`~m%m!u%LS~{V0Y`=)o*`(9AEz7=vRBzHpxBo zvVgGZU&^0(+xq3KJ^lj0(w2{wy?(UBmZRVj!{wvRZ#Y^8*UCAt zMS8R#(7&=dVsbWy{>SpZ${Y6mr~`s!F7IHpVTV!(xI}k(2fnRORlmoe+8XEX+pg=H z1)tP5OLq{WP&^T%k=L7IS@w&P5;7Fk$T?eh@Zam-iZXf0{soQfwwz`VDCp0rvV@a% zJJ*>0vpFXD6KjS{5RpiC;kg}j%cU15UfwCiER4ir15i?1TmHKFBG^5$$Q>=>Mctfn z7k#F~k#!V=9<$D&tGc@8@!=~CH8AOSu|gs5RVJcm2NUho8^y=Lnd|o&mIdwc-Ego& zaplhGGF#V3Bp-a~30mik@o%yTzUvHpu4(0*WBsrAWI7ML9Q|?qY3EzA31jP|E}sPJ zo@)}6aOHXJf^r$i%Z2-EPUR3XS>azRxD3fde>eF@TjpeufA2tEb@}U?7dsL4j$2SD z_m?KWtrLcQxEjOjM9s6Cd))~NWv(APPP}}5Wpw|D=K8hEJ#ExVC`{5NsR-wilt}UA zkNo|?CA-(TdjBi?JT*CwZMWBVRs_FPR9QEl<7Z+Fbk^B(^`5N1@j8h&0Jn~8*zPC5 zdUyY8B0nbp?m&H=d3WQ>1;4BdT=~FD_Mvr3H)CW88{EX3gW!_rb#3{X8dokjy zFWFl*EKZjt2sfO|1F+Q%2cZIdW&jzQBqPvJ&D0D=zGdpbNMpQ`(51-#MdqzA`hCH&)v*(n8yLlZQg7Fu(1xtKRjY4zIfi@a&FtWc$Y`+#>MAjkQN&j*FUgw z)^jDhVue#x)@nNIQobG#gIr89!mFpPc{jI47~pG@$T2_t%U~Hi`=jh+Es^5hNtrum z`=|?ew&Y`I%sPQ#Y_hd8GkO-Hyd?0~>jW9fd#p3<7IlAk@~<&Svvsnc