From 6e48f9754d7f22f43794bc13eaa793c5c9e5d50b Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 27 Aug 2021 16:08:23 +0530 Subject: [PATCH 01/29] feat(repo): add dart_code_metrics Signed-off-by: xsahil03x --- analysis_options.yaml | 43 ++++++++++++++++++- melos.yaml | 21 +++++++-- packages/stream_chat/pubspec.yaml | 1 + packages/stream_chat_flutter/pubspec.yaml | 3 +- .../stream_chat_flutter_core/pubspec.yaml | 1 + .../stream_chat_localizations/pubspec.yaml | 1 + packages/stream_chat_persistence/pubspec.yaml | 5 ++- 7 files changed, 67 insertions(+), 8 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 71604a87..c169a9a0 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,6 @@ analyzer: + plugins: + - dart_code_metrics exclude: - packages/*/lib/**/*.g.dart - packages/*/lib/src/emoji @@ -143,4 +145,43 @@ linter: - cast_nullable_to_non_nullable - unnecessary_null_checks - tighten_type_of_initializing_formals - - null_check_on_nullable_type_parameter \ No newline at end of file + - null_check_on_nullable_type_parameter + +# https://dartcodemetrics.dev/docs/getting-started/introduction +dart_code_metrics: + metrics-exclude: + - packages/*/test/** + metrics: + - weight-of-class: 0.33 + - source-lines-of-code: 90 + - cyclomatic-complexity: 20 + - maximum-nesting-level: 5 + rules: + # Dart Specific + - avoid-ignoring-return-values +# - avoid-non-null-assertion + - avoid-unused-parameters + - binary-expression-operand-order + - double-literal-format + # - member-ordering-extended + - member-ordering: + alphabetize: false + order: + - constructors + - public-fields + - private-fields + - no-boolean-literal-compare + - no-equal-arguments + - no-equal-then-else + - no-magic-number: + allowed: [ 3.14 ] + - prefer-match-file-name +# - prefer-trailing-comma: +# break-on: 2 + + # Flutter specific + - always-remove-listener + # - avoid-returning-widgets + - avoid-unnecessary-setstate + - avoid-wrapping-in-padding + diff --git a/melos.yaml b/melos.yaml index 6fee8f6f..ad15df90 100644 --- a/melos.yaml +++ b/melos.yaml @@ -11,6 +11,10 @@ scripts: run: melos run analyze && melos run format description: Run all static analysis checks + analyze:all: + run: melos run analyze && melos run metrics + description: Run all + analyze: run: | melos exec -c 5 --ignore="*example*" -- \ @@ -24,6 +28,14 @@ scripts: description: | Run `flutter format --set-exit-if-changed .` in all packages. + metrics: + run: | + melos exec -c 1 --ignore="*example*" -- \ + flutter pub run dart_code_metrics:metrics analyze lib + description: | + Run `dart_code_metrics` in all packages. + - Note: you can also rely on your IDEs Dart Analysis / Issues window. + lint:pub: run: | melos exec -c 5 --no-private --ignore="*example*" -- \ @@ -71,10 +83,13 @@ scripts: docs: run: | - npm install -g https://github.com/GetStream/stream-chat-docusaurus-cli && - npx stream-chat-docusaurus -i -s + npm install -g https://github.com/GetStream/stream-chat-docusaurus-cli && + npx stream-chat-docusaurus -i -s description: Runs the docusaurus documentation locally. +dev_dependencies: + dart_code_metrics: ^4.2.0-dev.1 + environment: sdk: '>=2.12.0 <3.0.0' - flutter: '>=1.22.4 <2.0.0' \ No newline at end of file + flutter: '>=1.17.0 <2.0.0' \ No newline at end of file diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 81657659..015dc05e 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: dev_dependencies: build_runner: ^2.0.1 + dart_code_metrics: ^4.2.0-dev.1 freezed: ^0.14.1+3 json_serializable: ^4.1.0 mocktail: ^0.1.1 diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 69cee1e9..6374fb50 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -55,10 +55,9 @@ flutter: uses-material-design: true dev_dependencies: + dart_code_metrics: ^4.2.0-dev.1 flutter_test: sdk: flutter golden_toolkit: ^0.9.0 mocktail: ^0.1.2 path: ^1.8.0 - pedantic: ^1.11.0 - diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index e5520921..a3364f08 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: stream_chat: ^3.0.0 dev_dependencies: + dart_code_metrics: ^4.2.0-dev.1 fake_async: ^1.2.0 flutter_test: sdk: flutter diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index 552ee5ec..4f8a3ad3 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -17,5 +17,6 @@ dependencies: stream_chat_flutter: ^3.0.0 dev_dependencies: + dart_code_metrics: ^4.2.0-dev.1 flutter_test: sdk: flutter diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index f6409fa2..59cd6f41 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/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: flutter: @@ -22,8 +23,8 @@ dependencies: dev_dependencies: build_runner: ^2.0.1 + dart_code_metrics: ^4.2.0-dev.1 flutter_test: sdk: flutter mocktail: ^0.1.1 - moor_generator: ^4.2.1 - pedantic: ^1.11.0 + moor_generator: ^4.2.1 \ No newline at end of file From ca7cf541a7a3252302f1f0acf5748a4c93706d0a Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 13 Sep 2021 13:05:23 +0530 Subject: [PATCH 02/29] chore(repo): fix lints Signed-off-by: xsahil03x --- analysis_options.yaml | 44 ++-- melos.yaml | 2 +- .../stream_chat/lib/src/client/channel.dart | 71 +++--- .../lib/src/core/api/channel_api.dart | 2 +- .../lib/src/core/api/device_api.dart | 2 +- .../lib/src/core/error/chat_error_code.dart | 20 +- .../lib/src/core/error/stream_chat_error.dart | 3 +- .../http/interceptor/logging_interceptor.dart | 33 ++- .../lib/src/core/models/attachment.dart | 7 +- .../lib/src/core/models/attachment_file.dart | 96 +++---- .../lib/src/core/models/channel_model.dart | 3 +- .../lib/src/core/models/message.dart | 3 +- .../lib/src/core/models/own_user.dart | 3 +- .../stream_chat/lib/src/core/models/user.dart | 10 +- packages/stream_chat/pubspec.yaml | 2 +- .../test/src/client/channel_test.dart | 4 +- .../attachment_upload_state_builder.dart | 6 +- .../lib/src/attachment/attachment_widget.dart | 2 +- .../lib/src/attachment/file_attachment.dart | 3 +- .../lib/src/attachment/giphy_attachment.dart | 2 +- .../lib/src/attachment_actions_modal.dart | 238 +++++++++--------- .../lib/src/back_button.dart | 1 + .../lib/src/channel_bottom_sheet.dart | 6 +- .../lib/src/channel_list_header.dart | 2 +- .../lib/src/channel_list_view.dart | 3 +- .../lib/src/channel_preview.dart | 160 ++++++------ .../lib/src/date_divider.dart | 21 +- .../lib/src/extension.dart | 3 +- .../lib/src/full_screen_media.dart | 148 +++++------ .../lib/src/gallery_footer.dart | 83 ++---- .../lib/src/gradient_avatar.dart | 36 +-- .../lib/src/localization/translations.dart | 12 +- .../lib/src/media_list_view.dart | 14 +- .../lib/src/message_actions_modal.dart | 23 +- .../lib/src/message_input.dart | 106 ++++---- .../lib/src/message_list_view.dart | 31 ++- .../lib/src/message_reactions_modal.dart | 25 +- .../lib/src/message_search_item.dart | 11 +- .../lib/src/message_search_list_view.dart | 53 ++-- .../lib/src/message_text.dart | 4 +- .../lib/src/message_widget.dart | 165 ++++++------ .../lib/src/quoted_message_widget.dart | 18 +- .../lib/src/reaction_bubble.dart | 2 +- .../lib/src/reaction_picker.dart | 9 +- .../lib/src/stream_chat.dart | 3 +- .../lib/src/stream_chat_theme.dart | 14 +- .../lib/src/theme/avatar_theme.dart | 1 + .../lib/src/theme/color_theme.dart | 18 +- .../lib/src/theme/gallery_footer_theme.dart | 27 +- .../lib/src/theme/message_theme.dart | 3 +- .../lib/src/typing_indicator.dart | 2 +- .../lib/src/user_item.dart | 5 +- .../lib/src/user_list_view.dart | 51 ++-- .../lib/src/user_reaction_display.dart | 60 ----- .../stream_chat_flutter/lib/src/utils.dart | 129 +++++----- .../lib/src/video_service.dart | 1 + packages/stream_chat_flutter/pubspec.yaml | 2 +- .../src/theme/channel_preview_theme_test.dart | 6 +- .../lib/src/better_stream_builder.dart | 3 +- .../lib/src/channels_bloc.dart | 6 +- .../lib/src/stream_channel.dart | 8 +- .../stream_chat_flutter_core/pubspec.yaml | 2 +- .../lib/src/stream_chat_localizations.dart | 2 +- .../lib/src/stream_chat_localizations_en.dart | 6 +- .../lib/src/stream_chat_localizations_es.dart | 6 +- .../lib/src/stream_chat_localizations_fr.dart | 6 +- .../lib/src/stream_chat_localizations_hi.dart | 6 +- .../lib/src/stream_chat_localizations_it.dart | 6 +- .../lib/src/stream_chat_localizations_ko.dart | 7 +- .../stream_chat_localizations/pubspec.yaml | 2 +- .../lib/src/dao/message_dao.dart | 20 +- .../lib/src/dao/pinned_message_dao.dart | 24 +- .../lib/src/db/moor_chat_database.dart | 47 ++-- .../src/stream_chat_persistence_client.dart | 2 +- packages/stream_chat_persistence/pubspec.yaml | 2 +- .../test/src/dao/channel_query_dao_test.dart | 4 +- 76 files changed, 1004 insertions(+), 969 deletions(-) delete mode 100644 packages/stream_chat_flutter/lib/src/user_reaction_display.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index c169a9a0..e52012e6 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,7 +3,7 @@ analyzer: - dart_code_metrics exclude: - packages/*/lib/**/*.g.dart - - packages/*/lib/src/emoji + - packages/*/lib/src/emoji/** - packages/*/lib/**/*.freezed.dart linter: @@ -152,36 +152,34 @@ dart_code_metrics: metrics-exclude: - packages/*/test/** metrics: - - weight-of-class: 0.33 - - source-lines-of-code: 90 - - cyclomatic-complexity: 20 - - maximum-nesting-level: 5 + source-lines-of-code: 90 + maximum-nesting-level: 5 + halstead-volume: 150 rules: # Dart Specific - - avoid-ignoring-return-values -# - avoid-non-null-assertion - - avoid-unused-parameters - binary-expression-operand-order - double-literal-format - # - member-ordering-extended - - member-ordering: - alphabetize: false - order: - - constructors - - public-fields - - private-fields + - prefer-match-file-name: + exclude: + - packages/*/test/** + - packages/**/util/** + - packages/**/utils.dart + - packages/stream_chat/lib/src/client/client.dart + - packages/stream_chat/lib/src/core/api/responses.dart + - packages/stream_chat/lib/src/core/api/requests.dart + - packages/stream_chat/lib/src/core/platform_detector/** + - packages/stream_chat_persistence/lib/src/db/shared/** + - packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart - no-boolean-literal-compare - - no-equal-arguments - no-equal-then-else - - no-magic-number: - allowed: [ 3.14 ] - - prefer-match-file-name -# - prefer-trailing-comma: -# break-on: 2 + - no-empty-block: + exclude: + - packages/*/test/** + - prefer-trailing-comma: + exclude: + - packages/*/test/** # Flutter specific - always-remove-listener - # - avoid-returning-widgets - avoid-unnecessary-setstate - avoid-wrapping-in-padding - diff --git a/melos.yaml b/melos.yaml index ad15df90..9c5d4e4f 100644 --- a/melos.yaml +++ b/melos.yaml @@ -88,7 +88,7 @@ scripts: description: Runs the docusaurus documentation locally. dev_dependencies: - dart_code_metrics: ^4.2.0-dev.1 + dart_code_metrics: ^4.2.0-dev.5 environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 2fd4bea9..9bcadf87 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -521,7 +521,7 @@ class Channel { state!.addMessage(message); try { - if (message.attachments.any((it) => !it.uploadState.isSuccess) == true) { + if (message.attachments.any((it) => !it.uploadState.isSuccess)) { final attachmentsUploadCompleter = Completer(); _messageAttachmentsUploadCompleter[message.id] = attachmentsUploadCompleter; @@ -849,12 +849,12 @@ class Channel { ..update(type, (value) { if (enforceUnique) return value; return value + 1; - }, ifAbsent: () => 1), + }, ifAbsent: () => 1), // ignore: prefer-trailing-comma reactionScores: {...message.reactionScores ?? {}} ..update(type, (value) { if (enforceUnique) return value; return value + 1; - }, ifAbsent: () => 1), + }, ifAbsent: () => 1), // ignore: prefer-trailing-comma latestReactions: latestReactions, ownReactions: ownReactions, ); @@ -878,7 +878,9 @@ class Channel { /// Delete a reaction from this channel. Future deleteReaction( - Message message, Reaction reaction) async { + Message message, + Reaction reaction, + ) async { final type = reaction.type; final user = _client.state.currentUser; @@ -1336,7 +1338,7 @@ class Channel { type, clearHistory: clearHistory, ); - if (clearHistory == true) { + if (clearHistory) { state!.truncate(); final cid = _cid; if (cid != null) { @@ -1501,28 +1503,27 @@ class ChannelClientState { final expiredAttachmentMessagesId = channelState.messages .where((m) => !_updatedMessagesIds.contains(m.id) && - m.attachments.isNotEmpty == true && + m.attachments.isNotEmpty && m.attachments.any((e) { - final url = e.imageUrl ?? e.assetUrl; - if (url == null || !url.contains('')) { - return false; - } - final uri = Uri.parse(url); - if (!uri.host.endsWith('stream-io-cdn.com') || - uri.queryParameters['Expires'] == null) { - return false; - } - final secondsFromEpoch = - int.parse(uri.queryParameters['Expires']!); + final url = e.imageUrl ?? e.assetUrl; + if (url == null || !url.contains('')) { + return false; + } + final uri = Uri.parse(url); + if (!uri.host.endsWith('stream-io-cdn.com') || + uri.queryParameters['Expires'] == null) { + return false; + } + final secondsFromEpoch = + int.parse(uri.queryParameters['Expires']!); final expiration = DateTime.fromMillisecondsSinceEpoch( secondsFromEpoch * 1000); - return expiration.isBefore(DateTime.now()); - }) == - true) + return expiration.isBefore(DateTime.now()); + })) .map((e) => e.id) .toList(); - if (expiredAttachmentMessagesId.isNotEmpty == true) { + if (expiredAttachmentMessagesId.isNotEmpty) { await _channel._initializedCompleter.future; _updatedMessagesIds.addAll(expiredAttachmentMessagesId); _channel.getMessagesById(expiredAttachmentMessagesId); @@ -1546,7 +1547,8 @@ class ChannelClientState { final user = e.user; updateChannelState(channelState.copyWith( members: List.from( - channelState.members..removeWhere((m) => m.userId == user!.id)), + channelState.members..removeWhere((m) => m.userId == user!.id), + ), )); })); } @@ -1647,7 +1649,7 @@ class ChannelClientState { ); addMessage(message); - if (message.pinned == true) { + if (message.pinned) { _channelState = _channelState.copyWith( pinnedMessages: [ ..._channelState.pinnedMessages, @@ -1794,9 +1796,8 @@ class ChannelClientState { channelStateStream.map((cs) => cs.pinnedMessages.toList()); /// Get channel last message. - Message? get lastMessage => _channelState.messages.isNotEmpty == true - ? _channelState.messages.last - : null; + Message? get lastMessage => + _channelState.messages.isNotEmpty ? _channelState.messages.last : null; /// Get channel last message. Stream get lastMessageStream => @@ -1859,8 +1860,8 @@ class ChannelClientState { (m) => m.user.id == message.user?.id, ) != null; - return message.silent != true && - message.shadowed != true && + return !message.silent && + !message.shadowed && message.user?.id != userId && !userIsMuted; } @@ -1898,9 +1899,7 @@ class ChannelClientState { ...updatedState.messages, ..._channelState.messages .where((m) => - updatedState.messages - .any((newMessage) => newMessage.id == m.id) != - true) + !updatedState.messages.any((newMessage) => newMessage.id == m.id)) .toList(), ]..sort(_sortByCreatedAt); @@ -1908,9 +1907,7 @@ class ChannelClientState { ...updatedState.watchers, ..._channelState.watchers .where((w) => - updatedState.watchers - .any((newWatcher) => newWatcher.id == w.id) != - true) + !updatedState.watchers.any((newWatcher) => newWatcher.id == w.id)) .toList(), ]; @@ -1922,9 +1919,7 @@ class ChannelClientState { ...updatedState.read, ..._channelState.read .where((r) => - updatedState.read - .any((newRead) => newRead.user.id == r.user.id) != - true) + !updatedState.read.any((newRead) => newRead.user.id == r.user.id)) .toList(), ]; @@ -2031,7 +2026,7 @@ class ChannelClientState { .on() .where((event) => event.user != null && - members.any((m) => m.userId == event.user!.id) == true) + members.any((m) => m.userId == event.user!.id)) .listen( (event) { final newMembers = List.from(members); diff --git a/packages/stream_chat/lib/src/core/api/channel_api.dart b/packages/stream_chat/lib/src/core/api/channel_api.dart index 619e0980..63cf5458 100644 --- a/packages/stream_chat/lib/src/core/api/channel_api.dart +++ b/packages/stream_chat/lib/src/core/api/channel_api.dart @@ -75,7 +75,7 @@ class ChannelApi { if (messageLimit != null) 'message_limit': messageLimit, // pagination - ...paginationParams.toJson() + ...paginationParams.toJson(), }), }, ); diff --git a/packages/stream_chat/lib/src/core/api/device_api.dart b/packages/stream_chat/lib/src/core/api/device_api.dart index 2d2b9d7b..f4b7f0b1 100644 --- a/packages/stream_chat/lib/src/core/api/device_api.dart +++ b/packages/stream_chat/lib/src/core/api/device_api.dart @@ -7,7 +7,7 @@ enum PushProvider { firebase, /// Send notifications using Apple's Push Notification service - apn + apn, } /// Helper extension for [PushProvider] diff --git a/packages/stream_chat/lib/src/core/error/chat_error_code.dart b/packages/stream_chat/lib/src/core/error/chat_error_code.dart index 6391428d..d597000a 100644 --- a/packages/stream_chat/lib/src/core/error/chat_error_code.dart +++ b/packages/stream_chat/lib/src/core/error/chat_error_code.dart @@ -90,7 +90,7 @@ enum ChatErrorCode { internalSystemError, /// No access to requested channels - noAccessToChannels + noAccessToChannels, } const _errorCodeWithDescription = { @@ -98,14 +98,20 @@ const _errorCodeWithDescription = { MapEntry(1000, 'Unauthorised, token not defined'), ChatErrorCode.inputError: MapEntry(4, 'Wrong data/parameter is sent to the API'), - ChatErrorCode.duplicateUsername: MapEntry(6, - 'Duplicate username is sent while enforce_unique_usernames is enabled'), + ChatErrorCode.duplicateUsername: MapEntry( + 6, + 'Duplicate username is sent while enforce_unique_usernames is enabled', + ), ChatErrorCode.messageTooLong: MapEntry(20, 'Message is too long'), ChatErrorCode.eventNotSupported: MapEntry(18, 'Event is not supported'), - ChatErrorCode.channelFeatureNotSupported: MapEntry(19, - 'The feature is currently disabled on the dashboard (i.e. Reactions & Replies)'), - ChatErrorCode.multipleNestling: MapEntry(21, - 'Multiple Levels Reply is not supported - the API only supports 1 level deep reply threads'), + ChatErrorCode.channelFeatureNotSupported: MapEntry( + 19, + 'The feature is currently disabled on the dashboard (i.e. Reactions & Replies)', + ), + ChatErrorCode.multipleNestling: MapEntry( + 21, + 'Multiple Levels Reply is not supported - the API only supports 1 level deep reply threads', + ), ChatErrorCode.customCommandEndpointCall: MapEntry(45, 'Custom Command handler returned an error'), ChatErrorCode.customCommandEndpointMissing: diff --git a/packages/stream_chat/lib/src/core/error/stream_chat_error.dart b/packages/stream_chat/lib/src/core/error/stream_chat_error.dart index 13ccef2a..ce234c84 100644 --- a/packages/stream_chat/lib/src/core/error/stream_chat_error.dart +++ b/packages/stream_chat/lib/src/core/error/stream_chat_error.dart @@ -35,7 +35,8 @@ class StreamWebSocketError extends StreamChatError { /// factory StreamWebSocketError.fromWebSocketChannelError( - WebSocketChannelException error) { + WebSocketChannelException error, + ) { final message = error.message ?? ''; return StreamWebSocketError(message); } diff --git a/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart index f78b46ea..57ffda7d 100644 --- a/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart +++ b/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart @@ -105,8 +105,11 @@ class LoggingInterceptor extends Interceptor { final formDataMap = {} ..addEntries(data.fields) ..addEntries(data.files); - _printMapAsTable(_logPrintRequest, formDataMap, - header: 'Form data | ${data.boundary}'); + _printMapAsTable( + _logPrintRequest, + formDataMap, + header: 'Form data | ${data.boundary}', + ); } else { _printBlock(_logPrintRequest, data.toString()); } @@ -201,14 +204,19 @@ class LoggingInterceptor extends Interceptor { } void _printRequestHeader( - void Function(Object) logPrint, RequestOptions options) { + void Function(Object) logPrint, + RequestOptions options, + ) { final uri = options.uri; final method = options.method; _printBoxed(logPrint, header: 'Request ║ $method ', text: uri.toString()); } - void _printLine(void Function(Object) logPrint, - [String pre = '', String suf = '╝']) => + void _printLine( + void Function(Object) logPrint, [ + String pre = '', + String suf = '╝', + ]) => logPrint('$pre${'═' * maxWidth}$suf'); void _printKV(void Function(Object) logPrint, String? key, Object? v) { @@ -227,8 +235,10 @@ class LoggingInterceptor extends Interceptor { final lines = (msg.length / maxWidth).ceil(); for (var i = 0; i < lines; ++i) { logPrint((i >= 0 ? '║ ' : '') + - msg.substring(i * maxWidth, - math.min(i * maxWidth + maxWidth, msg.length))); + msg.substring( + i * maxWidth, + math.min(i * maxWidth + maxWidth, msg.length), + )); } } @@ -301,8 +311,13 @@ class LoggingInterceptor extends Interceptor { if (compact) { logPrint('║${_indent(tabs)} $e${!isLast ? ',' : ''}'); } else { - _printPrettyMap(logPrint, e, - tabs: tabs + 1, isListItem: true, isLast: isLast); + _printPrettyMap( + logPrint, + e, + tabs: tabs + 1, + isListItem: true, + isLast: isLast, + ); } } else { logPrint('║${_indent(tabs + 2)} $e${isLast ? '' : ','}'); diff --git a/packages/stream_chat/lib/src/core/models/attachment.dart b/packages/stream_chat/lib/src/core/models/attachment.dart index 0ef9db7a..f45e318a 100644 --- a/packages/stream_chat/lib/src/core/models/attachment.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.dart @@ -56,12 +56,15 @@ class Attachment extends Equatable { /// Create a new instance from a json factory Attachment.fromJson(Map json) => _$AttachmentFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields)); + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// Create a new instance from a db data factory Attachment.fromData(Map json) => _$AttachmentFromJson(Serializer.moveToExtraDataFromRoot( - json, topLevelFields + dbSpecificTopLevelFields)); + json, + topLevelFields + dbSpecificTopLevelFields, + )); ///The attachment type based on the URL resource. This can be: audio, ///image or video diff --git a/packages/stream_chat/lib/src/core/models/attachment_file.dart b/packages/stream_chat/lib/src/core/models/attachment_file.dart index c6983b74..e855f7e3 100644 --- a/packages/stream_chat/lib/src/core/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/core/models/attachment_file.dart @@ -11,54 +11,6 @@ part 'attachment_file.freezed.dart'; part 'attachment_file.g.dart'; -/// Union class to hold various [UploadState] of a attachment. -@freezed -class UploadState with _$UploadState { - /// Preparing state of the union - const factory UploadState.preparing() = Preparing; - - /// InProgress state of the union - const factory UploadState.inProgress({ - required int uploaded, - required int total, - }) = InProgress; - - /// Success state of the union - const factory UploadState.success() = Success; - - /// Failed state of the union - const factory UploadState.failed({required String error}) = Failed; - - /// Creates a new instance from a json - factory UploadState.fromJson(Map json) => - _$UploadStateFromJson(json); -} - -/// Helper extension for UploadState -extension UploadStateX on UploadState? { - /// Returns true if state is [Preparing] - bool get isPreparing => this is Preparing; - - /// Returns true if state is [InProgress] - bool get isInProgress => this is InProgress; - - /// Returns true if state is [Success] - bool get isSuccess => this is Success; - - /// Returns true if state is [Failed] - bool get isFailed => this is Failed; -} - -Uint8List? _fromString(String? bytes) { - if (bytes == null) return null; - return Uint8List.fromList(bytes.codeUnits); -} - -String? _toString(Uint8List? bytes) { - if (bytes == null) return null; - return String.fromCharCodes(bytes); -} - /// The class that contains the information about an attachment file @JsonSerializable() class AttachmentFile { @@ -135,3 +87,51 @@ class AttachmentFile { return multiPartFile; } } + +/// Union class to hold various [UploadState] of a attachment. +@freezed +class UploadState with _$UploadState { + /// Preparing state of the union + const factory UploadState.preparing() = Preparing; + + /// InProgress state of the union + const factory UploadState.inProgress({ + required int uploaded, + required int total, + }) = InProgress; + + /// Success state of the union + const factory UploadState.success() = Success; + + /// Failed state of the union + const factory UploadState.failed({required String error}) = Failed; + + /// Creates a new instance from a json + factory UploadState.fromJson(Map json) => + _$UploadStateFromJson(json); +} + +/// Helper extension for UploadState +extension UploadStateX on UploadState? { + /// Returns true if state is [Preparing] + bool get isPreparing => this is Preparing; + + /// Returns true if state is [InProgress] + bool get isInProgress => this is InProgress; + + /// Returns true if state is [Success] + bool get isSuccess => this is Success; + + /// Returns true if state is [Failed] + bool get isFailed => this is Failed; +} + +Uint8List? _fromString(String? bytes) { + if (bytes == null) return null; + return Uint8List.fromList(bytes.codeUnits); +} + +String? _toString(Uint8List? bytes) { + if (bytes == null) return null; + return String.fromCharCodes(bytes); +} diff --git a/packages/stream_chat/lib/src/core/models/channel_model.dart b/packages/stream_chat/lib/src/core/models/channel_model.dart index 31ac4923..b6dba22b 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.dart @@ -38,7 +38,8 @@ class ChannelModel { /// Create a new instance from a json factory ChannelModel.fromJson(Map json) => _$ChannelModelFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields)); + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// The id of this channel final String id; diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index 6ad8721c..adbaf370 100644 --- a/packages/stream_chat/lib/src/core/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -81,7 +81,8 @@ class Message extends Equatable { /// Create a new instance from a json factory Message.fromJson(Map json) => _$MessageFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields)); + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// The message ID. This is either created by Stream or set client side when /// the message is added. diff --git a/packages/stream_chat/lib/src/core/models/own_user.dart b/packages/stream_chat/lib/src/core/models/own_user.dart index eb64eed3..efd60f3a 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -48,7 +48,8 @@ class OwnUser extends User { /// Create a new instance from json. factory OwnUser.fromJson(Map json) => _$OwnUserFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields)); + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// Create a new instance from [User] object. factory OwnUser.fromUser(User user) => OwnUser( diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index 5a5ef8e5..56452a60 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -118,12 +118,18 @@ class User extends Equatable { /// True if user is online. @JsonKey( - includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false) + includeIfNull: false, + toJson: Serializer.readOnly, + defaultValue: false, + ) final bool online; /// True if user is banned from the chat. @JsonKey( - includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false) + includeIfNull: false, + toJson: Serializer.readOnly, + defaultValue: false, + ) final bool banned; /// Map of custom user extraData. diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 015dc05e..38b5042b 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: dev_dependencies: build_runner: ^2.0.1 - dart_code_metrics: ^4.2.0-dev.1 + dart_code_metrics: ^4.2.0-dev.5 freezed: ^0.14.1+3 json_serializable: ^4.1.0 mocktail: ^0.1.1 diff --git a/packages/stream_chat/test/src/client/channel_test.dart b/packages/stream_chat/test/src/client/channel_test.dart index 6ec26d27..c2ea57b5 100644 --- a/packages/stream_chat/test/src/client/channel_test.dart +++ b/packages/stream_chat/test/src/client/channel_test.dart @@ -291,7 +291,7 @@ void main() { (index) => Attachment( id: 'test-attachment-id-$index', type: index.isEven ? 'image' : 'file', - file: AttachmentFile(size: 33 * index, path: 'test-file-path'), + file: AttachmentFile(size: index * 33, path: 'test-file-path'), ), ); @@ -498,7 +498,7 @@ void main() { (index) => Attachment( id: 'test-attachment-id-$index', type: index.isEven ? 'image' : 'file', - file: AttachmentFile(size: 33 * index, path: 'test-file-path'), + file: AttachmentFile(size: index * 33, path: 'test-file-path'), ), ); 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 1a0ba989..bd0aacc3 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 @@ -141,7 +141,7 @@ class _PreparingState extends StatelessWidget { uploaded: 0, total: double.maxFinite.toInt(), ), - ) + ), ], ); } @@ -181,7 +181,7 @@ class _InProgressState extends StatelessWidget { uploaded: sent, total: total, ), - ) + ), ], ); } @@ -234,7 +234,7 @@ class _FailedState extends StatelessWidget { ), ), ), - ) + ), ], ); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart index e92087b5..7a5c891b 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart @@ -78,7 +78,7 @@ class AttachmentError extends StatelessWidget { color: StreamChatTheme.of(context) .colorTheme .accentError - .withOpacity(.1), + .withOpacity(0.1), child: Center( child: Icon( Icons.error_outline, 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 4d2e8825..0b5d580f 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -253,7 +253,8 @@ class FileAttachment extends AttachmentWidget { if (message.status == MessageSendingStatus.sent) { trailingWidget = IconButton( icon: StreamSvgIcon.cloudDownload( - color: theme.colorTheme.textHighEmphasis), + color: theme.colorTheme.textHighEmphasis, + ), visualDensity: VisualDensity.compact, splashRadius: 16, onPressed: () { 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 8272a962..46786f88 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -297,7 +297,7 @@ class GiphyAttachment extends AttachmentWidget { color: StreamChatTheme.of(context) .colorTheme .textHighEmphasis - .withOpacity(.5), + .withOpacity(0.5), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), 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 b5a3cc87..f7f074c6 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart @@ -54,136 +54,134 @@ class AttachmentActionsModal extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ const SizedBox(height: kToolbarHeight), - Padding( + Container( padding: const EdgeInsets.only(right: 8), - child: Container( - width: MediaQuery.of(context).size.width * 0.5, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - ), - child: SizedBox( - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - _buildButton( - context, - context.translations.replyLabel, - StreamSvgIcon.iconCurveLineLeftUp( - size: 24, - color: theme.colorTheme.textLowEmphasis, - ), - () { - Navigator.pop(context, ReturnActionType.reply); - }, + width: MediaQuery.of(context).size.width * 0.5, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + ), + child: SizedBox( + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + _buildButton( + context, + context.translations.replyLabel, + StreamSvgIcon.iconCurveLineLeftUp( + size: 24, + color: theme.colorTheme.textLowEmphasis, ), - _buildButton( - context, - context.translations.showInChatLabel, - StreamSvgIcon.eye( - size: 24, - color: theme.colorTheme.textHighEmphasis, - ), - onShowMessage, + () { + Navigator.pop(context, ReturnActionType.reply); + }, + ), + _buildButton( + context, + context.translations.showInChatLabel, + StreamSvgIcon.eye( + size: 24, + color: theme.colorTheme.textHighEmphasis, ), - _buildButton( - context, - message.attachments[currentIndex].type == 'video' - ? context.translations.saveVideoLabel - : context.translations.saveImageLabel, - StreamSvgIcon.iconSave( - size: 24, - color: theme.colorTheme.textLowEmphasis, - ), - () { - final attachment = message.attachments[currentIndex]; - final isImage = attachment.type == 'image'; - final Future Function(Attachment, - {void Function(int, int) progressCallback}) - saveFile = fileDownloader ?? _downloadAttachment; - final Future Function(Attachment, - {void Function(int, int) progressCallback}) - saveImage = imageDownloader ?? _downloadAttachment; - final downloader = isImage ? saveImage : saveFile; - - final progressNotifier = - ValueNotifier<_DownloadProgress?>( - _DownloadProgress.initial(), - ); - - downloader( - attachment, - progressCallback: (received, total) { - progressNotifier.value = _DownloadProgress( - total, - received, - ); - }, - ).catchError((e, stk) { - progressNotifier.value = null; - }); - - // Closing attachment actions modal before opening - // attachment download dialog - Navigator.pop(context); - - showDialog( - barrierDismissible: false, - context: context, - barrierColor: theme.colorTheme.overlay, - builder: (context) => _buildDownloadProgressDialog( - context, - progressNotifier, - ), - ); - }, + onShowMessage, + ), + _buildButton( + context, + message.attachments[currentIndex].type == 'video' + ? context.translations.saveVideoLabel + : context.translations.saveImageLabel, + StreamSvgIcon.iconSave( + size: 24, + color: theme.colorTheme.textLowEmphasis, ), - if (StreamChat.of(context).currentUser?.id == - message.user?.id) - _buildButton( - context, - context.translations.deleteLabel.capitalize(), - StreamSvgIcon.delete( - size: 24, - color: theme.colorTheme.accentError, - ), - () { - final channel = StreamChannel.of(context).channel; - if (message.attachments.length > 1 || - message.text?.isNotEmpty == true) { - final remainingAttachments = [...message.attachments] - ..removeAt(currentIndex); - channel.updateMessage(message.copyWith( - attachments: remainingAttachments, - )); - Navigator.of(context) - ..pop() - ..maybePop(); - } else { - channel.deleteMessage(message); - Navigator.of(context) - ..pop() - ..maybePop(); - } + () { + final attachment = message.attachments[currentIndex]; + final isImage = attachment.type == 'image'; + final Future Function( + Attachment, { + void Function(int, int) progressCallback, + }) saveFile = fileDownloader ?? _downloadAttachment; + final Future Function( + Attachment, { + void Function(int, int) progressCallback, + }) saveImage = imageDownloader ?? _downloadAttachment; + final downloader = isImage ? saveImage : saveFile; + + final progressNotifier = ValueNotifier<_DownloadProgress?>( + _DownloadProgress.initial(), + ); + + downloader( + attachment, + progressCallback: (received, total) { + progressNotifier.value = _DownloadProgress( + total, + received, + ); }, + ).catchError((e, stk) { + progressNotifier.value = null; + }); + + // Closing attachment actions modal before opening + // attachment download dialog + Navigator.pop(context); + + showDialog( + barrierDismissible: false, + context: context, + barrierColor: theme.colorTheme.overlay, + builder: (context) => _buildDownloadProgressDialog( + context, + progressNotifier, + ), + ); + }, + ), + if (StreamChat.of(context).currentUser?.id == message.user?.id) + _buildButton( + context, + context.translations.deleteLabel.capitalize(), + StreamSvgIcon.delete( + size: 24, color: theme.colorTheme.accentError, ), - ] - .map((e) => Align( - alignment: Alignment.centerRight, - child: e, - )) - .insertBetween( - Container( - height: 1, - color: theme.colorTheme.borders, - ), + () { + final channel = StreamChannel.of(context).channel; + if (message.attachments.length > 1 || + message.text?.isNotEmpty == true) { + final remainingAttachments = [...message.attachments] + ..removeAt(currentIndex); + channel.updateMessage(message.copyWith( + attachments: remainingAttachments, + )); + Navigator.of(context) + ..pop() + ..maybePop(); + } else { + channel.deleteMessage(message); + Navigator.of(context) + ..pop() + ..maybePop(); + } + }, + color: theme.colorTheme.accentError, + ), + ] + .map((e) => Align( + alignment: Alignment.centerRight, + child: e, + )) + .insertBetween( + Container( + height: 1, + color: theme.colorTheme.borders, ), - ), + ), ), ), - ) + ), ], ); } diff --git a/packages/stream_chat_flutter/lib/src/back_button.dart b/packages/stream_chat_flutter/lib/src/back_button.dart index 6bd3e0cc..b0b04b8d 100644 --- a/packages/stream_chat_flutter/lib/src/back_button.dart +++ b/packages/stream_chat_flutter/lib/src/back_button.dart @@ -4,6 +4,7 @@ import 'package:stream_chat_flutter/src/unread_indicator.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Back button implementation +// ignore: prefer-match-file-name class StreamBackButton extends StatelessWidget { /// Constructor for creating back button const StreamBackButton({ 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 f4be36e1..74661c08 100644 --- a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart @@ -77,7 +77,8 @@ class _ChannelBottomSheetState extends State { UserAvatar( user: members .firstWhere( - (e) => e.user?.id != userAsMember.user?.id) + (e) => e.user?.id != userAsMember.user?.id, + ) .user!, constraints: const BoxConstraints( maxHeight: 64, @@ -93,7 +94,8 @@ class _ChannelBottomSheetState extends State { Text( members .firstWhere( - (e) => e.user?.id != userAsMember.user?.id) + (e) => e.user?.id != userAsMember.user?.id, + ) .user ?.name ?? '', 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 2dfc1ffb..3ebd9001 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -185,7 +185,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { ), onPressed: onNewChatButtonTap, ), - ) + ), ], title: Column( children: [ 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 96b27823..fd59489a 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -565,7 +565,8 @@ class _ChannelListViewState extends State { 'owner', ].contains(channel.state!.members .firstWhereOrNull( - (m) => m.userId == channel.client.state.currentUser?.id) + (m) => m.userId == channel.client.state.currentUser?.id, + ) ?.role)) IconSlideAction( color: backgroundColor, diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 09cbfcc4..6e3e5363 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -72,85 +72,82 @@ class ChannelPreview extends StatelessWidget { final channelPreviewTheme = ChannelPreviewTheme.of(context); final streamChatState = StreamChat.of(context); return BetterStreamBuilder( - stream: channel.isMutedStream, - initialData: channel.isMuted, - builder: (context, data) => AnimatedOpacity( - opacity: data ? 0.5 : 1, - duration: const Duration(milliseconds: 300), - child: ListTile( - visualDensity: VisualDensity.compact, - contentPadding: const EdgeInsets.symmetric( - horizontal: 8, - ), - onTap: () => onTap?.call(channel), - onLongPress: () => onLongPress?.call(channel), - leading: leading ?? ChannelAvatar(onTap: onImageTap), - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: title ?? - ChannelName( - textStyle: channelPreviewTheme.titleStyle, - ), + stream: channel.isMutedStream, + initialData: channel.isMuted, + builder: (context, data) => AnimatedOpacity( + opacity: data ? 0.5 : 1, + duration: const Duration(milliseconds: 300), + child: ListTile( + visualDensity: VisualDensity.compact, + contentPadding: const EdgeInsets.symmetric( + horizontal: 8, + ), + onTap: () => onTap?.call(channel), + onLongPress: () => onLongPress?.call(channel), + leading: leading ?? ChannelAvatar(onTap: onImageTap), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: title ?? + ChannelName( + textStyle: channelPreviewTheme.titleStyle, ), - BetterStreamBuilder>( - stream: channel.state?.membersStream, - initialData: channel.state?.members, - comparator: const ListEquality().equals, - builder: (context, members) { - if (members.isEmpty || - members.any((Member e) => - e.user!.id == - channel.client.state.currentUser?.id) != - true) { - return const SizedBox(); - } - return UnreadIndicator( - cid: channel.cid, - ); - }, - ), - ], - ), - subtitle: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible(child: subtitle ?? _buildSubtitle(context)), - sendingIndicator ?? - Builder( - builder: (context) { - final lastMessage = - channel.state?.messages.lastWhereOrNull( - (m) => !m.isDeleted && m.shadowed != true, - ); - if (lastMessage?.user?.id == - streamChatState.currentUser?.id) { - return Padding( - padding: const EdgeInsets.only(right: 4), - child: SendingIndicator( - message: lastMessage!, - size: channelPreviewTheme.indicatorIconSize, - isMessageRead: channel.state!.read - .where((element) => - element.user.id != - channel - .client.state.currentUser!.id) - .where((element) => element.lastRead - .isAfter(lastMessage.createdAt)) - .isNotEmpty == - true, - ), - ); - } - return const SizedBox(); - }, - ), - trailing ?? _buildDate(context), - ], - ), ), - )); + BetterStreamBuilder>( + stream: channel.state?.membersStream, + initialData: channel.state?.members, + comparator: const ListEquality().equals, + builder: (context, members) { + if (members.isEmpty || + !members.any((Member e) => + e.user!.id == channel.client.state.currentUser?.id)) { + return const SizedBox(); + } + return UnreadIndicator( + cid: channel.cid, + ); + }, + ), + ], + ), + subtitle: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible(child: subtitle ?? _buildSubtitle(context)), + sendingIndicator ?? + Builder( + builder: (context) { + final lastMessage = + channel.state?.messages.lastWhereOrNull( + (m) => !m.isDeleted && !m.shadowed, + ); + if (lastMessage?.user?.id == + streamChatState.currentUser?.id) { + return Padding( + padding: const EdgeInsets.only(right: 4), + child: SendingIndicator( + message: lastMessage!, + size: channelPreviewTheme.indicatorIconSize, + isMessageRead: channel.state!.read + .where((element) => + element.user.id != + channel.client.state.currentUser!.id) + .where((element) => element.lastRead + .isAfter(lastMessage.createdAt)) + .isNotEmpty, + ), + ); + } + return const SizedBox(); + }, + ), + trailing ?? _buildDate(context), + ], + ), + ), + ), + ); } Widget _buildDate(BuildContext context) => BetterStreamBuilder( @@ -246,10 +243,11 @@ class ChannelPreview extends StatelessWidget { lastMessage.mentionedUsers, lastMessage.attachments, channelPreviewTheme.subtitleStyle?.copyWith( - color: channelPreviewTheme.subtitleStyle?.color, - fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) - ? FontStyle.italic - : FontStyle.normal), + color: channelPreviewTheme.subtitleStyle?.color, + fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + ), channelPreviewTheme.subtitleStyle?.copyWith( color: channelPreviewTheme.subtitleStyle?.color, fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) diff --git a/packages/stream_chat_flutter/lib/src/date_divider.dart b/packages/stream_chat_flutter/lib/src/date_divider.dart index 7c977f60..c3b48158 100644 --- a/packages/stream_chat_flutter/lib/src/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/date_divider.dart @@ -21,25 +21,16 @@ class DateDivider extends StatelessWidget { @override Widget build(BuildContext context) { final createdAt = Jiffy(dateTime); - final now = DateTime.now(); + final now = Jiffy(DateTime.now()); - String dayInfo; - if (Jiffy(createdAt).isSame(now, Units.DAY)) { + var dayInfo = createdAt.MMMd; + if (createdAt.isSame(now, Units.DAY)) { dayInfo = context.translations.todayLabel; - } else if (Jiffy(createdAt) - .isSame(now.subtract(const Duration(days: 1)), Units.DAY)) { + } else if (createdAt.isSame(now.subtract(days: 1), Units.DAY)) { dayInfo = context.translations.yesterdayLabel; - } else if (Jiffy(createdAt).isAfter( - now.subtract(const Duration(days: 7)), - Units.DAY, - )) { + } else if (createdAt.isAfter(now.subtract(days: 7), Units.DAY)) { dayInfo = createdAt.EEEE; - } else if (Jiffy(createdAt).isAfter( - Jiffy(now).subtract(years: 1), - Units.DAY, - )) { - dayInfo = createdAt.MMMd; - } else { + } else if (createdAt.isAfter(now.subtract(years: 1), Units.DAY)) { dayInfo = createdAt.MMMd; } diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index 9551a40d..11a13630 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -127,7 +127,8 @@ extension FlipBorder on BorderRadius { topLeft: topRight, topRight: topLeft, bottomLeft: bottomRight, - bottomRight: bottomLeft) + bottomRight: bottomLeft, + ) : this; } 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 a959229c..57dadfc6 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -87,7 +87,7 @@ class _FullScreenMediaState extends State await Future.wait(videoPackages.values.map( (it) => it.initialize(), )); - setState(() {}); + setState(() {}); // ignore: no-empty-block } @override @@ -96,83 +96,83 @@ class _FullScreenMediaState extends State body: Stack( children: [ AnimatedBuilder( - animation: _controller, - builder: (context, snapshot) => PageView.builder( - controller: _pageController, - onPageChanged: (val) { + animation: _controller, + builder: (context, snapshot) => PageView.builder( + controller: _pageController, + onPageChanged: (val) { + setState(() { + _currentPage = val; + }); + }, + itemBuilder: (context, index) { + final attachment = widget.mediaAttachments[index]; + if (attachment.type == 'image' || + attachment.type == 'giphy') { + final imageUrl = attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl; + return PhotoView( + loadingBuilder: (context, image) => + const Offstage(),imageProvider: (imageUrl == null && + attachment.localUri != null && + attachment.file?.bytes != null) + ? Image.memory(attachment.file!.bytes!).image + : CachedNetworkImageProvider(imageUrl!), + maxScale: PhotoViewComputedScale.covered, + minScale: PhotoViewComputedScale.contained, + heroAttributes: PhotoViewHeroAttributes( + tag: widget.mediaAttachments, + ), + backgroundDecoration: BoxDecoration( + color: ColorTween( + begin: ChannelHeaderTheme.of(context).color, + end: Colors.black, + ).lerp(_controller.value), + ), + onTapUp: (a, b, c) { setState(() { - _currentPage = val; + _optionsShown = !_optionsShown; }); - }, - itemBuilder: (context, index) { - final attachment = widget.mediaAttachments[index]; - if (attachment.type == 'image' || - attachment.type == 'giphy') { - final imageUrl = attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl; - return PhotoView( - loadingBuilder: (context, image) => - const Offstage(), - imageProvider: (imageUrl == null && - attachment.localUri != null && - attachment.file?.bytes != null) - ? Image.memory(attachment.file!.bytes!).image - : CachedNetworkImageProvider(imageUrl!), - maxScale: PhotoViewComputedScale.covered, - minScale: PhotoViewComputedScale.contained, - heroAttributes: PhotoViewHeroAttributes( - tag: widget.mediaAttachments, - ), - backgroundDecoration: BoxDecoration( - color: ColorTween( - begin: ChannelHeaderTheme.of(context).color, - end: Colors.black, - ).lerp(_controller.value), - ), - onTapUp: (a, b, c) { - setState(() { - _optionsShown = !_optionsShown; - }); - if (_controller.isCompleted) { - _controller.reverse(); - } else { - _controller.forward(); - } - }, - ); - } else if (attachment.type == 'video') { - final controller = videoPackages[attachment.id]!; - if (!controller.initialized) { - return const Center( - child: CircularProgressIndicator(), - ); - } - return InkWell( - onTap: () { - setState(() { - _optionsShown = !_optionsShown; - }); - if (_controller.isCompleted) { - _controller.reverse(); - } else { - _controller.forward(); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 50, - ), - child: Chewie( - controller: controller.chewieController!, - ), - ), - ); + if (_controller.isCompleted) { + _controller.reverse(); + } else { + _controller.forward(); } - return Container(); }, - itemCount: widget.mediaAttachments.length, - )), + ); + } else if (attachment.type == 'video') { + final controller = videoPackages[attachment.id]!; + if (!controller.initialized) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return InkWell( + onTap: () { + setState(() { + _optionsShown = !_optionsShown; + }); + if (_controller.isCompleted) { + _controller.reverse(); + } else { + _controller.forward(); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 50, + ), + child: Chewie( + controller: controller.chewieController!, + ), + ), + ); + } + return Container(); + }, + itemCount: widget.mediaAttachments.length, + ), + ), AnimatedOpacity( opacity: _optionsShown ? 1.0 : 0.0, duration: const Duration(milliseconds: 300), diff --git a/packages/stream_chat_flutter/lib/src/gallery_footer.dart b/packages/stream_chat_flutter/lib/src/gallery_footer.dart index eaa03fb7..87ef1759 100644 --- a/packages/stream_chat_flutter/lib/src/gallery_footer.dart +++ b/packages/stream_chat_flutter/lib/src/gallery_footer.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; @@ -67,19 +66,6 @@ class GalleryFooter extends StatefulWidget implements PreferredSizeWidget { } class _GalleryFooterState extends State { - final TextEditingController _messageController = TextEditingController(); - final FocusNode _messageFocusNode = FocusNode(); - - final List _selectedChannels = []; - - @override - void initState() { - super.initState(); - _messageFocusNode.addListener(() { - setState(() {}); - }); - } - @override Widget build(BuildContext context) { const showShareButton = !kIsWeb; @@ -143,8 +129,9 @@ class _GalleryFooterState extends State { children: [ Text( context.translations.galleryPaginationText( - currentPage: widget.currentPage, - totalPages: widget.totalPages), + currentPage: widget.currentPage, + totalPages: widget.totalPages, + ), style: galleryFooterThemeData.titleTextStyle, ), ], @@ -264,29 +251,26 @@ class _GalleryFooterState extends State { children: [ media, if (widget.message.user != null) - Padding( - padding: const EdgeInsets.all(8), - child: Container( - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withOpacity(0.6), - boxShadow: [ - BoxShadow( - blurRadius: 8, - color: chatThemeData - .colorTheme.textHighEmphasis - .withOpacity(0.3), - ), - ], - ), - padding: const EdgeInsets.all(2), - child: UserAvatar( - user: widget.message.user!, - constraints: - BoxConstraints.tight(const Size(24, 24)), - showOnlineStatus: false, - ), + Container( + padding: const EdgeInsets.all(10), + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.6), + boxShadow: [ + BoxShadow( + blurRadius: 8, + color: chatThemeData + .colorTheme.textHighEmphasis + .withOpacity(0.3), + ), + ], + ), + child: UserAvatar( + user: widget.message.user!, + constraints: + BoxConstraints.tight(const Size(24, 24)), + showOnlineStatus: false, ), ), ], @@ -301,25 +285,4 @@ class _GalleryFooterState extends State { }, ); } - - /// Sends the current message - Future sendMessage() async { - final text = _messageController.text.trim(); - - final attachments = widget.message.attachments; - - _messageController.clear(); - - for (final channel in _selectedChannels) { - final message = Message( - text: text, - attachments: [attachments[widget.currentPage]], - ); - - await channel.sendMessage(message); - } - - _selectedChannels.clear(); - Navigator.pop(context); - } } diff --git a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart index 3a6aba10..a02976c8 100644 --- a/packages/stream_chat_flutter/lib/src/gradient_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/gradient_avatar.dart @@ -106,7 +106,8 @@ class DemoPainter extends CustomPainter { final p4 = pointsList.indexOf(off4); squares.add( - Offset4(p1, p2, p3, p4, i, j, rowCount, columnCount, gradient)); + Offset4(p1, p2, p3, p4, i, j, rowCount, columnCount, gradient), + ); } } @@ -123,17 +124,18 @@ class DemoPainter extends CustomPainter { final fontSize = username.length == 2 ? textSize : textSize * 1.5; TextPainter( - text: TextSpan( - text: username, - style: TextStyle( - fontFamily: fontFamily, - fontSize: fontSize, - fontWeight: FontWeight.w500, - color: Colors.white.withOpacity(0.7), - ), + text: TextSpan( + text: username, + style: TextStyle( + fontFamily: fontFamily, + fontSize: fontSize, + fontWeight: FontWeight.w500, + color: Colors.white.withOpacity(0.7), ), - textAlign: TextAlign.center, - textDirection: TextDirection.ltr) + ), + textAlign: TextAlign.center, + textDirection: TextDirection.ltr, + ) ..layout(maxWidth: size.width) ..paint( canvas, @@ -168,8 +170,8 @@ class DemoPainter extends CustomPainter { final sign1 = rand.nextInt(2) == 1 ? 1 : -1; final sign2 = rand.nextInt(2) == 1 ? 1 : -1; - final dx = 0.6 * sign1 * rand.nextInt(size.width ~/ columnCount); - final dy = 0.6 * sign2 * rand.nextInt(size.height ~/ rowCount); + final dx = sign1 * 0.6 * rand.nextInt(size.width ~/ columnCount); + final dy = sign2 * 0.6 * rand.nextInt(size.height ~/ rowCount); transformedList.add(Offset(orgDx + dx, orgDy + dy)); } @@ -223,8 +225,12 @@ class Offset4 { /// Draw the polygon on canvas void draw(Canvas canvas, List points) { final paint = Paint() - ..color = Color.fromARGB(255, Random().nextInt(255), - Random().nextInt(255), Random().nextInt(255)) + ..color = Color.fromARGB( + 255, + Random().nextInt(255), + Random().nextInt(255), + Random().nextInt(255), + ) ..shader = ui.Gradient.linear( points[p1], points[p3], diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 246cd9bf..8aa8d071 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -300,8 +300,10 @@ abstract class Translations { String get youText; /// Gallery footer pagination text - String galleryPaginationText( - {required int currentPage, required int totalPages}); + String galleryPaginationText({ + required int currentPage, + required int totalPages, + }); /// The text shown for "File" String get fileText; @@ -665,8 +667,10 @@ class DefaultTranslations implements Translations { String get youText => 'You'; @override - String galleryPaginationText( - {required int currentPage, required int totalPages}) => + String galleryPaginationText({ + required int currentPage, + required int totalPages, + }) => '${currentPage + 1} of $totalPages'; @override diff --git a/packages/stream_chat_flutter/lib/src/media_list_view.dart b/packages/stream_chat_flutter/lib/src/media_list_view.dart index 77359f8b..4c845529 100644 --- a/packages/stream_chat_flutter/lib/src/media_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/media_list_view.dart @@ -129,7 +129,7 @@ class _MediaListViewState extends State { ), ), ), - ] + ], ], ), ), @@ -144,9 +144,9 @@ class _MediaListViewState extends State { _getMedia(); } - void _getMedia() async { + Future _getMedia() async { final assetList = await PhotoManager.getAssetPathList().then((value) { - if (value.isNotEmpty == true) { + if (value.isNotEmpty) { return value.singleWhere((element) => element.isAll); } }); @@ -178,7 +178,9 @@ class MediaThumbnailProvider extends ImageProvider { @override ImageStreamCompleter load( - MediaThumbnailProvider key, DecoderCallback decode) => + MediaThumbnailProvider key, + DecoderCallback decode, + ) => MultiFrameImageStreamCompleter( codec: _loadAsync(key, decode), scale: 1, @@ -188,7 +190,9 @@ class MediaThumbnailProvider extends ImageProvider { ); Future _loadAsync( - MediaThumbnailProvider key, DecoderCallback decode) async { + MediaThumbnailProvider key, + DecoderCallback decode, + ) async { assert(key == this, 'Checks MediaThumbnailProvider'); final bytes = await media.thumbData; 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 387e31a1..a67004af 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -104,7 +104,7 @@ class _MessageActionsModalState extends State { final size = mediaQueryData.size; final user = StreamChat.of(context).currentUser; - final roughMaxSize = 2 * size.width / 3; + final roughMaxSize = size.width * 2 / 3; var messageTextLength = widget.message.text!.length; if (widget.message.quotedMessage != null) { var quotedMessageLength = @@ -119,7 +119,7 @@ class _MessageActionsModalState extends State { final roughSentenceSize = messageTextLength * (widget.messageTheme.messageTextStyle?.fontSize ?? 1) * 1.2; - final divFactor = widget.message.attachments.isNotEmpty == true + final divFactor = widget.message.attachments.isNotEmpty ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); @@ -142,14 +142,15 @@ class _MessageActionsModalState extends State { (widget.message.status == MessageSendingStatus.sent)) Align( alignment: Alignment( - user?.id == widget.message.user?.id - ? (divFactor >= 1.0 - ? -0.2 - shiftFactor - : (1.2 - divFactor)) - : (divFactor >= 1.0 - ? 0.2 + shiftFactor - : -(1.2 - divFactor)), - 0), + user?.id == widget.message.user?.id + ? (divFactor >= 1.0 + ? -0.2 - shiftFactor + : (1.2 - divFactor)) + : (divFactor >= 1.0 + ? shiftFactor + 0.2 + : -(1.2 - divFactor)), + 0, + ), child: ReactionPicker( message: widget.message, ), @@ -194,7 +195,7 @@ class _MessageActionsModalState extends State { .map((action) => _buildCustomAction( context, action, - )) + )), ].insertBetween( Container( height: 1, diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 82087fab..6f10bb5c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -373,11 +373,13 @@ class MessageInputState extends State { _parseExistingMessage(widget.editMessage ?? widget.initialMessage!); } textEditingController.addListener(_onChangedDebounced); - _focusNode.addListener(() { - if (_focusNode.hasFocus) { - _openFilePickerSection = false; - } - }); + _focusNode.addListener(_focusNodeListener); + } + + void _focusNodeListener() { + if (_focusNode.hasFocus) { + _openFilePickerSection = false; + } } int _timeOut = 0; @@ -533,7 +535,7 @@ class MessageInputState extends State { ? null : Border.all( color: _streamChatTheme.colorTheme.textHighEmphasis - .withOpacity(.5), + .withOpacity(0.5), width: 2, ), borderRadius: BorderRadius.circular(3), @@ -703,7 +705,7 @@ class MessageInputState extends State { decoration: _getInputDecoration(context), textCapitalization: TextCapitalization.sentences, ), - ) + ), ], ), ), @@ -750,31 +752,28 @@ class MessageInputState extends State { ? Row( mainAxisSize: MainAxisSize.min, children: [ - Padding( + Container( padding: const EdgeInsets.all(8), - child: Container( - constraints: BoxConstraints.tight(const Size(64, 24)), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: _streamChatTheme.colorTheme.accentPrimary, - ), - alignment: Alignment.center, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - StreamSvgIcon.lightning( + constraints: BoxConstraints.tight(const Size(64, 24)), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: _streamChatTheme.colorTheme.accentPrimary, + ), + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + StreamSvgIcon.lightning( + color: Colors.white, + size: 16, + ), + Text( + _chosenCommand?.name.toUpperCase() ?? '', + style: _streamChatTheme.textTheme.footnoteBold.copyWith( color: Colors.white, - size: 16, ), - Text( - _chosenCommand?.name.toUpperCase() ?? '', - style: - _streamChatTheme.textTheme.footnoteBold.copyWith( - color: Colors.white, - ), - ), - ], - ), + ), + ], ), ), ], @@ -1054,15 +1053,13 @@ class MessageInputState extends State { ), ), child: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: _streamChatTheme.colorTheme.inputBg, - borderRadius: BorderRadius.circular(4), - ), + child: Container( + width: 40, + height: 4, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: _streamChatTheme.colorTheme.inputBg, + borderRadius: BorderRadius.circular(4), ), ), ), @@ -1193,12 +1190,13 @@ class MessageInputState extends State { textEditingController.value = TextEditingValue( text: rejoin + textEditingController.text - .substring(textEditingController.selection.start), - selection: TextSelection.collapsed( - offset: rejoin.length, - ), - ); - _onChangedDebounced.cancel(); + .substring(textEditingController.selection.start, + ), + selection: TextSelection.collapsed( + offset: rejoin.length, + ), + ); + _onChangedDebounced.cancel(); setState(() => _showMentionsOverlay = false); }, ); @@ -1251,8 +1249,7 @@ class MessageInputState extends State { Widget _buildReplyToMessage() { if (!_hasQuotedMessage) return const Offstage(); final containsUrl = widget.quotedMessage!.attachments - .any((element) => element.titleLink != null) == - true; + .any((element) => element.titleLink != null); return QuotedMessageWidget( reverse: true, showBorder: !containsUrl, @@ -1357,7 +1354,7 @@ class MessageInputState extends State { setState(() => _attachments.remove(attachment.id)); }, fillColor: - _streamChatTheme.colorTheme.textHighEmphasis.withOpacity(.5), + _streamChatTheme.colorTheme.textHighEmphasis.withOpacity(0.5), child: Center( child: StreamSvgIcon.close( size: 24, @@ -1836,10 +1833,11 @@ class MessageInputState extends State { backgroundColor: _streamChatTheme.colorTheme.barsBg, context: context, shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - )), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), builder: (context) => Column( mainAxisSize: MainAxisSize.min, children: [ @@ -1872,7 +1870,7 @@ class MessageInputState extends State { ), Container( color: - _streamChatTheme.colorTheme.textHighEmphasis.withOpacity(.08), + _streamChatTheme.colorTheme.textHighEmphasis.withOpacity(0.08), height: 1, ), Row( @@ -1905,6 +1903,7 @@ class MessageInputState extends State { @override void dispose() { textEditingController.dispose(); + _focusNode.removeListener(_focusNodeListener); _stopSlowMode(); _onChangedDebounced.cancel(); super.dispose(); @@ -2015,7 +2014,8 @@ class _PickerWidgetState extends State<_PickerWidget> { Text( context.translations.enablePhotoAndVideoAccessMessage, style: widget.streamChatTheme.textTheme.body.copyWith( - color: widget.streamChatTheme.colorTheme.textLowEmphasis), + color: widget.streamChatTheme.colorTheme.textLowEmphasis, + ), textAlign: TextAlign.center, ), const SizedBox(height: 6), 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 e67f00dd..018cd908 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -60,6 +60,7 @@ typedef OnMessageTap = void Function(Message); typedef ReplyTapCallback = void Function(Message); /// Class for message details +// ignore: prefer-match-file-name class MessageDetails { /// Constructor for creating [MessageDetails] MessageDetails( @@ -355,8 +356,9 @@ class _MessageListViewState extends State { child: Text( context.translations.emptyChatMessagesText, style: _streamTheme.textTheme.footnote.copyWith( - color: _streamTheme.colorTheme.textHighEmphasis - .withOpacity(.5)), + color: _streamTheme.colorTheme.textHighEmphasis + .withOpacity(0.5), + ), ), ), messageListBuilder: widget.messageListBuilder ?? @@ -368,8 +370,9 @@ class _MessageListViewState extends State { child: Text( context.translations.genericErrorText, style: _streamTheme.textTheme.footnote.copyWith( - color: _streamTheme.colorTheme.textHighEmphasis - .withOpacity(.5)), + color: _streamTheme.colorTheme.textHighEmphasis + .withOpacity(0.5), + ), ), ), ); @@ -380,7 +383,7 @@ class _MessageListViewState extends State { if (_messageListLength != null) { if (_bottomPaginationActive || (_inBetweenList && _upToDate)) { - if (_itemPositionListener.itemPositions.value.isNotEmpty == true) { + if (_itemPositionListener.itemPositions.value.isNotEmpty) { final first = _itemPositionListener.itemPositions.value.first; final diff = newMessagesListLength - _messageListLength!; if (diff > 0) { @@ -973,7 +976,7 @@ class _MessageListViewState extends State { final allRead = readList.length >= (channel.memberCount ?? 0) - 1; final hasFileAttachment = - message.attachments.any((it) => it.type == 'file') == true; + message.attachments.any((it) => it.type == 'file'); final isThreadMessage = message.parentId != null && message.showInChannel == true; @@ -1005,7 +1008,7 @@ class _MessageListViewState extends State { final isOnlyEmoji = message.text?.isOnlyEmoji ?? false; final hasUrlAttachment = - message.attachments.any((it) => it.titleLink != null) == true; + message.attachments.any((it) => it.titleLink != null); final borderSide = isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment) @@ -1250,10 +1253,11 @@ class _MessageListViewState extends State { if (widget.onThreadTap != null) { _onThreadTap = (Message message) { widget.onThreadTap!( - message, - widget.threadBuilder != null - ? widget.threadBuilder!(context, message) - : null); + message, + widget.threadBuilder != null + ? widget.threadBuilder!(context, message) + : null, + ); }; } else if (widget.threadBuilder != null) { _onThreadTap = (Message message) { @@ -1262,7 +1266,8 @@ class _MessageListViewState extends State { MaterialPageRoute( builder: (_) => BetterStreamBuilder( stream: streamChannel!.channel.state!.messagesStream.map( - (messages) => messages.firstWhere((m) => m.id == message.id)), + (messages) => messages.firstWhere((m) => m.id == message.id), + ), initialData: message, builder: (_, data) => StreamChannel( channel: streamChannel!.channel, @@ -1309,7 +1314,7 @@ class _LoadingIndicator extends StatelessWidget { stream: stream, initialData: false, errorBuilder: (context, error) => Container( - color: streamTheme.colorTheme.accentError.withOpacity(.2), + color: streamTheme.colorTheme.accentError.withOpacity(0.2), child: Center( child: Text(context.translations.loadingMessagesError), ), 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 e9f2e55e..6a73c439 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -46,11 +46,11 @@ class MessageReactionsModal extends StatelessWidget { final size = MediaQuery.of(context).size; final user = StreamChat.of(context).currentUser; - final roughMaxSize = 2 * size.width / 3; + final roughMaxSize = size.width * 2 / 3; var messageTextLength = message.text!.length; if (message.quotedMessage != null) { var quotedMessageLength = message.quotedMessage!.text!.length + 40; - if (message.quotedMessage!.attachments.isNotEmpty == true) { + if (message.quotedMessage!.attachments.isNotEmpty) { quotedMessageLength += 40; } if (quotedMessageLength > messageTextLength) { @@ -60,7 +60,7 @@ class MessageReactionsModal extends StatelessWidget { final roughSentenceSize = messageTextLength * (messageTheme.messageTextStyle?.fontSize ?? 1) * 1.2; - final divFactor = message.attachments.isNotEmpty == true + final divFactor = message.attachments.isNotEmpty ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); @@ -80,14 +80,15 @@ class MessageReactionsModal extends StatelessWidget { (message.status == MessageSendingStatus.sent)) Align( alignment: Alignment( - user!.id == message.user!.id - ? (divFactor >= 1.0 - ? -0.2 - shiftFactor - : (1.2 - divFactor)) - : (divFactor >= 1.0 - ? 0.2 + shiftFactor - : -(1.2 - divFactor)), - 0), + user!.id == message.user!.id + ? (divFactor >= 1.0 + ? -0.2 - shiftFactor + : (1.2 - divFactor)) + : (divFactor >= 1.0 + ? shiftFactor + 0.2 + : -(1.2 - divFactor)), + 0, + ), child: ReactionPicker( message: message, ), @@ -102,7 +103,7 @@ class MessageReactionsModal extends StatelessWidget { context, user, ), - ] + ], ], ), ), 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 89d43531..07f9f1c4 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_item.dart @@ -146,11 +146,12 @@ class MessageSearchItem extends StatelessWidget { } TextSpan _getDisplayText( - String text, - List mentions, - List attachments, - TextStyle? normalTextStyle, - TextStyle? mentionsTextStyle) { + String text, + List mentions, + List attachments, + TextStyle? normalTextStyle, + TextStyle? mentionsTextStyle, + ) { final textList = text.split(' '); final resList = []; for (final e in textList) { 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 daf34ff4..a5edc203 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 @@ -217,7 +217,9 @@ class _MessageSearchListViewState extends State { ); Widget _listItemBuilder( - BuildContext context, GetMessageResponse getMessageResponse) { + BuildContext context, + GetMessageResponse getMessageResponse, + ) { if (widget.itemBuilder != null) { return widget.itemBuilder!(context, getMessageResponse); } @@ -231,33 +233,34 @@ class _MessageSearchListViewState extends State { final messageSearchBloc = MessageSearchBloc.of(context); return StreamBuilder( - stream: messageSearchBloc.queryMessagesLoading, - initialData: false, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Container( - color: StreamChatTheme.of(context) - .colorTheme - .accentError - .withOpacity(.2), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Center( - child: Text(context.translations.loadingMessagesError), - ), - ), - ); - } + stream: messageSearchBloc.queryMessagesLoading, + initialData: false, + builder: (context, snapshot) { + if (snapshot.hasError) { return Container( - height: 100, - padding: const EdgeInsets.all(32), - child: Center( - child: snapshot.data! - ? const CircularProgressIndicator() - : Container(), + color: StreamChatTheme.of(context) + .colorTheme + .accentError + .withOpacity(0.2), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Center( + child: Text(context.translations.loadingMessagesError), + ), ), ); - }); + } + return Container( + height: 100, + padding: const EdgeInsets.all(32), + child: Center( + child: snapshot.data! + ? const CircularProgressIndicator() + : Container(), + ), + ); + }, + ); } Widget _buildListView(List data) { diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index c1aad231..6cdcd760 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -88,7 +88,9 @@ class MessageText extends StatelessWidget { for (final user in message.mentionedUsers.toSet()) { final userName = user.name; messageTextToRender = messageTextToRender.replaceAll( - '@$userName', '[@$userName](@${userName.replaceAll(' ', '')})'); + '@$userName', + '[@$userName](@${userName.replaceAll(' ', '')})', + ); } return messageTextToRender; } diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 6731e15a..9d6269f1 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -575,8 +575,7 @@ class _MessageWidgetState extends State bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed; bool get isGiphy => - widget.message.attachments.any((element) => element.type == 'giphy') == - true; + widget.message.attachments.any((element) => element.type == 'giphy'); bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true; @@ -596,7 +595,7 @@ class _MessageWidgetState extends State isDeleted; @override - bool get wantKeepAlive => widget.message.attachments.isNotEmpty == true; + bool get wantKeepAlive => widget.message.attachments.isNotEmpty; late StreamChatThemeData _streamChatTheme; late StreamChatState _streamChat; @@ -674,7 +673,10 @@ class _MessageWidgetState extends State child: PortalEntry( portal: Container( transform: Matrix4.translationValues( - widget.reverse ? 12 : -12, 0, 0), + widget.reverse ? 12 : -12, + 0, + 0, + ), constraints: const BoxConstraints( maxWidth: 22 * 6.0, ), @@ -704,13 +706,15 @@ class _MessageWidgetState extends State ? Container( // ignore: lines_longer_than_80_chars margin: EdgeInsets.symmetric( - horizontal: - // ignore: lines_longer_than_80_chars - widget.showUserAvatar == - // ignore: lines_longer_than_80_chars - DisplayWidget.gone - ? 0 - : 4.0), + horizontal: + // ignore: lines_longer_than_80_chars + widget.showUserAvatar == + // ignore: lines_longer_than_80_chars + DisplayWidget + .gone + ? 0 + : 4.0, + ), child: DeletedMessage( borderRadiusGeometry: widget .borderRadiusGeometry, @@ -794,7 +798,7 @@ class _MessageWidgetState extends State widget.message.user != null) ...[ _buildUserAvatar(), const SizedBox(width: 4), - ] + ], ], ), if (showBottomRow) @@ -856,7 +860,11 @@ class _MessageWidgetState extends State : chatThemeData.ownMessageTheme, reverse: widget.reverse, padding: EdgeInsets.only( - right: 8, left: 8, top: 8, bottom: hasNonUrlAttachments ? 8 : 0), + right: 8, + left: 8, + top: 8, + bottom: hasNonUrlAttachments ? 8 : 0, + ), ); } @@ -1053,64 +1061,64 @@ class _MessageWidgetState extends State final channel = StreamChannel.of(context).channel; showDialog( - useRootNavigator: false, - context: context, - barrierColor: _streamChatTheme.colorTheme.overlay, - builder: (context) => StreamChannel( - channel: channel, - child: MessageActionsModal( - messageWidget: widget.copyWith( - key: const Key('MessageWidget'), - message: widget.message.copyWith( - text: (widget.message.text?.length ?? 0) > 200 - ? '${widget.message.text!.substring(0, 200)}...' - : widget.message.text, - ), - showReactions: false, - showUsername: false, - showTimestamp: false, - translateUserAvatar: false, - showSendingIndicator: false, - padding: const EdgeInsets.all(0), - showReactionPickerIndicator: widget.showReactions && - (widget.message.status == MessageSendingStatus.sent), - showPinHighlight: false, - showUserAvatar: widget.message.user!.id == - channel.client.state.currentUser!.id - ? DisplayWidget.gone - : DisplayWidget.show, - ), - onCopyTap: (message) => - Clipboard.setData(ClipboardData(text: message.text)), - messageTheme: widget.messageTheme, - reverse: widget.reverse, - showDeleteMessage: widget.showDeleteMessage || isDeleteFailed, - message: widget.message, - editMessageInputBuilder: widget.editMessageInputBuilder, - onReplyTap: widget.onReplyTap, - onThreadReplyTap: widget.onThreadTap, - showResendMessage: widget.showResendMessage && - (isSendFailed || isUpdateFailed), - showCopyMessage: widget.showCopyMessage && - !isFailedState && - widget.message.text?.trim().isNotEmpty == true, - showEditMessage: widget.showEditMessage && - !isDeleteFailed && - widget.message.attachments - .any((element) => element.type == 'giphy') != - true, - showReactions: widget.showReactions, - showReplyMessage: widget.showReplyMessage && - !isFailedState && - widget.onReplyTap != null, - showThreadReplyMessage: widget.showThreadReplyMessage && - !isFailedState && - widget.onThreadTap != null, - showFlagButton: widget.showFlagButton, - showPinButton: widget.showPinButton, - customActions: widget.customActions, - ), - )); + useRootNavigator: false, + context: context, + barrierColor: _streamChatTheme.colorTheme.overlay, + builder: (context) => StreamChannel( + channel: channel, + child: MessageActionsModal( + messageWidget: widget.copyWith( + key: const Key('MessageWidget'), + message: widget.message.copyWith( + text: (widget.message.text?.length ?? 0) > 200 + ? '${widget.message.text!.substring(0, 200)}...' + : widget.message.text, + ), + showReactions: false, + showUsername: false, + showTimestamp: false, + translateUserAvatar: false, + showSendingIndicator: false, + padding: const EdgeInsets.all(0), + showReactionPickerIndicator: widget.showReactions && + (widget.message.status == MessageSendingStatus.sent), + showPinHighlight: false, + showUserAvatar: + widget.message.user!.id == channel.client.state.currentUser!.id + ? DisplayWidget.gone + : DisplayWidget.show, + ), + onCopyTap: (message) => + Clipboard.setData(ClipboardData(text: message.text)), + messageTheme: widget.messageTheme, + reverse: widget.reverse, + showDeleteMessage: widget.showDeleteMessage || isDeleteFailed, + message: widget.message, + editMessageInputBuilder: widget.editMessageInputBuilder, + onReplyTap: widget.onReplyTap, + onThreadReplyTap: widget.onThreadTap, + showResendMessage: + widget.showResendMessage && (isSendFailed || isUpdateFailed), + showCopyMessage: widget.showCopyMessage && + !isFailedState && + widget.message.text?.trim().isNotEmpty == true, + showEditMessage: widget.showEditMessage && + !isDeleteFailed && + !widget.message.attachments + .any((element) => element.type == 'giphy'), + showReactions: widget.showReactions, + showReplyMessage: widget.showReplyMessage && + !isFailedState && + widget.onReplyTap != null, + showThreadReplyMessage: widget.showThreadReplyMessage && + !isFailedState && + widget.onThreadTap != null, + showFlagButton: widget.showFlagButton, + showPinButton: widget.showPinButton, + customActions: widget.customActions, + ), + ), + ); } void _showMessageReactionsModalBottomSheet(BuildContext context) { @@ -1290,8 +1298,9 @@ class _MessageWidgetState extends State ? widget.messageTheme.copyWith( messageTextStyle: widget.messageTheme.messageTextStyle!.copyWith( - fontSize: 42, - )) + fontSize: 42, + ), + ) : widget.messageTheme, ), ), @@ -1325,7 +1334,7 @@ class _MessageWidgetState extends State fontSize: 13, fontWeight: FontWeight.w400, ), - ) + ), ], ), ); @@ -1431,8 +1440,12 @@ class _ThreadReplyPainter extends CustomPainter { final path = Path() ..moveTo(reverse ? size.width : 0, 0) - ..quadraticBezierTo(reverse ? size.width : 0, size.height * 0.38, - reverse ? size.width : 0, size.height * 0.50) + ..quadraticBezierTo( + reverse ? size.width : 0, + size.height * 0.38, + reverse ? size.width : 0, + size.height * 0.5, + ) ..quadraticBezierTo( reverse ? size.width : 0, size.height, diff --git a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart index 01068abd..500ac039 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -35,6 +35,7 @@ class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> { super.initState(); _controller = VideoPlayerController.network(widget.attachment.assetUrl!) ..initialize().then((_) { + // ignore: no-empty-block setState(() {}); //when your thumbnail will show. }); } @@ -95,10 +96,10 @@ class QuotedMessageWidget extends StatelessWidget { /// Callback for tap on widget final GestureTapCallback? onTap; - bool get _hasAttachments => message.attachments.isNotEmpty == true; + bool get _hasAttachments => message.attachments.isNotEmpty; bool get _containsLinkAttachment => - message.attachments.any((element) => element.titleLink != null) == true; + message.attachments.any((element) => element.titleLink != null); bool get _containsText => message.text?.isNotEmpty == true; @@ -140,12 +141,14 @@ class QuotedMessageWidget extends StatelessWidget { messageTheme: isOnlyEmoji && _containsText ? messageTheme.copyWith( messageTextStyle: messageTheme.messageTextStyle?.copyWith( - fontSize: 32, - )) + fontSize: 32, + ), + ) : messageTheme.copyWith( messageTextStyle: messageTheme.messageTextStyle?.copyWith( - fontSize: 12, - )), + fontSize: 12, + ), + ), ), ), ].insertBetween(const SizedBox(width: 8)); @@ -275,7 +278,8 @@ class QuotedMessageWidget extends StatelessWidget { height: 32, width: 32, child: getFileTypeImage( - attachment.extraData['mime_type'] as String?), + attachment.extraData['mime_type'] as String?, + ), ), }; diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index ecc2b27a..203b8481 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -142,7 +142,7 @@ class ReactionBubble extends StatelessWidget { size: 16, color: (!highlightOwnReactions || reaction.user?.id == userId) ? chatThemeData.colorTheme.accentPrimary - : chatThemeData.colorTheme.textHighEmphasis.withOpacity(.5), + : chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.5), ), ); } diff --git a/packages/stream_chat_flutter/lib/src/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reaction_picker.dart index b5a685bd..2991a1bb 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_picker.dart @@ -62,10 +62,11 @@ class _ReactionPickerState extends State mainAxisSize: MainAxisSize.min, children: reactionIcons .map((reactionIcon) { - final ownReactionIndex = widget.message.ownReactions - ?.indexWhere( - (reaction) => reaction.type == reactionIcon.type) ?? - -1; + final ownReactionIndex = + widget.message.ownReactions?.indexWhere( + (reaction) => reaction.type == reactionIcon.type, + ) ?? + -1; final index = reactionIcons.indexOf(reactionIcon); final child = reactionIcon.builder( diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index af617d34..ba8bcf64 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -75,7 +75,8 @@ class StreamChat extends StatefulWidget { if (streamChatState == null) { throw Exception( - 'You must have a StreamChat widget at the top of your widget tree'); + 'You must have a StreamChat widget at the top of your widget tree', + ); } return streamChatState; 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 0e50f132..0e429225 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -145,7 +145,7 @@ class StreamChatThemeData { ) { final accentColor = colorTheme.accentPrimary; final iconTheme = - IconThemeData(color: colorTheme.textHighEmphasis.withOpacity(.5)); + IconThemeData(color: colorTheme.textHighEmphasis.withOpacity(0.5)); final channelHeaderTheme = ChannelHeaderThemeData( avatarTheme: AvatarThemeData( borderRadius: BorderRadius.circular(20), @@ -174,7 +174,7 @@ class StreamChatThemeData { color: const Color(0xff7A7A7A), ), lastMessageAtStyle: textTheme.footnote.copyWith( - color: colorTheme.textHighEmphasis.withOpacity(.5), + color: colorTheme.textHighEmphasis.withOpacity(0.5), ), indicatorIconSize: 16, ); @@ -278,7 +278,7 @@ class StreamChatThemeData { return StreamSvgIcon.loveReaction( color: highlighted ? theme.colorTheme.accentPrimary - : theme.primaryIconTheme.color!.withOpacity(.5), + : theme.primaryIconTheme.color!.withOpacity(0.5), size: size, ); }, @@ -290,7 +290,7 @@ class StreamChatThemeData { return StreamSvgIcon.thumbsUpReaction( color: highlighted ? theme.colorTheme.accentPrimary - : theme.primaryIconTheme.color!.withOpacity(.5), + : theme.primaryIconTheme.color!.withOpacity(0.5), size: size, ); }, @@ -302,7 +302,7 @@ class StreamChatThemeData { return StreamSvgIcon.thumbsDownReaction( color: highlighted ? theme.colorTheme.accentPrimary - : theme.primaryIconTheme.color!.withOpacity(.5), + : theme.primaryIconTheme.color!.withOpacity(0.5), size: size, ); }, @@ -314,7 +314,7 @@ class StreamChatThemeData { return StreamSvgIcon.lolReaction( color: highlighted ? theme.colorTheme.accentPrimary - : theme.primaryIconTheme.color!.withOpacity(.5), + : theme.primaryIconTheme.color!.withOpacity(0.5), size: size, ); }, @@ -326,7 +326,7 @@ class StreamChatThemeData { return StreamSvgIcon.wutReaction( color: highlighted ? theme.colorTheme.accentPrimary - : theme.primaryIconTheme.color!.withOpacity(.5), + : theme.primaryIconTheme.color!.withOpacity(0.5), size: size, ); }, diff --git a/packages/stream_chat_flutter/lib/src/theme/avatar_theme.dart b/packages/stream_chat_flutter/lib/src/theme/avatar_theme.dart index 6c287638..738fa0de 100644 --- a/packages/stream_chat_flutter/lib/src/theme/avatar_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/avatar_theme.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// A style that overrides the default appearance of various avatar widgets. +// ignore: prefer-match-file-name class AvatarThemeData with Diagnosticable { /// Creates an [AvatarThemeData]. const AvatarThemeData({ diff --git a/packages/stream_chat_flutter/lib/src/theme/color_theme.dart b/packages/stream_chat_flutter/lib/src/theme/color_theme.dart index d021c6cf..567c7b3e 100644 --- a/packages/stream_chat_flutter/lib/src/theme/color_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/color_theme.dart @@ -25,13 +25,23 @@ class ColorTheme { stops: [0, 1], ), this.borderTop = const Effect( - sigmaX: 0, sigmaY: -1, color: Color(0xff000000), blur: 0, alpha: 0.08), + sigmaX: 0, + sigmaY: -1, + color: Color(0xff000000), + blur: 0, + alpha: 0.08, + ), this.borderBottom = const Effect( - sigmaX: 0, sigmaY: 1, color: Color(0xff000000), blur: 0, alpha: 0.08), + sigmaX: 0, + sigmaY: 1, + color: Color(0xff000000), + blur: 0, + alpha: 0.08, + ), this.shadowIconButton = const Effect( - sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4), + sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4,), this.modalShadow = const Effect( - sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8), + sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8,), }) : brightness = Brightness.light; /// Initialise with dark theme diff --git a/packages/stream_chat_flutter/lib/src/theme/gallery_footer_theme.dart b/packages/stream_chat_flutter/lib/src/theme/gallery_footer_theme.dart index bdf273e7..a227b830 100644 --- a/packages/stream_chat_flutter/lib/src/theme/gallery_footer_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/gallery_footer_theme.dart @@ -151,11 +151,20 @@ class GalleryFooterThemeData with Diagnosticable { bottomSheetBarrierColor: Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t), bottomSheetBackgroundColor: Color.lerp( - a.bottomSheetBackgroundColor, b.bottomSheetBackgroundColor, t), + a.bottomSheetBackgroundColor, + b.bottomSheetBackgroundColor, + t, + ), bottomSheetPhotosTextStyle: TextStyle.lerp( - a.bottomSheetPhotosTextStyle, b.bottomSheetPhotosTextStyle, t), + a.bottomSheetPhotosTextStyle, + b.bottomSheetPhotosTextStyle, + t, + ), bottomSheetCloseIconColor: Color.lerp( - a.bottomSheetCloseIconColor, b.bottomSheetCloseIconColor, t), + a.bottomSheetCloseIconColor, + b.bottomSheetCloseIconColor, + t, + ), ); /// Merges one [GalleryFooterThemeData] with another. @@ -208,10 +217,16 @@ class GalleryFooterThemeData with Diagnosticable { ..add(ColorProperty('gridIconButtonColor', gridIconButtonColor)) ..add(ColorProperty('bottomSheetBarrierColor', bottomSheetBarrierColor)) ..add(ColorProperty( - 'bottomSheetBackgroundColor', bottomSheetBackgroundColor)) + 'bottomSheetBackgroundColor', + bottomSheetBackgroundColor, + )) ..add(DiagnosticsProperty( - 'bottomSheetPhotosTextStyle', bottomSheetPhotosTextStyle)) + 'bottomSheetPhotosTextStyle', + bottomSheetPhotosTextStyle, + )) ..add(ColorProperty( - 'bottomSheetCloseIconColor', bottomSheetCloseIconColor)); + 'bottomSheetCloseIconColor', + bottomSheetCloseIconColor, + )); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/message_theme.dart b/packages/stream_chat_flutter/lib/src/theme/message_theme.dart index 023a2917..334f4850 100644 --- a/packages/stream_chat_flutter/lib/src/theme/message_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/message_theme.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/theme/avatar_theme.dart'; /// Class for getting message theme +// ignore: prefer-match-file-name class MessageThemeData with Diagnosticable { /// Creates a [MessageThemeData]. const MessageThemeData({ @@ -99,7 +100,7 @@ class MessageThemeData with Diagnosticable { messageTextStyle: TextStyle.lerp(a.messageTextStyle, b.messageTextStyle, t), reactionsBackgroundColor: Color.lerp( - a.reactionsBackgroundColor, b.reactionsBackgroundColor, t), + a.reactionsBackgroundColor, b.reactionsBackgroundColor, t,), reactionsBorderColor: Color.lerp(a.messageBorderColor, b.reactionsBorderColor, t), reactionsMaskColor: diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart index 5d425e58..d961a8ff 100644 --- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart @@ -48,7 +48,7 @@ class TypingIndicator extends StatelessWidget { .map((e) => e.key)), builder: (context, data) => AnimatedSwitcher( duration: const Duration(milliseconds: 300), - child: data.isNotEmpty == true + child: data.isNotEmpty ? Padding( key: const Key('main'), padding: padding, diff --git a/packages/stream_chat_flutter/lib/src/user_item.dart b/packages/stream_chat_flutter/lib/src/user_item.dart index a2622385..cbaa0820 100644 --- a/packages/stream_chat_flutter/lib/src/user_item.dart +++ b/packages/stream_chat_flutter/lib/src/user_item.dart @@ -90,12 +90,13 @@ class UserItem extends StatelessWidget { Widget _buildLastActive(BuildContext context) { final chatTheme = StreamChatTheme.of(context); return Text( - user.online == true + user.online ? context.translations.userOnlineText : '${context.translations.userLastOnlineText} ' '${Jiffy(user.lastActive).fromNow()}', style: chatTheme.textTheme.footnote.copyWith( - color: chatTheme.colorTheme.textHighEmphasis.withOpacity(.5)), + color: chatTheme.colorTheme.textHighEmphasis.withOpacity(0.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 f7c9fa72..8697381a 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -73,7 +73,7 @@ class UserListView extends StatefulWidget { this.listBuilder, this.userListController, }) : assert( - crossAxisCount == 1 || groupAlphabetically == false, + crossAxisCount == 1 || !groupAlphabetically, 'Cannot group alphabetically when crossAxisCount > 1', ), limit = limit ?? pagination?.limit ?? 30, @@ -407,33 +407,34 @@ class _UserListViewState extends State Widget _buildQueryProgressIndicator(context, UsersBlocState usersProvider) => StreamBuilder( - stream: usersProvider.queryUsersLoading, - initialData: false, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Container( - color: StreamChatTheme.of(context) - .colorTheme - .accentError - .withOpacity(.2), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Center( - child: Text(context.translations.loadingUsersError), - ), - ), - ); - } + stream: usersProvider.queryUsersLoading, + initialData: false, + builder: (context, snapshot) { + if (snapshot.hasError) { return Container( - height: 100, - padding: const EdgeInsets.all(32), - child: Center( - child: snapshot.data! - ? const CircularProgressIndicator() - : Container(), + color: StreamChatTheme.of(context) + .colorTheme + .accentError + .withOpacity(0.2), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Center( + child: Text(context.translations.loadingUsersError), + ), ), ); - }); + } + return Container( + height: 100, + padding: const EdgeInsets.all(32), + child: Center( + child: snapshot.data! + ? const CircularProgressIndicator() + : Container(), + ), + ); + }, + ); Widget _separatorBuilder(context, i) => Container( height: 1, diff --git a/packages/stream_chat_flutter/lib/src/user_reaction_display.dart b/packages/stream_chat_flutter/lib/src/user_reaction_display.dart deleted file mode 100644 index b5ef623e..00000000 --- a/packages/stream_chat_flutter/lib/src/user_reaction_display.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/user_avatar.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - -/// Displays a list of users who reacted -class UserReactionDisplay extends StatelessWidget { - /// Constructor for creating a [UserReactionDisplay] - const UserReactionDisplay({ - Key? key, - required this.reactionToEmoji, - required this.message, - this.size = 30, - }) : super(key: key); - - /// Reaction map - final Map reactionToEmoji; - - /// Message which is reacted to - final Message message; - - /// Size of Icon - final double size; - - @override - Widget build(BuildContext context) => Container( - color: Colors.black87, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: reactionToEmoji.keys.map((reactionType) { - final firstUserReaction = message.latestReactions! - .firstWhere((element) => element.type == reactionType, - //ignore: unnecessary_parenthesis - orElse: (() => null) as Reaction Function()?); - - if (firstUserReaction.user == null) { - return IconButton( - iconSize: size, - icon: Container(), - onPressed: null, - ); - } - - return IconButton( - iconSize: size, - icon: UserAvatar( - user: firstUserReaction.user!, - constraints: BoxConstraints( - maxHeight: size - 5, - maxWidth: size - 5, - ), - onTap: (user) {}, - ), - onPressed: () {}, - ); - }).toList(), - ), - ); -} diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 9b85e8c2..b4ca48f0 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -29,78 +29,82 @@ Future showConfirmationDialog( }) { final chatThemeData = StreamChatTheme.of(context); return showModalBottomSheet( - useRootNavigator: false, - backgroundColor: chatThemeData.colorTheme.barsBg, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( + useRootNavigator: false, + backgroundColor: chatThemeData.colorTheme.barsBg, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), - )), - builder: (context) { - final effect = chatThemeData.colorTheme.borderTop; - return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 26), - if (icon != null) icon, - const SizedBox(height: 26), + ), + ), + builder: (context) { + final effect = chatThemeData.colorTheme.borderTop; + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 26), + if (icon != null) icon, + const SizedBox(height: 26), + Text( + title, + style: chatThemeData.textTheme.headlineBold, + ), + const SizedBox(height: 7), + if (question != null) Text( - title, - style: chatThemeData.textTheme.headlineBold, + question, + textAlign: TextAlign.center, ), - const SizedBox(height: 7), - if (question != null) - Text( - question, - textAlign: TextAlign.center, - ), - const SizedBox(height: 36), - Container( - color: effect.color!.withOpacity(effect.alpha ?? 1), - height: 1, - ), - Row( - children: [ - if (cancelText != null) - Flexible( - child: Container( - alignment: Alignment.center, - child: TextButton( - onPressed: () { - Navigator.of(context).pop(false); - }, - child: Text( - cancelText, - style: chatThemeData.textTheme.bodyBold.copyWith( - color: chatThemeData.colorTheme.textHighEmphasis - .withOpacity(0.5)), - ), - ), - ), - ), + const SizedBox(height: 36), + Container( + color: effect.color!.withOpacity(effect.alpha ?? 1), + height: 1, + ), + Row( + children: [ + if (cancelText != null) Flexible( child: Container( alignment: Alignment.center, child: TextButton( onPressed: () { - Navigator.pop(context, true); + Navigator.of(context).pop(false); }, child: Text( - okText, + cancelText, style: chatThemeData.textTheme.bodyBold.copyWith( - color: chatThemeData.colorTheme.accentError), + color: chatThemeData.colorTheme.textHighEmphasis + .withOpacity(0.5), + ), ), ), ), ), - ], - ), - ], - ), - ); - }); + Flexible( + child: Container( + alignment: Alignment.center, + child: TextButton( + onPressed: () { + Navigator.pop(context, true); + }, + child: Text( + okText, + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.accentError, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ); + }, + ); } /// Shows info dialog @@ -119,10 +123,11 @@ Future showInfoDialog( theme?.colorTheme.barsBg ?? chatThemeData.colorTheme.barsBg, context: context, shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - )), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), builder: (context) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, @@ -147,8 +152,8 @@ Future showInfoDialog( height: 36, ), Container( - color: theme?.colorTheme.textHighEmphasis.withOpacity(.08) ?? - chatThemeData.colorTheme.textHighEmphasis.withOpacity(.08), + color: theme?.colorTheme.textHighEmphasis.withOpacity(0.08) ?? + chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.08), height: 1, ), Center( diff --git a/packages/stream_chat_flutter/lib/src/video_service.dart b/packages/stream_chat_flutter/lib/src/video_service.dart index e914f466..4abe88f9 100644 --- a/packages/stream_chat_flutter/lib/src/video_service.dart +++ b/packages/stream_chat_flutter/lib/src/video_service.dart @@ -6,6 +6,7 @@ import 'package:video_compress/video_compress.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; /// +// ignore: prefer-match-file-name class IVideoService { IVideoService._(); diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 6374fb50..a04de14e 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -55,7 +55,7 @@ flutter: uses-material-design: true dev_dependencies: - dart_code_metrics: ^4.2.0-dev.1 + dart_code_metrics: ^4.2.0-dev.5 flutter_test: sdk: flutter golden_toolkit: ^0.9.0 diff --git a/packages/stream_chat_flutter/test/src/theme/channel_preview_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/channel_preview_theme_test.dart index 3be39952..dc5c0529 100644 --- a/packages/stream_chat_flutter/test/src/theme/channel_preview_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/channel_preview_theme_test.dart @@ -59,7 +59,7 @@ final _channelPreviewThemeControl = ChannelPreviewThemeData( color: const Color(0xff7A7A7A), ), lastMessageAtStyle: TextTheme.light().footnote.copyWith( - color: ColorTheme.light().textHighEmphasis.withOpacity(.5), + color: ColorTheme.light().textHighEmphasis.withOpacity(0.5), ), indicatorIconSize: 16, ); @@ -83,7 +83,7 @@ final _channelPreviewThemeControlMidLerp = ChannelPreviewThemeData( fontSize: 12, ), lastMessageAtStyle: TextTheme.light().footnote.copyWith( - color: const Color(0x807f7f7f).withOpacity(.5), + color: const Color(0x807f7f7f).withOpacity(0.5), ), indicatorIconSize: 16, ); @@ -102,7 +102,7 @@ final _channelPreviewThemeControlDark = ChannelPreviewThemeData( color: const Color(0xff7A7A7A), ), lastMessageAtStyle: TextTheme.dark().footnote.copyWith( - color: ColorTheme.dark().textHighEmphasis.withOpacity(.5), + color: ColorTheme.dark().textHighEmphasis.withOpacity(0.5), ), indicatorIconSize: 16, ); diff --git a/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart b/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart index 3c456246..2a2a5ab7 100644 --- a/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart +++ b/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart @@ -94,6 +94,7 @@ class _BetterStreamBuilderState if (widget.errorBuilder != null && error != _lastError) { _lastError = error; if (mounted) { + // ignore: no-empty-block setState(() {}); } } @@ -106,7 +107,7 @@ class _BetterStreamBuilderState if (!isEqual) { _lastEvent = event; if (mounted) { - setState(() {}); + setState(() {}); // ignore: no-empty-block } } } diff --git a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart index 1aed7198..0e8d48bc 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -111,8 +111,7 @@ class ChannelsBlocState extends State _paginationEnded = false; } - if ((!clear && _paginationEnded) || - _queryChannelsLoadingController.value == true) { + if ((!clear && _paginationEnded) || _queryChannelsLoadingController.value) { return; } @@ -221,7 +220,8 @@ class ChannelsBlocState extends State .listen((e) { final channel = e.channel; _channelsController.add(List.from( - (channels ?? [])..removeWhere((c) => c.cid == channel?.cid))); + (channels ?? [])..removeWhere((c) => c.cid == channel?.cid), + )); })); } 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 443529ac..8117a26e 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -88,7 +88,7 @@ class StreamChannelState extends State { bool preferOffline = false, }) async { if (_topPaginationEnded || - _queryTopMessagesController.value == true || + _queryTopMessagesController.value || channel.state == null) { return; } @@ -120,9 +120,9 @@ class StreamChannelState extends State { bool preferOffline = false, }) async { if (_bottomPaginationEnded || - _queryBottomMessagesController.value == true || + _queryBottomMessagesController.value || channel.state == null || - channel.state!.isUpToDate == true) return; + channel.state!.isUpToDate) return; _queryBottomMessagesController.add(true); if (channel.state!.messages.isEmpty) { @@ -164,7 +164,7 @@ class StreamChannelState extends State { bool preferOffline = false, }) async { if (_topPaginationEnded || - _queryTopMessagesController.value == true || + _queryTopMessagesController.value || channel.state == null) return; _queryTopMessagesController.add(true); diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index a3364f08..50fad9ee 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: stream_chat: ^3.0.0 dev_dependencies: - dart_code_metrics: ^4.2.0-dev.1 + dart_code_metrics: ^4.2.0-dev.5 fake_async: ^1.2.0 flutter_test: sdk: flutter 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 4094dcaa..b9ac541e 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -32,7 +32,7 @@ const kStreamChatSupportedLanguages = { 'it', 'es', 'ja', - 'ko' + 'ko', }; /// Creates a [GlobalStreamChatLocalizations] instance for the given `locale`. 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 5cb59755..c1106cab 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 @@ -350,8 +350,10 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get youText => 'You'; @override - String galleryPaginationText( - {required int currentPage, required int totalPages}) => + String galleryPaginationText({ + required int currentPage, + required int totalPages, + }) => '${currentPage + 1} of $totalPages'; @override diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart index 69ee9237..49e66184 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart @@ -355,8 +355,10 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get youText => 'Usted'; @override - String galleryPaginationText( - {required int currentPage, required int totalPages}) => + String galleryPaginationText({ + required int currentPage, + required int totalPages, + }) => '${currentPage + 1} de $totalPages'; @override diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart index 0727075a..0057b7cc 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -354,8 +354,10 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get youText => 'Vous'; @override - String galleryPaginationText( - {required int currentPage, required int totalPages}) => + String galleryPaginationText({ + required int currentPage, + required int totalPages, + }) => '${currentPage + 1} de $totalPages'; @override diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart index ea1738de..0d7e1ec0 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -349,8 +349,10 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get youText => 'आप'; @override - String galleryPaginationText( - {required int currentPage, required int totalPages}) => + String galleryPaginationText({ + required int currentPage, + required int totalPages, + }) => '${currentPage + 1} ऑफ़ $totalPages'; @override diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart index e60d4afe..18e961d6 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -351,8 +351,10 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get youText => 'te'; @override - String galleryPaginationText( - {required int currentPage, required int totalPages}) => + String galleryPaginationText({ + required int currentPage, + required int totalPages, + }) => '${currentPage + 1} di $totalPages'; @override diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart index a52a914f..3693bc11 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart @@ -334,9 +334,12 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String get youText => '당신'; @override - String galleryPaginationText( - {required int currentPage, required int totalPages}) => + String galleryPaginationText({ + required int currentPage, + required int totalPages, + }) => '${currentPage + 1} / $totalPages'; + //3 / 11 @override diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index 4f8a3ad3..78218a98 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -17,6 +17,6 @@ dependencies: stream_chat_flutter: ^3.0.0 dev_dependencies: - dart_code_metrics: ^4.2.0-dev.1 + dart_code_metrics: ^4.2.0-dev.5 flutter_test: sdk: flutter diff --git a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart index ef140239..ddaafdc5 100644 --- a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart @@ -62,8 +62,10 @@ class MessageDao extends DatabaseAccessor Future getMessageById(String id) async => await (select(messages).join([ leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), - leftOuterJoin(_pinnedByUsers, - messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), + leftOuterJoin( + _pinnedByUsers, + messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), ]) ..where(messages.id.equals(id))) .map(_messageFromJoinRow) @@ -74,8 +76,10 @@ class MessageDao extends DatabaseAccessor Future> getThreadMessages(String cid) async => Future.wait(await (select(messages).join([ leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), - leftOuterJoin(_pinnedByUsers, - messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), + leftOuterJoin( + _pinnedByUsers, + messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), ]) ..where(messages.channelCid.equals(cid)) ..where(messages.parentId.isNotNull()) @@ -92,7 +96,9 @@ class MessageDao extends DatabaseAccessor final msgList = await Future.wait(await (select(messages).join([ leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), leftOuterJoin( - _pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), + _pinnedByUsers, + messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), ]) ..where(messages.parentId.isNotNull()) ..where(messages.parentId.equals(parentId)) @@ -134,7 +140,9 @@ class MessageDao extends DatabaseAccessor final msgList = await Future.wait(await (select(messages).join([ leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), leftOuterJoin( - _pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), + _pinnedByUsers, + messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), ]) ..where(messages.channelCid.equals(cid)) ..where( diff --git a/packages/stream_chat_persistence/lib/src/dao/pinned_message_dao.dart b/packages/stream_chat_persistence/lib/src/dao/pinned_message_dao.dart index 9bc0fa64..bac52aa6 100644 --- a/packages/stream_chat_persistence/lib/src/dao/pinned_message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/pinned_message_dao.dart @@ -64,8 +64,10 @@ class PinnedMessageDao extends DatabaseAccessor Future getMessageById(String id) async => await (select(pinnedMessages).join([ leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - leftOuterJoin(_pinnedByUsers, - pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), + leftOuterJoin( + _pinnedByUsers, + pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), ]) ..where(pinnedMessages.id.equals(id))) .map(_messageFromJoinRow) @@ -76,8 +78,10 @@ class PinnedMessageDao extends DatabaseAccessor Future> getThreadMessages(String cid) async => Future.wait(await (select(pinnedMessages).join([ leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - leftOuterJoin(_pinnedByUsers, - pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), + leftOuterJoin( + _pinnedByUsers, + pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), ]) ..where(pinnedMessages.channelCid.equals(cid)) ..where(pinnedMessages.parentId.isNotNull()) @@ -93,8 +97,10 @@ class PinnedMessageDao extends DatabaseAccessor }) async { final msgList = await Future.wait(await (select(pinnedMessages).join([ leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - leftOuterJoin(_pinnedByUsers, - pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), + leftOuterJoin( + _pinnedByUsers, + pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), ]) ..where(pinnedMessages.parentId.isNotNull()) ..where(pinnedMessages.parentId.equals(parentId)) @@ -134,8 +140,10 @@ class PinnedMessageDao extends DatabaseAccessor }) async { final msgList = await Future.wait(await (select(pinnedMessages).join([ leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - leftOuterJoin(_pinnedByUsers, - pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)), + leftOuterJoin( + _pinnedByUsers, + pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), ]) ..where(pinnedMessages.channelCid.equals(cid)) ..where(pinnedMessages.parentId.isNull() | diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart index 85f7b2de..23f63d72 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart @@ -10,29 +10,30 @@ export 'shared/shared_db.dart'; part 'moor_chat_database.g.dart'; /// A chat database implemented using moor -@UseMoor(tables: [ - Channels, - Messages, - PinnedMessages, - PinnedMessageReactions, - Reactions, - Users, - Members, - Reads, - ChannelQueries, - ConnectionEvents, -], daos: [ - UserDao, - ChannelDao, - MessageDao, - PinnedMessageDao, - PinnedMessageReactionDao, - MemberDao, - ReactionDao, - ReadDao, - ChannelQueryDao, - ConnectionEventDao, -]) +@UseMoor( + tables: [ + Channels, + Messages, + PinnedMessages, + PinnedMessageReactions,Reactions, + Users, + Members, + Reads, + ChannelQueries, + ConnectionEvents, + ], + daos: [ + UserDao, + ChannelDao, + MessageDao, + PinnedMessageDao,PinnedMessageReactionDao, + MemberDao, + ReactionDao, + ReadDao, + ChannelQueryDao, + ConnectionEventDao, + ], +) class MoorChatDatabase extends _$MoorChatDatabase { /// Creates a new moor chat database instance MoorChatDatabase( diff --git a/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart b/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart index d7db7924..9d81fecd 100644 --- a/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart +++ b/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart @@ -229,7 +229,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { final parentId = message.parentId!; messageByParentIdDictionary[parentId] = [ ...messageByParentIdDictionary[parentId] ?? [], - message + message, ]; } return messageByParentIdDictionary; diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index 59cd6f41..b1b9f1e3 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: dev_dependencies: build_runner: ^2.0.1 - dart_code_metrics: ^4.2.0-dev.1 + dart_code_metrics: ^4.2.0-dev.5 flutter_test: sdk: flutter mocktail: ^0.1.1 diff --git a/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart index 2ea1e73a..bc07919a 100644 --- a/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart @@ -82,9 +82,9 @@ void main() { cid: cids[index], createdBy: users[index], config: ChannelConfig(), - extraData: {'test_custom_field': 3 + index}, + extraData: {'test_custom_field': index + 3}, createdAt: now, - memberCount: 3 + index, + memberCount: index + 3, lastMessageAt: now.add(Duration(hours: index)), ), ).reversed.toList(growable: false); From f7db4b1071945f5732514db6fa9a050fa1b5f90b Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 13 Sep 2021 13:11:53 +0530 Subject: [PATCH 03/29] chore(repo): add metrics workflow Signed-off-by: xsahil03x --- .github/workflows/dart_code_metrics.yaml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/dart_code_metrics.yaml diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml new file mode 100644 index 00000000..0dd4cbc6 --- /dev/null +++ b/.github/workflows/dart_code_metrics.yaml @@ -0,0 +1,22 @@ +name: Dart Code Metrics + +on: + pull_request: + push: + branches: + - master + - develop + +jobs: + check: + name: dart-code-metrics + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: dart-code-metrics + uses: dart-code-checker/dart-code-metrics-action@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + folders: 'packages' \ No newline at end of file From 215734078424525ff328673ec6129204aefbf27d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 13 Sep 2021 13:55:41 +0530 Subject: [PATCH 04/29] chore(repo): run dart fix Signed-off-by: xsahil03x --- .../lib/src/theme/color_theme.dart | 14 ++++++++++++-- .../lib/src/theme/message_theme.dart | 5 ++++- .../stream_chat_flutter/lib/src/user_avatar.dart | 4 +++- .../lib/src/message_search_bloc.dart | 3 +-- .../lib/src/users_bloc.dart | 3 +-- .../lib/src/db/moor_chat_database.dart | 6 ++++-- 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/theme/color_theme.dart b/packages/stream_chat_flutter/lib/src/theme/color_theme.dart index 567c7b3e..b32c023f 100644 --- a/packages/stream_chat_flutter/lib/src/theme/color_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/color_theme.dart @@ -39,9 +39,19 @@ class ColorTheme { alpha: 0.08, ), this.shadowIconButton = const Effect( - sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4,), + sigmaX: 0, + sigmaY: 2, + color: Color(0xff000000), + alpha: 0.5, + blur: 4, + ), this.modalShadow = const Effect( - sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8,), + sigmaX: 0, + sigmaY: 0, + color: Color(0xff000000), + alpha: 1, + blur: 8, + ), }) : brightness = Brightness.light; /// Initialise with dark theme diff --git a/packages/stream_chat_flutter/lib/src/theme/message_theme.dart b/packages/stream_chat_flutter/lib/src/theme/message_theme.dart index 334f4850..0a5330f0 100644 --- a/packages/stream_chat_flutter/lib/src/theme/message_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/message_theme.dart @@ -100,7 +100,10 @@ class MessageThemeData with Diagnosticable { messageTextStyle: TextStyle.lerp(a.messageTextStyle, b.messageTextStyle, t), reactionsBackgroundColor: Color.lerp( - a.reactionsBackgroundColor, b.reactionsBackgroundColor, t,), + a.reactionsBackgroundColor, + b.reactionsBackgroundColor, + t, + ), reactionsBorderColor: Color.lerp(a.messageBorderColor, b.reactionsBorderColor, t), reactionsMaskColor: diff --git a/packages/stream_chat_flutter/lib/src/user_avatar.dart b/packages/stream_chat_flutter/lib/src/user_avatar.dart index a923011c..86b34ea7 100644 --- a/packages/stream_chat_flutter/lib/src/user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/user_avatar.dart @@ -92,7 +92,9 @@ class UserAvatar extends StatelessWidget { streamChatTheme .ownMessageTheme.avatarTheme?.borderRadius, image: DecorationImage( - image: imageProvider, fit: BoxFit.cover), + image: imageProvider, + fit: BoxFit.cover, + ), ), ), ) diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart index 6477dfcd..e247a1cf 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart @@ -90,8 +90,7 @@ class MessageSearchBlocState extends State _paginationEnded = false; } - if ((!clear && _paginationEnded) || - _queryMessagesLoadingController.value == true) { + if ((!clear && _paginationEnded) || _queryMessagesLoadingController.value) { return; } diff --git a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart index d112961d..fc791cd8 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -77,8 +77,7 @@ class UsersBlocState extends State _paginationEnded = false; } - if ((!clear && _paginationEnded) || - _queryUsersLoadingController.value == true) { + if ((!clear && _paginationEnded) || _queryUsersLoadingController.value) { return; } diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart index 23f63d72..35befbc1 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart @@ -15,7 +15,8 @@ part 'moor_chat_database.g.dart'; Channels, Messages, PinnedMessages, - PinnedMessageReactions,Reactions, + PinnedMessageReactions, + Reactions, Users, Members, Reads, @@ -26,7 +27,8 @@ part 'moor_chat_database.g.dart'; UserDao, ChannelDao, MessageDao, - PinnedMessageDao,PinnedMessageReactionDao, + PinnedMessageDao, + PinnedMessageReactionDao, MemberDao, ReactionDao, ReadDao, From 2ecbac8bff0f4a339004dee3465b0a94abe0b2e6 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 13 Sep 2021 15:23:02 +0530 Subject: [PATCH 05/29] chore: fix metrics workflow Signed-off-by: xsahil03x --- .github/workflows/dart_code_metrics.yaml | 39 +++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index 0dd4cbc6..70c62e17 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -1,5 +1,8 @@ name: Dart Code Metrics +env: + folders: "example, lib, test" + on: pull_request: push: @@ -10,13 +13,41 @@ on: jobs: check: name: dart-code-metrics - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - - name: dart-code-metrics + + - name: "Stream Chat Metrics" uses: dart-code-checker/dart-code-metrics-action@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }} - folders: 'packages' \ No newline at end of file + relative_path: 'packages/stream_chat' + folders: ${{ env.folders }} + + - name: "Stream Chat Flutter Metrics" + uses: dart-code-checker/dart-code-metrics-action@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + relative_path: 'packages/stream_chat_flutter' + folders: ${{ env.folders }} + + - name: "Stream Chat Flutter Core Metrics" + uses: dart-code-checker/dart-code-metrics-action@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + relative_path: 'packages/stream_chat_flutter_core' + folders: ${{ env.folders }} + + - name: "Stream Chat Localizations Metrics" + uses: dart-code-checker/dart-code-metrics-action@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + relative_path: 'packages/stream_chat_localizations' + folders: ${{ env.folders }} + + - name: "Stream Chat Persistence Metrics" + uses: dart-code-checker/dart-code-metrics-action@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + relative_path: 'packages/stream_chat_persistence' + folders: ${{ env.folders }} \ No newline at end of file From 3152fadacb0a503349bb1d97778b572047b019eb Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 13 Sep 2021 15:32:57 +0530 Subject: [PATCH 06/29] chore: update metrics workflow Signed-off-by: xsahil03x --- .github/workflows/dart_code_metrics.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index 70c62e17..e556aaec 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -1,7 +1,7 @@ name: Dart Code Metrics env: - folders: "example, lib, test" + folders: "lib, test" on: pull_request: @@ -24,13 +24,6 @@ jobs: relative_path: 'packages/stream_chat' folders: ${{ env.folders }} - - name: "Stream Chat Flutter Metrics" - uses: dart-code-checker/dart-code-metrics-action@v1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - relative_path: 'packages/stream_chat_flutter' - folders: ${{ env.folders }} - - name: "Stream Chat Flutter Core Metrics" uses: dart-code-checker/dart-code-metrics-action@v1 with: @@ -38,6 +31,13 @@ jobs: relative_path: 'packages/stream_chat_flutter_core' folders: ${{ env.folders }} + - name: "Stream Chat Flutter Metrics" + uses: dart-code-checker/dart-code-metrics-action@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + relative_path: 'packages/stream_chat_flutter' + folders: ${{ env.folders }} + - name: "Stream Chat Localizations Metrics" uses: dart-code-checker/dart-code-metrics-action@v1 with: From 36a2ee11a7049a224e39f51e966d8fee93555d0f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 13 Sep 2021 15:46:05 +0530 Subject: [PATCH 07/29] chore: update metrics workflow Signed-off-by: xsahil03x --- .github/workflows/dart_code_metrics.yaml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index e556aaec..9d36add8 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -1,6 +1,7 @@ name: Dart Code Metrics env: + flutter_version: "2.5.0" folders: "lib, test" on: @@ -15,7 +16,26 @@ jobs: name: dart-code-metrics runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: "Git Checkout" + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: "Cache Flutter dependencies" + uses: actions/cache@v2 + with: + path: /opt/hostedtoolcache/flutter + key: ${{ env.flutter_version }}-flutter + + - name: "Install Flutter" + uses: subosito/flutter-action@v1 + with: + flutter-version: ${{ env.flutter_version }} + + - name: "Install Tools" + run: flutter pub global activate melos + - name: "Bootstrap Workspace" + run: melos bootstrap - name: "Stream Chat Metrics" uses: dart-code-checker/dart-code-metrics-action@v1 From f7723198de8db4a53b6691b28de02e9ee5ef3a4d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 13 Sep 2021 16:26:46 +0530 Subject: [PATCH 08/29] chore: fix dependency conflicts Signed-off-by: xsahil03x --- melos.yaml | 2 +- packages/stream_chat_flutter/example/pubspec.yaml | 13 +++++++------ .../stream_chat_flutter_core/example/pubspec.yaml | 6 +++++- .../stream_chat_localizations/example/pubspec.yaml | 8 ++++++-- .../stream_chat_persistence/example/pubspec.yaml | 4 ++-- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/melos.yaml b/melos.yaml index 9c5d4e4f..062f57a3 100644 --- a/melos.yaml +++ b/melos.yaml @@ -4,7 +4,7 @@ versioning: mode: independent packages: - - packages/** + - packages/stream_chat*/* scripts: lint:all: diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index d3fec032..3afa4cc1 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -27,18 +27,19 @@ dependencies: cupertino_icons: ^1.0.3 flutter: sdk: flutter - # stream_chat: - # path: ../../stream_chat - # stream_chat_flutter_core: - # path: ../../stream_chat_flutter_core + +dependency_overrides: + stream_chat: + path: ../../stream_chat stream_chat_flutter: - path: ../ + path: ../../stream_chat_flutter + stream_chat_flutter_core: + path: ../../stream_chat_flutter_core stream_chat_localizations: path: ../../stream_chat_localizations stream_chat_persistence: path: ../../stream_chat_persistence - dev_dependencies: flutter_test: sdk: flutter diff --git a/packages/stream_chat_flutter_core/example/pubspec.yaml b/packages/stream_chat_flutter_core/example/pubspec.yaml index 6390abc4..9af602cc 100644 --- a/packages/stream_chat_flutter_core/example/pubspec.yaml +++ b/packages/stream_chat_flutter_core/example/pubspec.yaml @@ -26,8 +26,12 @@ dependencies: cupertino_icons: ^1.0.3 flutter: sdk: flutter + +dependency_overrides: + stream_chat: + path: ../../stream_chat stream_chat_flutter_core: - path: ../ + path: ../../stream_chat_flutter_core dev_dependencies: diff --git a/packages/stream_chat_localizations/example/pubspec.yaml b/packages/stream_chat_localizations/example/pubspec.yaml index a139831e..5d47ef36 100644 --- a/packages/stream_chat_localizations/example/pubspec.yaml +++ b/packages/stream_chat_localizations/example/pubspec.yaml @@ -11,12 +11,16 @@ dependencies: cupertino_icons: ^1.0.3 flutter: sdk: flutter - stream_chat_localizations: - path: ../ dependency_overrides: + stream_chat: + path: ../../stream_chat stream_chat_flutter: path: ../../stream_chat_flutter + stream_chat_flutter_core: + path: ../../stream_chat_flutter_core + stream_chat_localizations: + path: ../../stream_chat_localizations dev_dependencies: flutter_test: diff --git a/packages/stream_chat_persistence/example/pubspec.yaml b/packages/stream_chat_persistence/example/pubspec.yaml index 8dd6a719..cbf48a8b 100644 --- a/packages/stream_chat_persistence/example/pubspec.yaml +++ b/packages/stream_chat_persistence/example/pubspec.yaml @@ -11,12 +11,12 @@ dependencies: cupertino_icons: ^1.0.3 flutter: sdk: flutter - stream_chat_persistence: - path: ../ dependency_overrides: stream_chat: path: ../../stream_chat + stream_chat_persistence: + path: ../../stream_chat_persistence dev_dependencies: flutter_test: From bf21f7b3506b680a608dafadbae54bf0427100cf Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 30 Sep 2021 16:32:21 +0530 Subject: [PATCH 09/29] chore(repo): finish adding dart_code_metrics.yaml Signed-off-by: xsahil03x --- .github/workflows/dart_code_metrics.yaml | 2 +- .github/workflows/stream_flutter_workflow.yml | 2 +- analysis_options.yaml | 10 +- melos.yaml | 2 +- packages/stream_chat/example/pubspec.yaml | 5 +- .../stream_chat_flutter/example/pubspec.yaml | 17 +- .../lib/src/attachment_actions_modal.dart | 234 +++++++++--------- .../lib/src/full_screen_media.dart | 4 +- .../lib/src/gallery_footer.dart | 43 ++-- .../lib/src/message_input.dart | 69 +++--- .../lib/src/quoted_message_widget.dart | 2 +- .../example/pubspec.yaml | 10 +- .../example/pubspec.yaml | 14 +- .../example/pubspec.yaml | 10 +- 14 files changed, 203 insertions(+), 221 deletions(-) diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index 9d36add8..1272babc 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -33,7 +33,7 @@ jobs: flutter-version: ${{ env.flutter_version }} - name: "Install Tools" - run: flutter pub global activate melos + run: flutter pub global activate melos 1.0.0-dev.3 - name: "Bootstrap Workspace" run: melos bootstrap diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 28063c18..1eb5082a 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -31,7 +31,7 @@ jobs: flutter-version: ${{ env.flutter_version }} - name: "Install Tools" run: | - flutter pub global activate melos + flutter pub global activate melos 1.0.0-dev.3 - name: "Bootstrap Workspace" run: melos bootstrap - name: "Dart Analyze" diff --git a/analysis_options.yaml b/analysis_options.yaml index e52012e6..a1fa9388 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -149,12 +149,6 @@ linter: # https://dartcodemetrics.dev/docs/getting-started/introduction dart_code_metrics: - metrics-exclude: - - packages/*/test/** - metrics: - source-lines-of-code: 90 - maximum-nesting-level: 5 - halstead-volume: 150 rules: # Dart Specific - binary-expression-operand-order @@ -162,6 +156,7 @@ dart_code_metrics: - prefer-match-file-name: exclude: - packages/*/test/** + - packages/*/example/** - packages/**/util/** - packages/**/utils.dart - packages/stream_chat/lib/src/client/client.dart @@ -181,5 +176,4 @@ dart_code_metrics: # Flutter specific - always-remove-listener - - avoid-unnecessary-setstate - - avoid-wrapping-in-padding + - avoid-unnecessary-setstate \ No newline at end of file diff --git a/melos.yaml b/melos.yaml index 062f57a3..9c5d4e4f 100644 --- a/melos.yaml +++ b/melos.yaml @@ -4,7 +4,7 @@ versioning: mode: independent packages: - - packages/stream_chat*/* + - packages/** scripts: lint:all: diff --git a/packages/stream_chat/example/pubspec.yaml b/packages/stream_chat/example/pubspec.yaml index 1b092b3e..cf1f3874 100644 --- a/packages/stream_chat/example/pubspec.yaml +++ b/packages/stream_chat/example/pubspec.yaml @@ -1,4 +1,4 @@ -name: example +name: stream_chat_example description: A new Flutter project. publish_to: "none" @@ -11,8 +11,7 @@ dependencies: cupertino_icons: ^1.0.0 flutter: sdk: flutter - stream_chat: - path: ../ + stream_chat: ^2.2.1 dev_dependencies: flutter_test: diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index 3afa4cc1..9c7ae6ee 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -1,4 +1,4 @@ -name: example +name: stream_chat_flutter_example description: A new Flutter project. # The following line prevents the package from being accidentally published to @@ -27,18 +27,9 @@ dependencies: cupertino_icons: ^1.0.3 flutter: sdk: flutter - -dependency_overrides: - stream_chat: - path: ../../stream_chat - stream_chat_flutter: - path: ../../stream_chat_flutter - stream_chat_flutter_core: - path: ../../stream_chat_flutter_core - stream_chat_localizations: - path: ../../stream_chat_localizations - stream_chat_persistence: - path: ../../stream_chat_persistence + stream_chat_flutter: ^2.2.1 + stream_chat_localizations: ^1.1.0 + stream_chat_persistence: ^2.2.0 dev_dependencies: flutter_test: 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 f7f074c6..f49561aa 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart @@ -54,131 +54,135 @@ class AttachmentActionsModal extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ const SizedBox(height: kToolbarHeight), - Container( + Padding( padding: const EdgeInsets.only(right: 8), - width: MediaQuery.of(context).size.width * 0.5, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - ), - child: SizedBox( - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - _buildButton( - context, - context.translations.replyLabel, - StreamSvgIcon.iconCurveLineLeftUp( - size: 24, - color: theme.colorTheme.textLowEmphasis, - ), - () { - Navigator.pop(context, ReturnActionType.reply); - }, - ), - _buildButton( - context, - context.translations.showInChatLabel, - StreamSvgIcon.eye( - size: 24, - color: theme.colorTheme.textHighEmphasis, - ), - onShowMessage, - ), - _buildButton( - context, - message.attachments[currentIndex].type == 'video' - ? context.translations.saveVideoLabel - : context.translations.saveImageLabel, - StreamSvgIcon.iconSave( - size: 24, - color: theme.colorTheme.textLowEmphasis, - ), - () { - final attachment = message.attachments[currentIndex]; - final isImage = attachment.type == 'image'; - final Future Function( - Attachment, { - void Function(int, int) progressCallback, - }) saveFile = fileDownloader ?? _downloadAttachment; - final Future Function( - Attachment, { - void Function(int, int) progressCallback, - }) saveImage = imageDownloader ?? _downloadAttachment; - final downloader = isImage ? saveImage : saveFile; - - final progressNotifier = ValueNotifier<_DownloadProgress?>( - _DownloadProgress.initial(), - ); - - downloader( - attachment, - progressCallback: (received, total) { - progressNotifier.value = _DownloadProgress( - total, - received, - ); - }, - ).catchError((e, stk) { - progressNotifier.value = null; - }); - - // Closing attachment actions modal before opening - // attachment download dialog - Navigator.pop(context); - - showDialog( - barrierDismissible: false, - context: context, - barrierColor: theme.colorTheme.overlay, - builder: (context) => _buildDownloadProgressDialog( - context, - progressNotifier, - ), - ); - }, - ), - if (StreamChat.of(context).currentUser?.id == message.user?.id) + child: Container( + width: MediaQuery.of(context).size.width * 0.5, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + ), + child: SizedBox( + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ _buildButton( context, - context.translations.deleteLabel.capitalize(), - StreamSvgIcon.delete( + context.translations.replyLabel, + StreamSvgIcon.iconCurveLineLeftUp( size: 24, - color: theme.colorTheme.accentError, + color: theme.colorTheme.textLowEmphasis, ), () { - final channel = StreamChannel.of(context).channel; - if (message.attachments.length > 1 || - message.text?.isNotEmpty == true) { - final remainingAttachments = [...message.attachments] - ..removeAt(currentIndex); - channel.updateMessage(message.copyWith( - attachments: remainingAttachments, - )); - Navigator.of(context) - ..pop() - ..maybePop(); - } else { - channel.deleteMessage(message); - Navigator.of(context) - ..pop() - ..maybePop(); - } + Navigator.pop(context, ReturnActionType.reply); }, - color: theme.colorTheme.accentError, ), - ] - .map((e) => Align( - alignment: Alignment.centerRight, - child: e, - )) - .insertBetween( - Container( - height: 1, - color: theme.colorTheme.borders, + _buildButton( + context, + context.translations.showInChatLabel, + StreamSvgIcon.eye( + size: 24, + color: theme.colorTheme.textHighEmphasis, ), + onShowMessage, ), + _buildButton( + context, + message.attachments[currentIndex].type == 'video' + ? context.translations.saveVideoLabel + : context.translations.saveImageLabel, + StreamSvgIcon.iconSave( + size: 24, + color: theme.colorTheme.textLowEmphasis, + ), + () { + final attachment = message.attachments[currentIndex]; + final isImage = attachment.type == 'image'; + final Future Function( + Attachment, { + void Function(int, int) progressCallback, + }) saveFile = fileDownloader ?? _downloadAttachment; + final Future Function( + Attachment, { + void Function(int, int) progressCallback, + }) saveImage = imageDownloader ?? _downloadAttachment; + final downloader = isImage ? saveImage : saveFile; + + final progressNotifier = + ValueNotifier<_DownloadProgress?>( + _DownloadProgress.initial(), + ); + + downloader( + attachment, + progressCallback: (received, total) { + progressNotifier.value = _DownloadProgress( + total, + received, + ); + }, + ).catchError((e, stk) { + progressNotifier.value = null; + }); + + // Closing attachment actions modal before opening + // attachment download dialog + Navigator.pop(context); + + showDialog( + barrierDismissible: false, + context: context, + barrierColor: theme.colorTheme.overlay, + builder: (context) => _buildDownloadProgressDialog( + context, + progressNotifier, + ), + ); + }, + ), + if (StreamChat.of(context).currentUser?.id == + message.user?.id) + _buildButton( + context, + context.translations.deleteLabel.capitalize(), + StreamSvgIcon.delete( + size: 24, + color: theme.colorTheme.accentError, + ), + () { + final channel = StreamChannel.of(context).channel; + if (message.attachments.length > 1 || + message.text?.isNotEmpty == true) { + final remainingAttachments = [...message.attachments] + ..removeAt(currentIndex); + channel.updateMessage(message.copyWith( + attachments: remainingAttachments, + )); + Navigator.of(context) + ..pop() + ..maybePop(); + } else { + channel.deleteMessage(message); + Navigator.of(context) + ..pop() + ..maybePop(); + } + }, + color: theme.colorTheme.accentError, + ), + ] + .map((e) => Align( + alignment: Alignment.centerRight, + child: e, + )) + .insertBetween( + Container( + height: 1, + color: theme.colorTheme.borders, + ), + ), + ), ), ), ), 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 57dadfc6..0c47383c 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -112,8 +112,8 @@ class _FullScreenMediaState extends State attachment.assetUrl ?? attachment.thumbUrl; return PhotoView( - loadingBuilder: (context, image) => - const Offstage(),imageProvider: (imageUrl == null && + loadingBuilder: (context, image) => const Offstage(), + imageProvider: (imageUrl == null && attachment.localUri != null && attachment.file?.bytes != null) ? Image.memory(attachment.file!.bytes!).image diff --git a/packages/stream_chat_flutter/lib/src/gallery_footer.dart b/packages/stream_chat_flutter/lib/src/gallery_footer.dart index 87ef1759..dcfa0e29 100644 --- a/packages/stream_chat_flutter/lib/src/gallery_footer.dart +++ b/packages/stream_chat_flutter/lib/src/gallery_footer.dart @@ -251,26 +251,29 @@ class _GalleryFooterState extends State { children: [ media, if (widget.message.user != null) - Container( - padding: const EdgeInsets.all(10), - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withOpacity(0.6), - boxShadow: [ - BoxShadow( - blurRadius: 8, - color: chatThemeData - .colorTheme.textHighEmphasis - .withOpacity(0.3), - ), - ], - ), - child: UserAvatar( - user: widget.message.user!, - constraints: - BoxConstraints.tight(const Size(24, 24)), - showOnlineStatus: false, + Padding( + padding: const EdgeInsets.all(8), + child: Container( + padding: const EdgeInsets.all(2), + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.6), + boxShadow: [ + BoxShadow( + blurRadius: 8, + color: chatThemeData + .colorTheme.textHighEmphasis + .withOpacity(0.3), + ), + ], + ), + child: UserAvatar( + user: widget.message.user!, + constraints: + BoxConstraints.tight(const Size(24, 24)), + showOnlineStatus: false, + ), ), ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 6f10bb5c..99819f77 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -752,28 +752,31 @@ class MessageInputState extends State { ? Row( mainAxisSize: MainAxisSize.min, children: [ - Container( + Padding( padding: const EdgeInsets.all(8), - constraints: BoxConstraints.tight(const Size(64, 24)), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: _streamChatTheme.colorTheme.accentPrimary, - ), - alignment: Alignment.center, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - StreamSvgIcon.lightning( - color: Colors.white, - size: 16, - ), - Text( - _chosenCommand?.name.toUpperCase() ?? '', - style: _streamChatTheme.textTheme.footnoteBold.copyWith( + child: Container( + constraints: BoxConstraints.tight(const Size(64, 24)), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: _streamChatTheme.colorTheme.accentPrimary, + ), + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + StreamSvgIcon.lightning( color: Colors.white, + size: 16, ), - ), - ], + Text( + _chosenCommand?.name.toUpperCase() ?? '', + style: + _streamChatTheme.textTheme.footnoteBold.copyWith( + color: Colors.white, + ), + ), + ], + ), ), ), ], @@ -1024,8 +1027,10 @@ class MessageInputState extends State { _attachments.isNotEmpty) ? null : () { - pickFile(DefaultAttachmentTypes.image, - camera: true); + pickFile( + DefaultAttachmentTypes.image, + camera: true, + ); }, ), IconButton( @@ -1038,8 +1043,10 @@ class MessageInputState extends State { _attachments.isNotEmpty) ? null : () { - pickFile(DefaultAttachmentTypes.video, - camera: true); + pickFile( + DefaultAttachmentTypes.video, + camera: true, + ); }, ), ], @@ -1053,13 +1060,15 @@ class MessageInputState extends State { ), ), child: Center( - child: Container( - width: 40, - height: 4, - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: _streamChatTheme.colorTheme.inputBg, - borderRadius: BorderRadius.circular(4), + child: Padding( + padding: const EdgeInsets.all(8), + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: _streamChatTheme.colorTheme.inputBg, + borderRadius: BorderRadius.circular(4), + ), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart index 500ac039..c69d490d 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -189,7 +189,7 @@ class QuotedMessageWidget extends StatelessWidget { image: DecorationImage( fit: BoxFit.cover, image: CachedNetworkImageProvider( - attachment.imageUrl!, + attachment.thumbUrl!, ), ), ), diff --git a/packages/stream_chat_flutter_core/example/pubspec.yaml b/packages/stream_chat_flutter_core/example/pubspec.yaml index 9af602cc..4b0e27fb 100644 --- a/packages/stream_chat_flutter_core/example/pubspec.yaml +++ b/packages/stream_chat_flutter_core/example/pubspec.yaml @@ -1,4 +1,4 @@ -name: example +name: stream_chat_flutter_core_example description: Example app for testing stream_chat_flutter_core # The following line prevents the package from being accidentally published to @@ -26,13 +26,7 @@ dependencies: cupertino_icons: ^1.0.3 flutter: sdk: flutter - -dependency_overrides: - stream_chat: - path: ../../stream_chat - stream_chat_flutter_core: - path: ../../stream_chat_flutter_core - + stream_chat_flutter_core: ^2.2.1 dev_dependencies: flutter_test: diff --git a/packages/stream_chat_localizations/example/pubspec.yaml b/packages/stream_chat_localizations/example/pubspec.yaml index 5d47ef36..9aaa23c6 100644 --- a/packages/stream_chat_localizations/example/pubspec.yaml +++ b/packages/stream_chat_localizations/example/pubspec.yaml @@ -1,4 +1,4 @@ -name: example +name: stream_chat_localizations_example description: A new Flutter project. publish_to: 'none' @@ -11,16 +11,8 @@ dependencies: cupertino_icons: ^1.0.3 flutter: sdk: flutter - -dependency_overrides: - stream_chat: - path: ../../stream_chat - stream_chat_flutter: - path: ../../stream_chat_flutter - stream_chat_flutter_core: - path: ../../stream_chat_flutter_core - stream_chat_localizations: - path: ../../stream_chat_localizations + stream_chat_flutter: ^2.2.1 + stream_chat_localizations: ^1.1.0 dev_dependencies: flutter_test: diff --git a/packages/stream_chat_persistence/example/pubspec.yaml b/packages/stream_chat_persistence/example/pubspec.yaml index cbf48a8b..6c6e8c6d 100644 --- a/packages/stream_chat_persistence/example/pubspec.yaml +++ b/packages/stream_chat_persistence/example/pubspec.yaml @@ -1,4 +1,4 @@ -name: example +name: stream_chat_persistence_example description: A new Flutter project. publish_to: 'none' @@ -11,12 +11,8 @@ dependencies: cupertino_icons: ^1.0.3 flutter: sdk: flutter - -dependency_overrides: - stream_chat: - path: ../../stream_chat - stream_chat_persistence: - path: ../../stream_chat_persistence + stream_chat: ^2.2.1 + stream_chat_persistence: ^2.2.0 dev_dependencies: flutter_test: From 32d8b5573cfc0ad36cf877423f1928f5ce5fcacd Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 1 Oct 2021 13:55:23 +0530 Subject: [PATCH 10/29] chore: flutter format Signed-off-by: xsahil03x --- .../lib/src/message_input.dart | 16 ++++++++-------- 1 file changed, 8 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 99819f77..2d6669ed 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1198,14 +1198,14 @@ class MessageInputState extends State { textEditingController.value = TextEditingValue( text: rejoin + - textEditingController.text - .substring(textEditingController.selection.start, - ), - selection: TextSelection.collapsed( - offset: rejoin.length, - ), - ); - _onChangedDebounced.cancel(); + textEditingController.text.substring( + textEditingController.selection.start, + ), + selection: TextSelection.collapsed( + offset: rejoin.length, + ), + ); + _onChangedDebounced.cancel(); setState(() => _showMentionsOverlay = false); }, ); From dff64067d2d5c763e8b25ac869fafb851f21ba4e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 1 Oct 2021 16:57:02 +0530 Subject: [PATCH 11/29] chore: flutter format Signed-off-by: xsahil03x --- packages/stream_chat/lib/src/client/channel.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 9bcadf87..e3f1139f 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1516,8 +1516,8 @@ class ChannelClientState { } final secondsFromEpoch = int.parse(uri.queryParameters['Expires']!); - final expiration = DateTime.fromMillisecondsSinceEpoch( - secondsFromEpoch * 1000); + final expiration = + DateTime.fromMillisecondsSinceEpoch(secondsFromEpoch * 1000); return expiration.isBefore(DateTime.now()); })) .map((e) => e.id) From 1b4b4cdf0042475831b4b11e5e179750d94dd62f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 1 Oct 2021 17:30:47 +0530 Subject: [PATCH 12/29] chore: fix analysis checks Signed-off-by: xsahil03x --- .../lib/src/commands_overlay.dart | 16 +++++---- .../lib/src/emoji_overlay.dart | 4 +-- .../lib/src/message_input.dart | 5 +-- .../src/{overlays.dart => multi_overlay.dart} | 34 +++++++++---------- .../lib/src/video_service.dart | 12 +++---- 5 files changed, 38 insertions(+), 33 deletions(-) rename packages/stream_chat_flutter/lib/src/{overlays.dart => multi_overlay.dart} (100%) diff --git a/packages/stream_chat_flutter/lib/src/commands_overlay.dart b/packages/stream_chat_flutter/lib/src/commands_overlay.dart index ffaec928..9f8aca1e 100644 --- a/packages/stream_chat_flutter/lib/src/commands_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/commands_overlay.dart @@ -49,8 +49,9 @@ class CommandsOverlay extends StatelessWidget { child: Container( constraints: BoxConstraints.loose(size), decoration: BoxDecoration( - color: _streamChatTheme.colorTheme.barsBg, - borderRadius: BorderRadius.circular(8)), + color: _streamChatTheme.colorTheme.barsBg, + borderRadius: BorderRadius.circular(8), + ), child: ListView( padding: const EdgeInsets.all(0), shrinkWrap: true, @@ -72,9 +73,9 @@ class CommandsOverlay extends StatelessWidget { context.translations.instantCommandsLabel, style: TextStyle( color: _streamChatTheme.colorTheme.textHighEmphasis - .withOpacity(.5), + .withOpacity(0.5), ), - ) + ), ], ), ), @@ -102,7 +103,8 @@ class CommandsOverlay extends StatelessWidget { TextSpan( text: c.name.capitalize(), style: const TextStyle( - fontWeight: FontWeight.bold), + fontWeight: FontWeight.bold, + ), children: [ TextSpan( text: ' /${c.name} ${c.args}', @@ -132,7 +134,9 @@ class CommandsOverlay extends StatelessWidget { } Widget _buildCommandIcon( - StreamChatThemeData _streamChatTheme, String iconType) { + StreamChatThemeData _streamChatTheme, + String iconType, + ) { switch (iconType) { case 'giphy': return CircleAvatar( diff --git a/packages/stream_chat_flutter/lib/src/emoji_overlay.dart b/packages/stream_chat_flutter/lib/src/emoji_overlay.dart index c37dd687..0a3cb634 100644 --- a/packages/stream_chat_flutter/lib/src/emoji_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/emoji_overlay.dart @@ -81,10 +81,10 @@ class EmojiOverlay extends StatelessWidget { ), style: TextStyle( color: _streamChatTheme.colorTheme.textHighEmphasis - .withOpacity(.5), + .withOpacity(0.5), ), ), - ) + ), ], ), ); diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 2d6669ed..37f6eb67 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -17,7 +17,7 @@ import 'package:stream_chat_flutter/src/emoji_overlay.dart'; import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; -import 'package:stream_chat_flutter/src/overlays.dart'; +import 'package:stream_chat_flutter/src/multi_overlay.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; @@ -639,7 +639,7 @@ class MessageInputState extends State { ), secondChild: widget.disableAttachments && !widget.showCommandsButton && - widget.actions.isNotEmpty != true + !widget.actions.isNotEmpty ? const Offstage() : Wrap( children: [ @@ -826,6 +826,7 @@ class MessageInputState extends State { final channel = StreamChannel.of(context).channel; if (value.isNotEmpty) { + // ignore: no-empty-block channel.keyStroke(widget.parentMessage?.id).catchError((e) {}); } diff --git a/packages/stream_chat_flutter/lib/src/overlays.dart b/packages/stream_chat_flutter/lib/src/multi_overlay.dart similarity index 100% rename from packages/stream_chat_flutter/lib/src/overlays.dart rename to packages/stream_chat_flutter/lib/src/multi_overlay.dart index 8d36be50..31b9d5e9 100644 --- a/packages/stream_chat_flutter/lib/src/overlays.dart +++ b/packages/stream_chat_flutter/lib/src/multi_overlay.dart @@ -2,23 +2,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_portal/flutter_portal.dart'; -/// Class that contains the parameters for building an overlay entry -class OverlayOptions { - /// Constructs a new overlay options object - /// [visible] - the visibility of the overlay - /// [widget] - the widget to be displayed - OverlayOptions({ - required this.visible, - required this.widget, - }); - - /// the visibility of the overlay - final bool visible; - - /// the widget to be displayed - final Widget widget; -} - /// Widget that renders a single overlay widget from a list of [overlayOptions] /// It shows the first one that is visible class MultiOverlay extends StatelessWidget { @@ -61,3 +44,20 @@ class MultiOverlay extends StatelessWidget { ); } } + +/// Class that contains the parameters for building an overlay entry +class OverlayOptions { + /// Constructs a new overlay options object + /// [visible] - the visibility of the overlay + /// [widget] - the widget to be displayed + OverlayOptions({ + required this.visible, + required this.widget, + }); + + /// the visibility of the overlay + final bool visible; + + /// the widget to be displayed + final Widget widget; +} diff --git a/packages/stream_chat_flutter/lib/src/video_service.dart b/packages/stream_chat_flutter/lib/src/video_service.dart index 4abe88f9..a50a43c8 100644 --- a/packages/stream_chat_flutter/lib/src/video_service.dart +++ b/packages/stream_chat_flutter/lib/src/video_service.dart @@ -7,11 +7,11 @@ import 'package:video_thumbnail/video_thumbnail.dart'; /// // ignore: prefer-match-file-name -class IVideoService { - IVideoService._(); +class _IVideoService { + _IVideoService._(); - /// Singleton instance of [IVideoService] - static final IVideoService instance = IVideoService._(); + /// Singleton instance of [_IVideoService] + static final _IVideoService instance = _IVideoService._(); final _lock = Lock(); /// compress video from [path] @@ -65,6 +65,6 @@ class IVideoService { ); } -/// Get instance of [IVideoService] +/// Get instance of [_IVideoService] // ignore: non_constant_identifier_names -IVideoService get VideoService => IVideoService.instance; +_IVideoService get VideoService => _IVideoService.instance; From cf50a442e70b91d499f283b10186162bd71cd25e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 7 Oct 2021 19:49:16 +0530 Subject: [PATCH 13/29] fix: reduce test to satisfy dependency for code metrics Signed-off-by: xsahil03x --- packages/stream_chat/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 123c4b21..ca92217f 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -31,4 +31,4 @@ dev_dependencies: freezed: ^0.14.1+3 json_serializable: ^5.0.2 mocktail: ^0.1.1 - test: ^1.18.2 + test: ^1.17.12 \ No newline at end of file From 506146959c08881a52a4b7eb6ce1efca7029e0e4 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 7 Oct 2021 19:50:07 +0530 Subject: [PATCH 14/29] chore: bump code metrics to v4.4.0 Signed-off-by: xsahil03x --- melos.yaml | 2 +- packages/stream_chat/pubspec.yaml | 2 +- packages/stream_chat_flutter/pubspec.yaml | 2 +- packages/stream_chat_flutter_core/pubspec.yaml | 2 +- packages/stream_chat_localizations/pubspec.yaml | 2 +- packages/stream_chat_persistence/pubspec.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/melos.yaml b/melos.yaml index 9c5d4e4f..4e1c08ac 100644 --- a/melos.yaml +++ b/melos.yaml @@ -88,7 +88,7 @@ scripts: description: Runs the docusaurus documentation locally. dev_dependencies: - dart_code_metrics: ^4.2.0-dev.5 + dart_code_metrics: ^4.4.0 environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index ca92217f..e2f0356a 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: dev_dependencies: build_runner: ^2.0.1 - dart_code_metrics: ^4.2.0-dev.5 + dart_code_metrics: ^4.4.0 freezed: ^0.14.1+3 json_serializable: ^5.0.2 mocktail: ^0.1.1 diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 30760c3e..a36baba9 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -55,7 +55,7 @@ flutter: uses-material-design: true dev_dependencies: - dart_code_metrics: ^4.2.0-dev.5 + dart_code_metrics: ^4.4.0 flutter_test: sdk: flutter golden_toolkit: ^0.10.0 diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 97efe648..1bc8a1f9 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: stream_chat: ^3.1.1 dev_dependencies: - dart_code_metrics: ^4.2.0-dev.5 + dart_code_metrics: ^4.4.0 fake_async: ^1.2.0 flutter_test: sdk: flutter diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index 78218a98..aaa4c56b 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -17,6 +17,6 @@ dependencies: stream_chat_flutter: ^3.0.0 dev_dependencies: - dart_code_metrics: ^4.2.0-dev.5 + dart_code_metrics: ^4.4.0 flutter_test: sdk: flutter diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index b1b9f1e3..3e24da27 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: dev_dependencies: build_runner: ^2.0.1 - dart_code_metrics: ^4.2.0-dev.5 + dart_code_metrics: ^4.4.0 flutter_test: sdk: flutter mocktail: ^0.1.1 From 0146d2cc85ce486e71416faf670ca58f428830bd Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 7 Oct 2021 16:40:29 +0200 Subject: [PATCH 15/29] fix(ui): missing trailing comma --- 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 c346d3f7..ad2ff455 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -677,7 +677,8 @@ class _MessageListViewState extends State { child: BetterStreamBuilder>( initialData: _itemPositionListener.itemPositions.value, stream: _valueListenableToStreamAdapter( - _itemPositionListener.itemPositions), + _itemPositionListener.itemPositions, + ), comparator: (a, b) { if (a == null || b == null) { return false; From fb6e1e8977fb7e0fa06da75ee173e5417a634c02 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 8 Oct 2021 17:28:29 +0530 Subject: [PATCH 16/29] fix for markAllRead() --- packages/stream_chat/lib/src/client/client.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index ae6716fd..753eabc0 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1376,6 +1376,8 @@ class ClientState { _listenChannelHidden(); _listenUserUpdated(); + + _listenAllChannelsRead(); } final _subscriptions = []; @@ -1405,6 +1407,18 @@ class ClientState { })); } + void _listenAllChannelsRead() { + _subscriptions + .add(_client.on(EventType.notificationMarkRead).listen((event) { + if (event.cid == null) { + channels + .forEach((key, value) { + value.state?.unreadCount = 0; + }); + } + })); + } + void _listenChannelDeleted() { _subscriptions.add(_client .on( From 19c0491b9db68be3d41e8eae6d29d3eab75a808f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 8 Oct 2021 17:43:31 +0530 Subject: [PATCH 17/29] fmt and changelog --- packages/stream_chat/CHANGELOG.md | 6 ++++++ packages/stream_chat/lib/src/client/client.dart | 7 +++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 667365d0..e1ea988f 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +🐞 Fixed + +- markAllRead() now updates channel states. + ## 3.1.1 ✅ Added diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 753eabc0..6b6ae933 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1411,10 +1411,9 @@ class ClientState { _subscriptions .add(_client.on(EventType.notificationMarkRead).listen((event) { if (event.cid == null) { - channels - .forEach((key, value) { - value.state?.unreadCount = 0; - }); + channels.forEach((key, value) { + value.state?.unreadCount = 0; + }); } })); } From 7d90dc0cfe4bf7b957c4ec703574468f5e2934da Mon Sep 17 00:00:00 2001 From: Efthymios Sarmpanis Date: Thu, 30 Sep 2021 01:06:41 +0300 Subject: [PATCH 18/29] feat(ui): add the ability to add a background image in MessageListView --- packages/stream_chat_flutter/CHANGELOG.md | 4 ++ .../example/assets/background_doodle.png | Bin 0 -> 158626 bytes .../example/lib/tutorial_part_6.dart | 6 ++ .../stream_chat_flutter/example/pubspec.yaml | 4 +- .../lib/src/message_list_view.dart | 10 +++- .../src/theme/message_list_view_theme.dart | 20 ++++++- .../test/src/message_list_view_test.dart | 55 +++++++++++++++++- .../theme/message_list_view_theme_test.dart | 38 ++++++++++++ 8 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 packages/stream_chat_flutter/example/assets/background_doodle.png diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index ffc30572..c67b1e7d 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -34,6 +34,10 @@ - `UserListView` `filter` property now is non-nullable. +✅ Added + +- `MessageListViewThemeData` now accepts a `DecorationImage` as a background image for `MessageListView`. + 🐞 Fixed - [[#668]](https://github.com/GetStream/stream-chat-flutter/issues/668): Fix `MessageInput` rendering errors in case diff --git a/packages/stream_chat_flutter/example/assets/background_doodle.png b/packages/stream_chat_flutter/example/assets/background_doodle.png new file mode 100644 index 0000000000000000000000000000000000000000..9ddf754f819cbf9aee379c53622d7af4b46c9d06 GIT binary patch literal 158626 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